Merge "Verify arguments passed are non-null." into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 3620a11..59a7cbc 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -21,6 +21,7 @@
         // !!! KEEP THIS LIST ALPHABETICAL !!!
         "aconfig_mediacodec_flags_java_lib",
         "android.adaptiveauth.flags-aconfig-java",
+        "android.app.appfunctions.flags-aconfig-java",
         "android.app.contextualsearch.flags-aconfig-java",
         "android.app.flags-aconfig-java",
         "android.app.ondeviceintelligence-aconfig-java",
@@ -172,6 +173,7 @@
 // Window
 aconfig_declarations {
     name: "com.android.window.flags.window-aconfig",
+    exportable: true,
     package: "com.android.window.flags",
     container: "system",
     srcs: ["core/java/android/window/flags/*.aconfig"],
@@ -1108,6 +1110,7 @@
 // Chooser / "Sharesheet"
 aconfig_declarations {
     name: "android.service.chooser.flags-aconfig",
+    exportable: true,
     package: "android.service.chooser",
     container: "system",
     srcs: ["core/java/android/service/chooser/flags.aconfig"],
@@ -1381,6 +1384,21 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+// AppFunctions
+aconfig_declarations {
+    name: "android.app.appfunctions.flags-aconfig",
+    exportable: true,
+    package: "android.app.appfunctions.flags",
+    container: "system",
+    srcs: ["core/java/android/app/appfunctions/flags/flags.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.app.appfunctions.flags-aconfig-java",
+    aconfig_declarations: "android.app.appfunctions.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // Adaptive Auth
 aconfig_declarations {
     name: "android.adaptiveauth.flags-aconfig",
diff --git a/INPUT_OWNERS b/INPUT_OWNERS
index 06ead06..9b1016e 100644
--- a/INPUT_OWNERS
+++ b/INPUT_OWNERS
@@ -1,4 +1,5 @@
 # Bug component: 136048
+# Please assign bugs to android-framework-input-triage@.
 arpitks@google.com
 asmitapoddar@google.com
 hcutts@google.com
diff --git a/PERFORMANCE_OWNERS b/PERFORMANCE_OWNERS
index 48a0201..02b0a1e 100644
--- a/PERFORMANCE_OWNERS
+++ b/PERFORMANCE_OWNERS
@@ -6,3 +6,4 @@
 shayba@google.com
 jdduke@google.com
 shombert@google.com
+kevinjeon@google.com
diff --git a/Ravenwood.bp b/Ravenwood.bp
index cebeb18..5f32ba0 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -25,25 +25,16 @@
     visibility: ["//visibility:private"],
 }
 
-// Generate the stub/impl from framework-all, with hidden APIs.
-java_genrule {
-    name: "framework-minus-apex.ravenwood-base",
+// Process framework-all with hoststubgen for Ravenwood.
+// This step takes several tens of seconds, so we manually shard it to multiple modules.
+// All the copies have to be kept in sync.
+// TODO: Do the sharding better, either by making hostsubgen support sharding natively, or
+// making a better build rule.
+
+genrule_defaults {
+    name: "framework-minus-apex.ravenwood-base_defaults",
+    defaults: ["ravenwood-internal-only-visibility-genrule"],
     tools: ["hoststubgen"],
-    cmd: "$(location hoststubgen) " +
-        "@$(location :ravenwood-standard-options) " +
-
-        "--debug-log $(location hoststubgen_framework-minus-apex.log) " +
-        "--stats-file $(location hoststubgen_framework-minus-apex_stats.csv) " +
-        "--supported-api-list-file $(location hoststubgen_framework-minus-apex_apis.csv) " +
-
-        "--out-impl-jar $(location ravenwood.jar) " +
-
-        "--gen-keep-all-file $(location hoststubgen_framework-minus-apex_keep_all.txt) " +
-        "--gen-input-dump-file $(location hoststubgen_framework-minus-apex_dump.txt) " +
-
-        "--in-jar $(location :framework-minus-apex-for-hoststubgen) " +
-        "--policy-override-file $(location :ravenwood-framework-policies) " +
-        "--annotation-allowed-classes-file $(location :ravenwood-annotation-allowed-classes) ",
     srcs: [
         ":framework-minus-apex-for-hoststubgen",
         ":ravenwood-framework-policies",
@@ -52,27 +43,120 @@
     ],
     out: [
         "ravenwood.jar",
+        "hoststubgen_framework-minus-apex.log",
+    ],
+}
 
-        // Following files are created just as FYI.
+framework_minus_apex_cmd = "$(location hoststubgen) " +
+    "@$(location :ravenwood-standard-options) " +
+    "--debug-log $(location hoststubgen_framework-minus-apex.log) " +
+    "--out-impl-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) "
+
+java_genrule {
+    name: "framework-minus-apex.ravenwood-base_X0",
+    defaults: ["framework-minus-apex.ravenwood-base_defaults"],
+    cmd: framework_minus_apex_cmd + " --num-shards 10 --shard-index 0",
+}
+
+java_genrule {
+    name: "framework-minus-apex.ravenwood-base_X1",
+    defaults: ["framework-minus-apex.ravenwood-base_defaults"],
+    cmd: framework_minus_apex_cmd + " --num-shards 10 --shard-index 1",
+}
+
+java_genrule {
+    name: "framework-minus-apex.ravenwood-base_X2",
+    defaults: ["framework-minus-apex.ravenwood-base_defaults"],
+    cmd: framework_minus_apex_cmd + " --num-shards 10 --shard-index 2",
+}
+
+java_genrule {
+    name: "framework-minus-apex.ravenwood-base_X3",
+    defaults: ["framework-minus-apex.ravenwood-base_defaults"],
+    cmd: framework_minus_apex_cmd + " --num-shards 10 --shard-index 3",
+}
+
+java_genrule {
+    name: "framework-minus-apex.ravenwood-base_X4",
+    defaults: ["framework-minus-apex.ravenwood-base_defaults"],
+    cmd: framework_minus_apex_cmd + " --num-shards 10 --shard-index 4",
+}
+
+java_genrule {
+    name: "framework-minus-apex.ravenwood-base_X5",
+    defaults: ["framework-minus-apex.ravenwood-base_defaults"],
+    cmd: framework_minus_apex_cmd + " --num-shards 10 --shard-index 5",
+}
+
+java_genrule {
+    name: "framework-minus-apex.ravenwood-base_X6",
+    defaults: ["framework-minus-apex.ravenwood-base_defaults"],
+    cmd: framework_minus_apex_cmd + " --num-shards 10 --shard-index 6",
+}
+
+java_genrule {
+    name: "framework-minus-apex.ravenwood-base_X7",
+    defaults: ["framework-minus-apex.ravenwood-base_defaults"],
+    cmd: framework_minus_apex_cmd + " --num-shards 10 --shard-index 7",
+}
+
+java_genrule {
+    name: "framework-minus-apex.ravenwood-base_X8",
+    defaults: ["framework-minus-apex.ravenwood-base_defaults"],
+    cmd: framework_minus_apex_cmd + " --num-shards 10 --shard-index 8",
+}
+
+java_genrule {
+    name: "framework-minus-apex.ravenwood-base_X9",
+    defaults: ["framework-minus-apex.ravenwood-base_defaults"],
+    cmd: framework_minus_apex_cmd + " --num-shards 10 --shard-index 9",
+}
+
+// Build framework-minus-apex.ravenwood-base without sharding.
+// We extract the various dump files from this one, rather than the sharded ones, because
+// some dumps use the output from other classes (e.g. base classes) which may not be in the
+// same shard. Also some of the dump files ("apis") may be slow even when sharded, because
+// the output contains the information from all the input classes, rather than the output classes.
+// Not using sharding is fine for this module because it's only used for collecting the
+// dump / stats files, which don't have to happen regularly.
+java_genrule {
+    name: "framework-minus-apex.ravenwood-base_all",
+    defaults: ["framework-minus-apex.ravenwood-base_defaults"],
+    cmd: framework_minus_apex_cmd +
+        "--stats-file $(location hoststubgen_framework-minus-apex_stats.csv) " +
+        "--supported-api-list-file $(location hoststubgen_framework-minus-apex_apis.csv) " +
+
+        "--gen-keep-all-file $(location hoststubgen_framework-minus-apex_keep_all.txt) " +
+        "--gen-input-dump-file $(location hoststubgen_framework-minus-apex_dump.txt) ",
+
+    out: [
         "hoststubgen_framework-minus-apex_keep_all.txt",
         "hoststubgen_framework-minus-apex_dump.txt",
-
-        "hoststubgen_framework-minus-apex.log",
         "hoststubgen_framework-minus-apex_stats.csv",
         "hoststubgen_framework-minus-apex_apis.csv",
     ],
-    defaults: ["ravenwood-internal-only-visibility-genrule"],
 }
 
-// Extract the impl jar from "framework-minus-apex.ravenwood-base" for subsequent build rules.
-// Note this emits a "device side" output, so that ravenwood tests can (implicitly)
-// depend on it.
+// Marge all the sharded jars
 java_genrule {
     name: "framework-minus-apex.ravenwood",
-    defaults: ["ravenwood-internal-only-visibility-genrule"],
-    cmd: "cp $(in) $(out)",
+    defaults: ["ravenwood-internal-only-visibility-java"],
+    cmd: "$(location merge_zips) $(out) $(in)",
+    tools: ["merge_zips"],
     srcs: [
-        ":framework-minus-apex.ravenwood-base{ravenwood.jar}",
+        ":framework-minus-apex.ravenwood-base_X0{ravenwood.jar}",
+        ":framework-minus-apex.ravenwood-base_X1{ravenwood.jar}",
+        ":framework-minus-apex.ravenwood-base_X2{ravenwood.jar}",
+        ":framework-minus-apex.ravenwood-base_X3{ravenwood.jar}",
+        ":framework-minus-apex.ravenwood-base_X4{ravenwood.jar}",
+        ":framework-minus-apex.ravenwood-base_X5{ravenwood.jar}",
+        ":framework-minus-apex.ravenwood-base_X6{ravenwood.jar}",
+        ":framework-minus-apex.ravenwood-base_X7{ravenwood.jar}",
+        ":framework-minus-apex.ravenwood-base_X8{ravenwood.jar}",
+        ":framework-minus-apex.ravenwood-base_X9{ravenwood.jar}",
     ],
     out: [
         "framework-minus-apex.ravenwood.jar",
@@ -139,6 +223,9 @@
     ],
 }
 
+// TODO(b/313930116) This jarjar is a bit slow. We should use hoststubgen for renaming,
+// but services.core.ravenwood has complex dependencies, so it'll take more than
+// just using hoststubgen "rename"s.
 java_library {
     name: "services.core.ravenwood-jarjar",
     defaults: ["ravenwood-internal-only-visibility-java"],
@@ -151,7 +238,6 @@
 
 // Jars in "ravenwood-runtime" are set to the classpath, sorted alphabetically.
 // Rename some of the dependencies to make sure they're included in the intended order.
-// Also apply jarjar.
 java_library {
     name: "100-framework-minus-apex.ravenwood",
     defaults: ["ravenwood-internal-only-visibility-java"],
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 49384cd..5db0772 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -137,13 +137,14 @@
       "name": "CtsStrictJavaPackagesTestCases"
     }
   ],
-  "postsubmit-ravenwood": [
+  "ravenwood-presubmit": [
     {
       "name": "CtsUtilTestCasesRavenwood",
-      "host": true,
-      "file_patterns": [
-        "[Rr]avenwood"
-      ]
+      "host": true
+    },
+    {
+      "name": "RavenwoodBivalentTest",
+      "host": true
     }
   ],
   "postsubmit-managedprofile-stress": [
diff --git a/ZYGOTE_OWNERS b/ZYGOTE_OWNERS
index f6d15e0..6918c16 100644
--- a/ZYGOTE_OWNERS
+++ b/ZYGOTE_OWNERS
@@ -1,4 +1,4 @@
 chriswailes@google.com
+hboehm@google.com
 maco@google.com
-narayan@google.com
 ngeoffray@google.com
diff --git a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientSocketPerfTest.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientSocketPerfTest.java
index d7b1c9a2..f20b170 100644
--- a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientSocketPerfTest.java
+++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientSocketPerfTest.java
@@ -43,7 +43,6 @@
 import javax.crypto.Cipher;
 import javax.crypto.NoSuchPaddingException;
 
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -143,7 +142,7 @@
 
         // Always use the same server for consistency across the benchmarks.
         server = config.serverFactory().newServer(
-                ChannelType.CHANNEL, config.messageSize(), config.protocol().getProtocols(),
+                config.messageSize(), config.protocol().getProtocols(),
                 ciphers(config));
 
         server.setMessageProcessor(new ServerEndpoint.MessageProcessor() {
@@ -197,7 +196,6 @@
      */
     @Test
     @Parameters(method = "getParams")
-    @Ignore("b/351034205")
     public void time(Config config) throws Exception {
         reset();
         setup(config);
diff --git a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/EndpointFactory.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/EndpointFactory.java
index 0655f45..ba2acb8 100644
--- a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/EndpointFactory.java
+++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/EndpointFactory.java
@@ -43,10 +43,10 @@
         factories.clientFactory, channelType, port, protocols, ciphers);
   }
 
-  public ServerEndpoint newServer(ChannelType channelType, int messageSize,
+  public ServerEndpoint newServer(int messageSize,
       String[] protocols, String[] ciphers) throws IOException {
     return new ServerEndpoint(factories.serverFactory, factories.serverSocketFactory,
-        channelType, messageSize, protocols, ciphers);
+        messageSize, protocols, ciphers);
   }
 
   private static final class Factories {
diff --git a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerEndpoint.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerEndpoint.java
index 3631c3f..1e4f124 100644
--- a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerEndpoint.java
+++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerEndpoint.java
@@ -34,8 +34,6 @@
 import javax.net.ssl.SSLSocket;
 import javax.net.ssl.SSLSocketFactory;
 
-import org.conscrypt.ChannelType;
-
 /**
  * A simple socket-based test server.
  */
@@ -63,7 +61,6 @@
     }
 
     private final ServerSocket serverSocket;
-    private final ChannelType channelType;
     private final SSLSocketFactory socketFactory;
     private final int messageSize;
     private final String[] protocols;
@@ -78,11 +75,10 @@
     private volatile Future<?> processFuture;
 
     ServerEndpoint(SSLSocketFactory socketFactory, SSLServerSocketFactory serverSocketFactory,
-            ChannelType channelType, int messageSize, String[] protocols,
+            int messageSize, String[] protocols,
             String[] cipherSuites) throws IOException {
-        this.serverSocket = channelType.newServerSocket(serverSocketFactory);
+        this.serverSocket = serverSocketFactory.createServerSocket();
         this.socketFactory = socketFactory;
-        this.channelType = channelType;
         this.messageSize = messageSize;
         this.protocols = protocols;
         this.cipherSuites = cipherSuites;
@@ -134,7 +130,7 @@
                 if (stopping) {
                     return;
                 }
-                socket = channelType.accept(serverSocket, socketFactory);
+                socket = (SSLSocket) serverSocket.accept();
                 socket.setEnabledProtocols(protocols);
                 socket.setEnabledCipherSuites(cipherSuites);
 
diff --git a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerSocketPerfTest.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerSocketPerfTest.java
index 8916a3c..af3c405 100644
--- a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerSocketPerfTest.java
+++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerSocketPerfTest.java
@@ -43,7 +43,6 @@
 import junitparams.JUnitParamsRunner;
 import junitparams.Parameters;
 
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -131,8 +130,7 @@
 
         final ChannelType channelType = config.channelType();
 
-        server = config.serverFactory().newServer(
-            channelType, config.messageSize(),
+        server = config.serverFactory().newServer(config.messageSize(),
             new String[] {"TLSv1.3", "TLSv1.2"}, ciphers(config));
         server.setMessageProcessor(new MessageProcessor() {
             @Override
@@ -202,7 +200,6 @@
 
     @Test
     @Parameters(method = "getParams")
-    @Ignore("b/351034205")
     public void throughput(Config config) throws Exception {
         setup(config);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
diff --git a/apct-tests/perftests/multiuser/Android.bp b/apct-tests/perftests/multiuser/Android.bp
index 856dba3..9eea712 100644
--- a/apct-tests/perftests/multiuser/Android.bp
+++ b/apct-tests/perftests/multiuser/Android.bp
@@ -45,3 +45,8 @@
         "trace_configs/trace_config_multi_user.textproto",
     ],
 }
+
+prebuilt_etc {
+    name: "trace_config_multi_user.textproto",
+    src: ":multi_user_trace_config",
+}
diff --git a/apct-tests/perftests/settingsprovider/src/android/provider/SettingsProviderPerfTest.java b/apct-tests/perftests/settingsprovider/src/android/provider/SettingsProviderPerfTest.java
index c00c8d5..06cd942 100644
--- a/apct-tests/perftests/settingsprovider/src/android/provider/SettingsProviderPerfTest.java
+++ b/apct-tests/perftests/settingsprovider/src/android/provider/SettingsProviderPerfTest.java
@@ -36,7 +36,7 @@
 
 @RunWith(AndroidJUnit4.class)
 public final class SettingsProviderPerfTest {
-    private static final String NAMESPACE = "test@namespace";
+    private static final String NAMESPACE = "testing";
     private static final String SETTING_NAME1 = "test:setting1";
     private static final String SETTING_NAME2 = "test-setting2";
     private static final String UNSET_SETTING = "test_unset_setting";
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 18ee6f2..d61439c 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -357,6 +357,7 @@
     }
 
     // TODO(b/172085676): Move inside alarm store.
+    @GuardedBy("mLock")
     private final SparseArray<AlarmManager.AlarmClockInfo> mNextAlarmClockForUser =
             new SparseArray<>();
     private final SparseArray<AlarmManager.AlarmClockInfo> mTmpSparseAlarmClockArray =
@@ -2616,6 +2617,13 @@
                 mInFlightListeners.add(callback);
             }
         }
+
+        /** @see AlarmManagerInternal#getNextAlarmTriggerTimeForUser(int) */
+        @Override
+        public long getNextAlarmTriggerTimeForUser(@UserIdInt int userId) {
+            final AlarmManager.AlarmClockInfo nextAlarm = getNextAlarmClockImpl(userId);
+            return nextAlarm != null ? nextAlarm.getTriggerTime() : 0;
+        }
     }
 
     boolean hasUseExactAlarmInternal(String packageName, int uid) {
@@ -4441,6 +4449,11 @@
         public void run() {
             ArrayList<Alarm> triggerList = new ArrayList<Alarm>();
 
+            synchronized (mLock) {
+                mLastTimeChangeClockTime = mInjector.getCurrentTimeMillis();
+                mLastTimeChangeRealtime = mInjector.getElapsedRealtimeMillis();
+            }
+
             while (true) {
                 int result = mInjector.waitForAlarm();
                 final long nowRTC = mInjector.getCurrentTimeMillis();
@@ -4464,10 +4477,9 @@
                         expectedClockTime = lastTimeChangeClockTime
                                 + (nowELAPSED - mLastTimeChangeRealtime);
                     }
-                    if (lastTimeChangeClockTime == 0 || nowRTC < (expectedClockTime - 1000)
+                    if (nowRTC < (expectedClockTime - 1000)
                             || nowRTC > (expectedClockTime + 1000)) {
-                        // The change is by at least +/- 1000 ms (or this is the first change),
-                        // let's do it!
+                        // The change is by at least +/- 1000 ms, let's do it!
                         if (DEBUG_BATCH) {
                             Slog.v(TAG, "Time changed notification from kernel; rebatching");
                         }
diff --git a/api/Android.bp b/api/Android.bp
index d931df1..341be3d53 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -284,7 +284,7 @@
 // These are libs from framework-internal-utils that are required (i.e. being referenced)
 // from framework-non-updatable-sources. Add more here when there's a need.
 // DO NOT add the entire framework-internal-utils. It might cause unnecessary circular
-// dependencies gets bigger.
+// dependencies when the list gets bigger.
 android_non_updatable_stubs_libs = [
     "android.hardware.cas-V1.2-java",
     "android.hardware.health-V1.0-java-constants",
@@ -384,6 +384,11 @@
     "sdk_system_current_android",
 ]
 
+java_defaults {
+    name: "module-classpath-java-defaults",
+    libs: non_updatable_api_deps_on_modules,
+}
+
 // Defaults with module APIs in the classpath (mostly from prebuilts).
 // Suitable for compiling android-non-updatable.
 stubs_defaults {
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index 8dfddf0..d991da5 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -563,8 +563,12 @@
 
 java_defaults {
     name: "android-non-updatable_from_text_defaults",
+    defaults: ["android-non-updatable-stubs-libs-defaults"],
     static_libs: ["framework-res-package-jar"],
     libs: ["stub-annotations"],
+    sdk_version: "none",
+    system_modules: "none",
+    previous_api: ":android.api.public.latest",
 }
 
 java_defaults {
@@ -582,10 +586,10 @@
         "api-stubs-docs-non-updatable.api.contribution",
     ],
     defaults: ["android-non-updatable_everything_from_text_defaults"],
-    full_api_surface_stub: "android_stubs_current.from-text",
     // Use full Android API not just the non-updatable API as the latter is incomplete
     // and can result in incorrect behavior.
     previous_api: ":android.api.combined.public.latest",
+    libs: ["all-modules-public-stubs"],
 }
 
 java_api_library {
@@ -596,10 +600,10 @@
         "system-api-stubs-docs-non-updatable.api.contribution",
     ],
     defaults: ["android-non-updatable_everything_from_text_defaults"],
-    full_api_surface_stub: "android_system_stubs_current.from-text",
     // Use full Android API not just the non-updatable API as the latter is incomplete
     // and can result in incorrect behavior.
     previous_api: ":android.api.combined.system.latest",
+    libs: ["all-modules-system-stubs"],
 }
 
 java_api_library {
@@ -611,10 +615,10 @@
         "test-api-stubs-docs-non-updatable.api.contribution",
     ],
     defaults: ["android-non-updatable_everything_from_text_defaults"],
-    full_api_surface_stub: "android_test_stubs_current.from-text",
     // Use full Android API not just the non-updatable API as the latter is incomplete
     // and can result in incorrect behavior.
     previous_api: ":android.api.combined.test.latest",
+    libs: ["all-modules-system-stubs"],
 }
 
 java_api_library {
@@ -625,8 +629,10 @@
         "system-api-stubs-docs-non-updatable.api.contribution",
         "module-lib-api-stubs-docs-non-updatable.api.contribution",
     ],
-    defaults: ["android-non-updatable_everything_from_text_defaults"],
-    full_api_surface_stub: "android_module_lib_stubs_current_full.from-text",
+    defaults: [
+        "module-classpath-java-defaults",
+        "android-non-updatable_everything_from_text_defaults",
+    ],
     // Use full Android API not just the non-updatable API as the latter is incomplete
     // and can result in incorrect behavior.
     previous_api: ":android.api.combined.module-lib.latest",
@@ -644,14 +650,16 @@
         "test-api-stubs-docs-non-updatable.api.contribution",
         "module-lib-api-stubs-docs-non-updatable.api.contribution",
     ],
-    defaults: ["android-non-updatable_everything_from_text_defaults"],
-    full_api_surface_stub: "android_test_module_lib_stubs_current.from-text",
+    defaults: [
+        "module-classpath-java-defaults",
+        "android-non-updatable_everything_from_text_defaults",
+    ],
 
     // No need to specify previous_api as this is not used for compiling against.
-
     // This module is only used for hiddenapi, and other modules should not
     // depend on this module.
     visibility: ["//visibility:private"],
+    libs: ["all-modules-system-stubs"],
 }
 
 java_defaults {
@@ -665,7 +673,7 @@
 }
 
 java_library {
-    name: "android_stubs_current.from-source",
+    name: "android_stubs_current",
     static_libs: [
         "all-modules-public-stubs",
         "android-non-updatable.stubs",
@@ -675,7 +683,7 @@
 }
 
 java_library {
-    name: "android_stubs_current_exportable.from-source",
+    name: "android_stubs_current_exportable",
     static_libs: [
         "all-modules-public-stubs-exportable",
         "android-non-updatable.stubs.exportable",
@@ -685,7 +693,7 @@
 }
 
 java_library {
-    name: "android_system_stubs_current.from-source",
+    name: "android_system_stubs_current",
     static_libs: [
         "all-modules-system-stubs",
         "android-non-updatable.stubs.system",
@@ -698,7 +706,7 @@
 }
 
 java_library {
-    name: "android_system_stubs_current_exportable.from-source",
+    name: "android_system_stubs_current_exportable",
     static_libs: [
         "all-modules-system-stubs-exportable",
         "android-non-updatable.stubs.exportable.system",
@@ -722,7 +730,7 @@
 }
 
 java_library {
-    name: "android_test_stubs_current.from-source",
+    name: "android_test_stubs_current",
     static_libs: [
         // Updatable modules do not have test APIs, but we want to include their SystemApis, like we
         // include the SystemApi of framework-non-updatable-sources.
@@ -739,7 +747,7 @@
 }
 
 java_library {
-    name: "android_test_stubs_current_exportable.from-source",
+    name: "android_test_stubs_current_exportable",
     static_libs: [
         // Updatable modules do not have test APIs, but we want to include their SystemApis, like we
         // include the SystemApi of framework-non-updatable-sources.
@@ -760,7 +768,7 @@
 
 // This module does not need to be copied to dist
 java_library {
-    name: "android_test_frameworks_core_stubs_current.from-source",
+    name: "android_test_frameworks_core_stubs_current",
     static_libs: [
         "all-updatable-modules-system-stubs",
         "android-non-updatable.stubs.test",
@@ -772,7 +780,7 @@
 }
 
 java_library {
-    name: "android_module_lib_stubs_current.from-source",
+    name: "android_module_lib_stubs_current",
     defaults: [
         "android.jar_defaults",
     ],
@@ -785,7 +793,7 @@
 }
 
 java_library {
-    name: "android_module_lib_stubs_current_exportable.from-source",
+    name: "android_module_lib_stubs_current_exportable",
     defaults: [
         "android.jar_defaults",
         "android_stubs_dists_default",
@@ -801,20 +809,20 @@
 }
 
 java_library {
-    name: "android_system_server_stubs_current.from-source",
+    name: "android_system_server_stubs_current",
     defaults: [
         "android.jar_defaults",
     ],
     srcs: [":services-non-updatable-stubs"],
     installable: false,
     static_libs: [
-        "android_module_lib_stubs_current.from-source",
+        "android_module_lib_stubs_current",
     ],
     visibility: ["//frameworks/base/services"],
 }
 
 java_library {
-    name: "android_system_server_stubs_current_exportable.from-source",
+    name: "android_system_server_stubs_current_exportable",
     defaults: [
         "android.jar_defaults",
         "android_stubs_dists_default",
@@ -822,7 +830,7 @@
     srcs: [":services-non-updatable-stubs{.exportable}"],
     installable: false,
     static_libs: [
-        "android_module_lib_stubs_current_exportable.from-source",
+        "android_module_lib_stubs_current_exportable",
     ],
     dist: {
         dir: "apistubs/android/system-server",
@@ -897,215 +905,6 @@
     },
 }
 
-//
-// Java API defaults and libraries for single tree build
-//
-
-java_defaults {
-    name: "stub-annotation-defaults",
-    libs: [
-        "stub-annotations",
-    ],
-    static_libs: [
-        // stub annotations do not contribute to the API surfaces but are statically
-        // linked in the stubs for API surfaces (see frameworks/base/StubLibraries.bp).
-        // This is because annotation processors insist on loading the classes for any
-        // annotations found, thus should exist inside android.jar.
-        "private-stub-annotations-jar",
-    ],
-    is_stubs_module: true,
-}
-
-// Listing of API domains contribution and dependencies per API surfaces
-java_defaults {
-    name: "android_test_stubs_current_contributions",
-    api_surface: "test",
-    api_contributions: [
-        "framework-virtualization.stubs.source.test.api.contribution",
-        "framework-location.stubs.source.test.api.contribution",
-    ],
-}
-
-java_defaults {
-    name: "android_test_frameworks_core_stubs_current_contributions",
-    api_surface: "test",
-    api_contributions: [
-        "test-api-stubs-docs-non-updatable.api.contribution",
-    ],
-}
-
-java_defaults {
-    name: "android_module_lib_stubs_current_contributions",
-    api_surface: "module-lib",
-    api_contributions: [
-        "api-stubs-docs-non-updatable.api.contribution",
-        "system-api-stubs-docs-non-updatable.api.contribution",
-        "module-lib-api-stubs-docs-non-updatable.api.contribution",
-        "art.module.public.api.stubs.source.api.contribution",
-        "art.module.public.api.stubs.source.system.api.contribution",
-        "art.module.public.api.stubs.source.module_lib.api.contribution",
-        "i18n.module.public.api.stubs.source.api.contribution",
-        "i18n.module.public.api.stubs.source.system.api.contribution",
-        "i18n.module.public.api.stubs.source.module_lib.api.contribution",
-    ],
-    previous_api: ":android.api.combined.module-lib.latest",
-}
-
-// Java API library definitions per API surface
-java_api_library {
-    name: "android_stubs_current.from-text",
-    api_surface: "public",
-    defaults: [
-        // This module is dynamically created at frameworks/base/api/api.go
-        // instead of being written out, in order to minimize edits in the codebase
-        // when there is a change in the list of modules.
-        // that contributes to an api surface.
-        "android_stubs_current_contributions",
-        "stub-annotation-defaults",
-    ],
-    api_contributions: [
-        "api-stubs-docs-non-updatable.api.contribution",
-    ],
-    visibility: ["//visibility:public"],
-    enable_validation: false,
-    stubs_type: "everything",
-}
-
-java_api_library {
-    name: "android_system_stubs_current.from-text",
-    api_surface: "system",
-    defaults: [
-        "android_stubs_current_contributions",
-        "android_system_stubs_current_contributions",
-        "stub-annotation-defaults",
-    ],
-    api_contributions: [
-        "api-stubs-docs-non-updatable.api.contribution",
-        "system-api-stubs-docs-non-updatable.api.contribution",
-    ],
-    visibility: ["//visibility:public"],
-    enable_validation: false,
-    stubs_type: "everything",
-}
-
-java_api_library {
-    name: "android_test_stubs_current.from-text",
-    api_surface: "test",
-    defaults: [
-        "android_stubs_current_contributions",
-        "android_system_stubs_current_contributions",
-        "android_test_stubs_current_contributions",
-        "stub-annotation-defaults",
-    ],
-    api_contributions: [
-        "api-stubs-docs-non-updatable.api.contribution",
-        "system-api-stubs-docs-non-updatable.api.contribution",
-        "test-api-stubs-docs-non-updatable.api.contribution",
-    ],
-    visibility: ["//visibility:public"],
-    enable_validation: false,
-    stubs_type: "everything",
-}
-
-java_api_library {
-    name: "android_test_frameworks_core_stubs_current.from-text",
-    api_surface: "test",
-    defaults: [
-        "android_stubs_current_contributions",
-        "android_system_stubs_current_contributions",
-        "android_test_frameworks_core_stubs_current_contributions",
-    ],
-    libs: [
-        "stub-annotations",
-    ],
-    api_contributions: [
-        "api-stubs-docs-non-updatable.api.contribution",
-        "system-api-stubs-docs-non-updatable.api.contribution",
-    ],
-    enable_validation: false,
-    stubs_type: "everything",
-}
-
-java_api_library {
-    name: "android_module_lib_stubs_current_full.from-text",
-    api_surface: "module-lib",
-    defaults: [
-        "android_stubs_current_contributions",
-        "android_system_stubs_current_contributions",
-        "android_module_lib_stubs_current_contributions_full",
-    ],
-    libs: [
-        "stub-annotations",
-    ],
-    api_contributions: [
-        "api-stubs-docs-non-updatable.api.contribution",
-        "system-api-stubs-docs-non-updatable.api.contribution",
-        "module-lib-api-stubs-docs-non-updatable.api.contribution",
-    ],
-    visibility: ["//visibility:public"],
-    enable_validation: false,
-    stubs_type: "everything",
-}
-
-java_api_library {
-    name: "android_module_lib_stubs_current.from-text",
-    api_surface: "module-lib",
-    defaults: [
-        "android_module_lib_stubs_current_contributions",
-    ],
-    libs: [
-        "android_module_lib_stubs_current_full.from-text",
-        "stub-annotations",
-    ],
-    visibility: ["//visibility:public"],
-    enable_validation: false,
-    stubs_type: "everything",
-}
-
-java_api_library {
-    name: "android_test_module_lib_stubs_current.from-text",
-    api_surface: "module-lib",
-    defaults: [
-        "android_stubs_current_contributions",
-        "android_system_stubs_current_contributions",
-        "android_test_stubs_current_contributions",
-        "android_module_lib_stubs_current_contributions",
-    ],
-    libs: [
-        "android_module_lib_stubs_current_full.from-text",
-        "stub-annotations",
-    ],
-    api_contributions: [
-        "test-api-stubs-docs-non-updatable.api.contribution",
-    ],
-
-    // This module is only used to build android-non-updatable.stubs.test_module_lib
-    // and other modules should not depend on this module.
-    visibility: [
-        "//visibility:private",
-    ],
-    enable_validation: false,
-    stubs_type: "everything",
-}
-
-java_api_library {
-    name: "android_system_server_stubs_current.from-text",
-    api_surface: "system-server",
-    api_contributions: [
-        "services-non-updatable-stubs.api.contribution",
-    ],
-    libs: [
-        "android_module_lib_stubs_current.from-text",
-        "stub-annotations",
-    ],
-    static_libs: [
-        "android_module_lib_stubs_current.from-text",
-    ],
-    visibility: ["//visibility:public"],
-    enable_validation: false,
-    stubs_type: "everything",
-}
-
 ////////////////////////////////////////////////////////////////////////
 // api-versions.xml generation, for public and system. This API database
 // also contains the android.test.* APIs.
diff --git a/api/api.go b/api/api.go
index b6b1a7e..5b7f534 100644
--- a/api/api.go
+++ b/api/api.go
@@ -15,9 +15,7 @@
 package api
 
 import (
-	"fmt"
 	"sort"
-	"strings"
 
 	"github.com/google/blueprint/proptools"
 
@@ -464,79 +462,6 @@
 	}
 }
 
-func createApiContributionDefaults(ctx android.LoadHookContext, modules []string) {
-	defaultsSdkKinds := []android.SdkKind{
-		android.SdkPublic, android.SdkSystem, android.SdkModule,
-	}
-	for _, sdkKind := range defaultsSdkKinds {
-		props := defaultsProps{}
-		props.Name = proptools.StringPtr(
-			sdkKind.DefaultJavaLibraryName() + "_contributions")
-		if sdkKind == android.SdkModule {
-			props.Name = proptools.StringPtr(
-				sdkKind.DefaultJavaLibraryName() + "_contributions_full")
-		}
-		props.Api_surface = proptools.StringPtr(sdkKind.String())
-		apiSuffix := ""
-		if sdkKind != android.SdkPublic {
-			apiSuffix = "." + strings.ReplaceAll(sdkKind.String(), "-", "_")
-		}
-		props.Api_contributions = transformArray(
-			modules, "", fmt.Sprintf(".stubs.source%s.api.contribution", apiSuffix))
-		props.Defaults_visibility = []string{"//visibility:public"}
-		props.Previous_api = proptools.StringPtr(":android.api.combined." + sdkKind.String() + ".latest")
-		ctx.CreateModule(java.DefaultsFactory, &props)
-	}
-}
-
-func createFullApiLibraries(ctx android.LoadHookContext) {
-	javaLibraryNames := []string{
-		"android_stubs_current",
-		"android_system_stubs_current",
-		"android_test_stubs_current",
-		"android_test_frameworks_core_stubs_current",
-		"android_module_lib_stubs_current",
-		"android_system_server_stubs_current",
-	}
-
-	for _, libraryName := range javaLibraryNames {
-		props := libraryProps{}
-		props.Name = proptools.StringPtr(libraryName)
-		staticLib := libraryName + ".from-source"
-		if ctx.Config().BuildFromTextStub() {
-			staticLib = libraryName + ".from-text"
-		}
-		props.Static_libs = []string{staticLib}
-		props.Defaults = []string{"android.jar_defaults"}
-		props.Visibility = []string{"//visibility:public"}
-		props.Is_stubs_module = proptools.BoolPtr(true)
-
-		ctx.CreateModule(java.LibraryFactory, &props)
-	}
-}
-
-func createFullExportableApiLibraries(ctx android.LoadHookContext) {
-	javaLibraryNames := []string{
-		"android_stubs_current_exportable",
-		"android_system_stubs_current_exportable",
-		"android_test_stubs_current_exportable",
-		"android_module_lib_stubs_current_exportable",
-		"android_system_server_stubs_current_exportable",
-	}
-
-	for _, libraryName := range javaLibraryNames {
-		props := libraryProps{}
-		props.Name = proptools.StringPtr(libraryName)
-		staticLib := libraryName + ".from-source"
-		props.Static_libs = []string{staticLib}
-		props.Defaults = []string{"android.jar_defaults"}
-		props.Visibility = []string{"//visibility:public"}
-		props.Is_stubs_module = proptools.BoolPtr(true)
-
-		ctx.CreateModule(java.LibraryFactory, &props)
-	}
-}
-
 func (a *CombinedApis) createInternalModules(ctx android.LoadHookContext) {
 	bootclasspath := a.bootclasspath(ctx)
 	system_server_classpath := a.systemServerClasspath(ctx)
@@ -562,12 +487,6 @@
 	createMergedAnnotationsFilegroups(ctx, bootclasspath, system_server_classpath)
 
 	createPublicStubsSourceFilegroup(ctx, bootclasspath)
-
-	createApiContributionDefaults(ctx, bootclasspath)
-
-	createFullApiLibraries(ctx)
-
-	createFullExportableApiLibraries(ctx)
 }
 
 func combinedApisModuleFactory() android.Module {
diff --git a/api/api_test.go b/api/api_test.go
index 47d1670..fb26f82 100644
--- a/api/api_test.go
+++ b/api/api_test.go
@@ -52,6 +52,12 @@
 		"core.current.stubs",
 		"ext",
 		"framework",
+		"android_stubs_current",
+		"android_system_stubs_current",
+		"android_test_stubs_current",
+		"android_test_frameworks_core_stubs_current",
+		"android_module_lib_stubs_current",
+		"android_system_server_stubs_current",
 		"android_stubs_current.from-text",
 		"android_system_stubs_current.from-text",
 		"android_test_stubs_current.from-text",
@@ -190,61 +196,60 @@
 			}
 		}),
 	).RunTestWithBp(t, `
-		java_sdk_library {
-			name: "framework-foo",
-			srcs: ["a.java"],
-			public: {
-				enabled: true,
-			},
-			system: {
-				enabled: true,
-			},
-			test: {
-				enabled: true,
-			},
-			module_lib: {
-				enabled: true,
-			},
-			api_packages: [
-				"foo",
-			],
-			sdk_version: "core_current",
-			annotations_enabled: true,
-		}
+	java_sdk_library {
+		name: "framework-foo",
+		srcs: ["a.java"],
+		public: {
+			enabled: true,
+		},
+		system: {
+			enabled: true,
+		},
+		test: {
+			enabled: true,
+		},
+		module_lib: {
+			enabled: true,
+		},
+		api_packages: [
+			"foo",
+		],
+		sdk_version: "core_current",
+		annotations_enabled: true,
+	}
+	java_sdk_library {
+		name: "framework-bar",
+		srcs: ["a.java"],
+		public: {
+			enabled: true,
+		},
+		system: {
+			enabled: true,
+		},
+		test: {
+			enabled: true,
+		},
+		module_lib: {
+			enabled: true,
+		},
+		api_packages: [
+			"foo",
+		],
+		sdk_version: "core_current",
+		annotations_enabled: true,
+	}
 
-		java_sdk_library {
-			name: "framework-bar",
-			srcs: ["a.java"],
-			public: {
-				enabled: true,
-			},
-			system: {
-				enabled: true,
-			},
-			test: {
-				enabled: true,
-			},
-			module_lib: {
-				enabled: true,
-			},
-			api_packages: [
-				"foo",
+	combined_apis {
+		name: "foo",
+		bootclasspath: [
+			"framework-bar",
+		] + select(boolean_var_for_testing(), {
+			true: [
+				"framework-foo",
 			],
-			sdk_version: "core_current",
-			annotations_enabled: true,
-		}
-
-		combined_apis {
-			name: "foo",
-			bootclasspath: [
-				"framework-bar",
-			] + select(boolean_var_for_testing(), {
-				true: [
-					"framework-foo",
-				],
-				default: [],
-			}),
-		}
+			default: [],
+		}),
+	}
 	`)
 
 	subModuleDependsOnSelectAppendedModule := java.CheckModuleHasDependency(t,
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp
index f264125..6902d6d 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.cpp
+++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp
@@ -78,6 +78,11 @@
 
 namespace android::os {
 
+template <typename T>
+const T* Idmap2Service::GetPointer(const OwningPtr<T>& ptr) {
+    return std::visit([](auto&& ptr) { return ptr.get(); }, ptr);
+}
+
 Status Idmap2Service::getIdmapPath(const std::string& overlay_path,
                                    int32_t user_id ATTRIBUTE_UNUSED, std::string* _aidl_return) {
   assert(_aidl_return);
@@ -224,7 +229,7 @@
       if (is_framework ||
         (item.dev == st.st_dev && item.inode == st.st_ino && item.size == st.st_size
           && item.mtime.tv_sec == st.st_mtim.tv_sec && item.mtime.tv_nsec == st.st_mtim.tv_nsec)) {
-        return {item.apk.get()};
+        return {item.apk};
       }
       container_cache_.erase(cache_it);
     }
@@ -238,14 +243,14 @@
     return {std::move(*target)};
   }
 
-  const auto res = target->get();
+  auto res = std::shared_ptr(std::move(*target));
   std::lock_guard lock(container_cache_mutex_);
   container_cache_.emplace(target_path, CachedContainer {
     .dev = dev_t(st.st_dev),
     .inode = ino_t(st.st_ino),
     .size = st.st_size,
     .mtime = st.st_mtim,
-    .apk = std::move(*target)
+    .apk = res
   });
   return {res};
 }
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.h b/cmds/idmap2/idmap2d/Idmap2Service.h
index a69fa61..272ec6b 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.h
+++ b/cmds/idmap2/idmap2d/Idmap2Service.h
@@ -85,7 +85,7 @@
     ino_t inode;
     int64_t size;
     struct timespec mtime;
-    std::unique_ptr<idmap2::TargetResourceContainer> apk;
+    std::shared_ptr<idmap2::TargetResourceContainer> apk;
   };
   std::unordered_map<std::string, CachedContainer> container_cache_;
   std::mutex container_cache_mutex_;
@@ -95,24 +95,15 @@
   std::mutex frro_iter_mutex_;
 
   template <typename T>
-  using MaybeUniquePtr = std::variant<std::unique_ptr<T>, T*>;
+  using OwningPtr = std::variant<std::unique_ptr<T>, std::shared_ptr<T>>;
 
-  using TargetResourceContainerPtr = MaybeUniquePtr<idmap2::TargetResourceContainer>;
+  using TargetResourceContainerPtr = OwningPtr<idmap2::TargetResourceContainer>;
   idmap2::Result<TargetResourceContainerPtr> GetTargetContainer(const std::string& target_path);
 
   template <typename T>
-  WARN_UNUSED static const T* GetPointer(const MaybeUniquePtr<T>& ptr);
+  WARN_UNUSED static const T* GetPointer(const OwningPtr<T>& ptr);
 };
 
-template <typename T>
-const T* Idmap2Service::GetPointer(const MaybeUniquePtr<T>& ptr) {
-  auto u = std::get_if<T*>(&ptr);
-  if (u != nullptr) {
-    return *u;
-  }
-  return std::get<std::unique_ptr<T>>(ptr).get();
-}
-
 }  // namespace android::os
 
 #endif  // IDMAP2_IDMAP2D_IDMAP2SERVICE_H_
diff --git a/config/preloaded-classes-denylist b/config/preloaded-classes-denylist
index dd2569a..a413bbd 100644
--- a/config/preloaded-classes-denylist
+++ b/config/preloaded-classes-denylist
@@ -1,23 +1,13 @@
 android.content.AsyncTaskLoader$LoadTask
-android.media.MediaCodecInfo$CodecCapabilities$FeatureList
-android.media.MediaCodecInfo$LazyHolder
 android.net.ConnectivityThread$Singleton
+android.os.FileObserver
+android.os.NullVibrator
+android.speech.tts.TextToSpeech$Connection$SetupConnectionAsyncTask
+android.widget.Magnifier
+gov.nist.core.net.DefaultNetworkLayer
 android.net.rtp.AudioGroup
 android.net.rtp.AudioStream
 android.net.rtp.RtpStream
-android.os.FileObserver
-android.os.NullVibrator
-android.permission.PermissionManager
-android.provider.MediaStore
-android.speech.tts.TextToSpeech$Connection$SetupConnectionAsyncTask
-android.view.HdrRenderState
-android.text.TextFlags
-android.widget.Magnifier
-com.android.internal.jank.InteractionJankMonitor$InstanceHolder
-com.android.internal.os.BinderCallsStats$SettingsObserver
-com.android.internal.util.LatencyTracker$SLatencyTrackerHolder
-com.android.server.BootReceiver$2
-gov.nist.core.net.DefaultNetworkLayer
-java.util.ImmutableCollections
 java.util.concurrent.ThreadLocalRandom
-sun.nio.fs.UnixChannelFactory
+java.util.ImmutableCollections
+com.android.internal.jank.InteractionJankMonitor$InstanceHolder
diff --git a/core/api/current.txt b/core/api/current.txt
index 354e26b..36c91758 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -10684,6 +10684,7 @@
     field public static final String ACTIVITY_SERVICE = "activity";
     field public static final String ALARM_SERVICE = "alarm";
     field public static final String APPWIDGET_SERVICE = "appwidget";
+    field @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public static final String APP_FUNCTION_SERVICE = "app_function";
     field public static final String APP_OPS_SERVICE = "appops";
     field public static final String APP_SEARCH_SERVICE = "app_search";
     field public static final String AUDIO_SERVICE = "audio";
@@ -59034,7 +59035,7 @@
   }
 
   public static interface CompoundButton.OnCheckedChangeListener {
-    method public void onCheckedChanged(android.widget.CompoundButton, boolean);
+    method public void onCheckedChanged(@NonNull android.widget.CompoundButton, boolean);
   }
 
   public abstract class CursorAdapter extends android.widget.BaseAdapter implements android.widget.Filterable android.widget.ThemedSpinnerAdapter {
@@ -60099,7 +60100,7 @@
   }
 
   public static interface RadioGroup.OnCheckedChangeListener {
-    method public void onCheckedChanged(android.widget.RadioGroup, @IdRes int);
+    method public void onCheckedChanged(@NonNull android.widget.RadioGroup, @IdRes int);
   }
 
   public class RatingBar extends android.widget.AbsSeekBar {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 15e5706..445a572 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -11981,6 +11981,7 @@
     field public static final String ACTION_MANAGE_APP_OVERLAY_PERMISSION = "android.settings.MANAGE_APP_OVERLAY_PERMISSION";
     field public static final String ACTION_MANAGE_DOMAIN_URLS = "android.settings.MANAGE_DOMAIN_URLS";
     field public static final String ACTION_MANAGE_MORE_DEFAULT_APPS_SETTINGS = "android.settings.MANAGE_MORE_DEFAULT_APPS_SETTINGS";
+    field @FlaggedApi("android.nfc.nfc_action_manage_services_settings") public static final String ACTION_MANAGE_OTHER_NFC_SERVICES_SETTINGS = "android.settings.MANAGE_OTHER_NFC_SERVICES_SETTINGS";
     field public static final String ACTION_NOTIFICATION_POLICY_ACCESS_DETAIL_SETTINGS = "android.settings.NOTIFICATION_POLICY_ACCESS_DETAIL_SETTINGS";
     field public static final String ACTION_REQUEST_ENABLE_CONTENT_CAPTURE = "android.settings.REQUEST_ENABLE_CONTENT_CAPTURE";
     field public static final String ACTION_SHOW_ADMIN_SUPPORT_DETAILS = "android.settings.SHOW_ADMIN_SUPPORT_DETAILS";
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 90de7ab..4fb35c3 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -7312,7 +7312,7 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private void finish(int finishTask) {
         if (DEBUG_FINISH_ACTIVITY) {
-            Log.d("Instrumentation", "finishActivity: finishTask=" + finishTask, new Throwable());
+            Log.d(Instrumentation.TAG, "finishActivity: finishTask=" + finishTask, new Throwable());
         }
         if (mParent == null) {
             int resultCode;
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 74e9583..be70de2 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -16,6 +16,7 @@
 
 package android.app;
 
+import static android.app.Instrumentation.DEBUG_FINISH_ACTIVITY;
 import static android.app.WindowConfiguration.activityTypeToString;
 import static android.app.WindowConfiguration.windowingModeToString;
 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
@@ -80,6 +81,7 @@
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.DisplayMetrics;
+import android.util.Log;
 import android.util.Singleton;
 import android.util.Size;
 import android.view.WindowInsetsController.Appearance;
@@ -6011,6 +6013,10 @@
          * Finishes all activities in this task and removes it from the recent tasks list.
          */
         public void finishAndRemoveTask() {
+            if (DEBUG_FINISH_ACTIVITY) {
+                Log.d(Instrumentation.TAG, "AppTask#finishAndRemoveTask: task="
+                        + getTaskInfo(), new Throwable());
+            }
             try {
                 mAppTaskImpl.finishAndRemoveTask();
             } catch (RemoteException e) {
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 89efa9b..d318812 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -961,6 +961,17 @@
             @Nullable VoiceInteractionManagerProvider provider);
 
     /**
+     * Get whether or not the previous user's packages will be killed before the user is
+     * stopped during a user switch.
+     *
+     * <p> The primary use case of this method is for {@link com.android.server.SystemService}
+     * classes to call this API in their
+     * {@link com.android.server.SystemService#onUserSwitching} method implementation to prevent
+     * restarting any of the previous user's processes that will be killed during the user switch.
+     */
+    public abstract boolean isEarlyPackageKillEnabledForUserSwitch(int fromUserId, int toUserId);
+
+    /**
      * Sets whether the current foreground user (and its profiles) should be stopped after switched
      * out.
      */
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 5214d2c..21396a1 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -10768,8 +10768,13 @@
                 final long key = makeKey(uidState, flag);
 
                 NoteOpEvent event = events.get(key);
-                if (lastEvent == null
-                        || event != null && event.getNoteTime() > lastEvent.getNoteTime()) {
+                if (event == null) {
+                    continue;
+                }
+
+                if (lastEvent == null || event.getNoteTime() > lastEvent.getNoteTime()
+                        || (event.getNoteTime() == lastEvent.getNoteTime()
+                                && event.getDuration() > lastEvent.getDuration())) {
                     lastEvent = event;
                 }
             }
@@ -11013,7 +11018,8 @@
 
                 if (access != null) {
                     NoteOpEvent existingAccess = accessEvents.get(key);
-                    if (existingAccess == null || existingAccess.getDuration() == -1) {
+                    if (existingAccess == null || existingAccess.getDuration() == -1
+                            || existingAccess.getDuration() < access.getDuration()) {
                         accessEvents.append(key, access);
                     } else if (existingAccess.mProxy == null && access.mProxy != null ) {
                         existingAccess.mProxy = access.mProxy;
diff --git a/core/java/android/app/ComponentOptions.java b/core/java/android/app/ComponentOptions.java
index b3fc058..0819833 100644
--- a/core/java/android/app/ComponentOptions.java
+++ b/core/java/android/app/ComponentOptions.java
@@ -105,17 +105,10 @@
     public @NonNull ComponentOptions setPendingIntentBackgroundActivityStartMode(
             @BackgroundActivityStartMode int state) {
         switch (state) {
-            case MODE_BACKGROUND_ACTIVITY_START_ALLOWED:
-                if (mPendingIntentBalAllowed != MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS) {
-                    // do not overwrite ALWAYS with ALLOWED for backwards compatibility,
-                    // if setPendingIntentBackgroundActivityLaunchAllowedByPermission is used
-                    // before this method.
-                    mPendingIntentBalAllowed = state;
-                }
-                break;
             case MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED:
             case MODE_BACKGROUND_ACTIVITY_START_DENIED:
             case MODE_BACKGROUND_ACTIVITY_START_COMPAT:
+            case MODE_BACKGROUND_ACTIVITY_START_ALLOWED:
             case MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS:
             case MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE:
                 mPendingIntentBalAllowed = state;
@@ -139,35 +132,6 @@
         return mPendingIntentBalAllowed;
     }
 
-    /**
-     * Get PendingIntent activity is allowed to be started in the background if the caller
-     * has BAL permission.
-     * @hide
-     * @deprecated check for #MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
-     */
-    @Deprecated
-    public boolean isPendingIntentBackgroundActivityLaunchAllowedByPermission() {
-        return mPendingIntentBalAllowed == MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS;
-    }
-
-    /**
-     * Set PendingIntent activity can be launched from background if caller has BAL permission.
-     * @hide
-     * @deprecated use #MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
-     */
-    @Deprecated
-    public void setPendingIntentBackgroundActivityLaunchAllowedByPermission(boolean allowed) {
-        if (allowed) {
-            setPendingIntentBackgroundActivityStartMode(
-                    MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS);
-        } else {
-            if (getPendingIntentBackgroundActivityStartMode()
-                    == MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS) {
-                setPendingIntentBackgroundActivityStartMode(MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
-            }
-        }
-    }
-
     /** @hide */
     public Bundle toBundle() {
         Bundle b = new Bundle();
diff --git a/core/java/android/app/DisabledWallpaperManager.java b/core/java/android/app/DisabledWallpaperManager.java
index 4a5836c..b06fb9e 100644
--- a/core/java/android/app/DisabledWallpaperManager.java
+++ b/core/java/android/app/DisabledWallpaperManager.java
@@ -15,11 +15,16 @@
  */
 package android.app;
 
+import android.annotation.FloatRange;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.graphics.Bitmap;
+import android.graphics.Point;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.Bundle;
@@ -27,9 +32,12 @@
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
 import android.util.Log;
+import android.util.SparseArray;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.List;
+import java.util.Map;
 
 /**
  * A no-op implementation of {@link WallpaperManager}.
@@ -54,29 +62,19 @@
     private DisabledWallpaperManager() {
     }
 
-    @Override
-    public boolean isWallpaperSupported() {
-        return false;
+    @UnsupportedAppUsage
+    public IWallpaperManager getIWallpaperManager() {
+        return unsupported();
     }
 
     @Override
-    public boolean isSetWallpaperAllowed() {
-        return false;
+    public boolean isLockscreenLiveWallpaperEnabled() {
+        return unsupportedBoolean();
     }
 
-    private static <T> T unsupported() {
-        if (DEBUG) Log.w(TAG, "unsupported method called; returning null", new Exception());
-        return null;
-    }
-
-    private static boolean unsupportedBoolean() {
-        if (DEBUG) Log.w(TAG, "unsupported method called; returning false", new Exception());
-        return false;
-    }
-
-    private static int unsupportedInt() {
-        if (DEBUG) Log.w(TAG, "unsupported method called; returning -1", new Exception());
-        return -1;
+    @Override
+    public boolean shouldEnableWideColorGamut() {
+        return unsupportedBoolean();
     }
 
     @Override
@@ -122,6 +120,11 @@
     }
 
     @Override
+    public boolean wallpaperSupportsWcg(int which) {
+        return unsupportedBoolean();
+    }
+
+    @Override
     public Bitmap getBitmap() {
         return unsupported();
     }
@@ -131,12 +134,61 @@
         return unsupported();
     }
 
+    @Nullable
+    public Bitmap getBitmap(boolean hardware, @SetWallpaperFlags int which) {
+        return unsupported();
+    }
+
     @Override
     public Bitmap getBitmapAsUser(int userId, boolean hardware) {
         return unsupported();
     }
 
     @Override
+    public Bitmap getBitmapAsUser(int userId, boolean hardware, @SetWallpaperFlags int which) {
+        return unsupported();
+    }
+
+    @Override
+    public Bitmap getBitmapAsUser(int userId, boolean hardware,
+            @SetWallpaperFlags int which, boolean returnDefault) {
+        return unsupported();
+    }
+
+    @Override
+    public Rect peekBitmapDimensions() {
+        return unsupported();
+    }
+
+    @Override
+    public Rect peekBitmapDimensions(@SetWallpaperFlags int which) {
+        return unsupported();
+    }
+
+    @Nullable
+    public Rect peekBitmapDimensions(@SetWallpaperFlags int which, boolean returnDefault) {
+        return unsupported();
+    }
+
+    @Override
+    public List<Rect> getBitmapCrops(@NonNull List<Point> displaySizes,
+            @SetWallpaperFlags int which, boolean originalBitmap) {
+        return unsupported();
+    }
+
+    @Override
+    public List<Rect> getBitmapCrops(@NonNull Point bitmapSize, @NonNull List<Point> displaySizes,
+            @Nullable Map<Point, Rect> cropHints) {
+        return unsupported();
+    }
+
+    @Override
+    public WallpaperColors getWallpaperColors(@NonNull Bitmap bitmap,
+            @Nullable Map<Point, Rect> cropHints) {
+        return unsupported();
+    }
+
+    @Override
     public ParcelFileDescriptor getWallpaperFile(int which) {
         return unsupported();
     }
@@ -173,6 +225,17 @@
     }
 
     @Override
+    public void addOnColorsChangedListener(@NonNull LocalWallpaperColorConsumer callback,
+            List<RectF> regions, int which) throws IllegalArgumentException {
+        unsupported();
+    }
+
+    @Override
+    public void removeOnColorsChangedListener(@NonNull LocalWallpaperColorConsumer callback) {
+        unsupported();
+    }
+
+    @Override
     public ParcelFileDescriptor getWallpaperFile(int which, int userId) {
         return unsupported();
     }
@@ -192,12 +255,6 @@
         return unsupported();
     }
 
-    @Override
-    public ParcelFileDescriptor getWallpaperInfoFile() {
-        return unsupported();
-    }
-
-    @Override
     public WallpaperInfo getWallpaperInfoForUser(int userId) {
         return unsupported();
     }
@@ -213,6 +270,11 @@
     }
 
     @Override
+    public ParcelFileDescriptor getWallpaperInfoFile() {
+        return unsupported();
+    }
+
+    @Override
     public int getWallpaperId(int which) {
         return unsupportedInt();
     }
@@ -264,6 +326,11 @@
         return 0;
     }
 
+    public int setBitmapWithCrops(@Nullable Bitmap fullImage, @NonNull Map<Point, Rect> cropHints,
+            boolean allowBackup, @SetWallpaperFlags int which) throws IOException {
+        return unsupportedInt();
+    }
+
     @Override
     public void setStream(InputStream bitmapData) throws IOException {
         unsupported();
@@ -284,6 +351,19 @@
     }
 
     @Override
+    public int setStreamWithCrops(InputStream bitmapData, @NonNull Map<Point, Rect> cropHints,
+            boolean allowBackup, @SetWallpaperFlags int which) throws IOException {
+        return unsupportedInt();
+    }
+
+
+    @Override
+    public int setStreamWithCrops(InputStream bitmapData, @NonNull SparseArray<Rect> cropHints,
+            boolean allowBackup, @SetWallpaperFlags int which) throws IOException {
+        return unsupportedInt();
+    }
+
+    @Override
     public boolean hasResourceWallpaper(int resid) {
         return unsupportedBoolean();
     }
@@ -328,12 +408,40 @@
         return unsupportedBoolean();
     }
 
+
+    @Override
+    public void setWallpaperDimAmount(@FloatRange(from = 0f, to = 1f) float dimAmount) {
+        unsupported();
+    }
+
+    @Override
+    public @FloatRange(from = 0f, to = 1f) float getWallpaperDimAmount() {
+        return unsupportedInt();
+    }
+
+    @Override
+    public boolean lockScreenWallpaperExists() {
+        return unsupportedBoolean();
+    }
+
     @Override
     public boolean setWallpaperComponent(ComponentName name, int userId) {
         return unsupportedBoolean();
     }
 
     @Override
+    public boolean setWallpaperComponentWithFlags(@NonNull ComponentName name,
+            @SetWallpaperFlags int which) {
+        return unsupportedBoolean();
+    }
+
+    @Override
+    public boolean setWallpaperComponentWithFlags(@NonNull ComponentName name,
+            @SetWallpaperFlags int which, int userId) {
+        return unsupportedBoolean();
+    }
+
+    @Override
     public void setWallpaperOffsets(IBinder windowToken, float xOffset, float yOffset) {
         unsupported();
     }
@@ -350,6 +458,21 @@
     }
 
     @Override
+    public void setWallpaperZoomOut(@NonNull IBinder windowToken, float zoom) {
+        unsupported();
+    }
+
+    @Override
+    public boolean isWallpaperSupported() {
+        return false;
+    }
+
+    @Override
+    public boolean isSetWallpaperAllowed() {
+        return false;
+    }
+
+    @Override
     public void clearWallpaperOffsets(IBinder windowToken) {
         unsupported();
     }
@@ -369,8 +492,18 @@
         return unsupportedBoolean();
     }
 
-    @Override
-    public boolean wallpaperSupportsWcg(int which) {
-        return unsupportedBoolean();
+    private static <T> T unsupported() {
+        if (DEBUG) Log.w(TAG, "unsupported method called; returning null", new Exception());
+        return null;
+    }
+
+    private static boolean unsupportedBoolean() {
+        if (DEBUG) Log.w(TAG, "unsupported method called; returning false", new Exception());
+        return false;
+    }
+
+    private static int unsupportedInt() {
+        if (DEBUG) Log.w(TAG, "unsupported method called; returning -1", new Exception());
+        return -1;
     }
 }
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index be27046..45852c7 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -98,7 +98,7 @@
      */
     public static final String REPORT_KEY_STREAMRESULT = "stream";
 
-    private static final String TAG = "Instrumentation";
+    static final String TAG = "Instrumentation";
 
     private static final long CONNECT_TIMEOUT_MILLIS = 60_000;
 
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index fd4d8e9..0cc210b 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -1836,9 +1836,10 @@
                     // have shared library asset paths appended if there are any.
                     if (r.getImpl() != null) {
                         final ResourcesImpl oldImpl = r.getImpl();
+                        final AssetManager oldAssets = oldImpl.getAssets();
                         // ResourcesImpl constructor will help to append shared library asset paths.
-                        if (oldImpl.getAssets().isUpToDate()) {
-                            final ResourcesImpl newImpl = new ResourcesImpl(oldImpl.getAssets(),
+                        if (oldAssets != AssetManager.getSystem() && oldAssets.isUpToDate()) {
+                            final ResourcesImpl newImpl = new ResourcesImpl(oldAssets,
                                     oldImpl.getMetrics(), oldImpl.getConfiguration(),
                                     oldImpl.getDisplayAdjustments());
                             r.setImpl(newImpl);
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index e73f471..114a2c4 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -16,6 +16,8 @@
 
 package android.app;
 
+import static android.app.appfunctions.flags.Flags.enableAppFunctionManager;
+
 import android.accounts.AccountManager;
 import android.accounts.IAccountManager;
 import android.adservices.AdServicesFrameworkInitializer;
@@ -28,6 +30,8 @@
 import android.app.admin.IDevicePolicyManager;
 import android.app.ambientcontext.AmbientContextManager;
 import android.app.ambientcontext.IAmbientContextManager;
+import android.app.appfunctions.AppFunctionManager;
+import android.app.appfunctions.IAppFunctionManager;
 import android.app.appsearch.AppSearchManagerFrameworkInitializer;
 import android.app.blob.BlobStoreManagerFrameworkInitializer;
 import android.app.contentsuggestions.ContentSuggestionsManager;
@@ -925,6 +929,21 @@
                 return new CompanionDeviceManager(service, ctx.getOuterContext());
             }});
 
+        if (enableAppFunctionManager()) {
+            registerService(Context.APP_FUNCTION_SERVICE, AppFunctionManager.class,
+                    new CachedServiceFetcher<>() {
+                        @Override
+                        public AppFunctionManager createService(ContextImpl ctx)
+                                throws ServiceNotFoundException {
+                            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());
+                        }
+                    });
+        }
+
         registerService(Context.VIRTUAL_DEVICE_SERVICE, VirtualDeviceManager.class,
                 new CachedServiceFetcher<VirtualDeviceManager>() {
             @Override
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 1a72df1..5903a7f 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -123,6 +123,8 @@
  * <p> An app can check whether wallpapers are supported for the current user, by calling
  * {@link #isWallpaperSupported()}, and whether setting of wallpapers is allowed, by calling
  * {@link #isSetWallpaperAllowed()}.
+ * Any public APIs added to WallpaperManager should have a corresponding stub in
+ * {@link DisabledWallpaperManager}.
  */
 @SystemService(Context.WALLPAPER_SERVICE)
 public class WallpaperManager {
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index c789af3..9148e3c 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -37,7 +37,6 @@
   }
 }
 
-
 flag {
   name: "onboarding_bugreport_v2_enabled"
   is_exported: true
@@ -403,3 +402,13 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "dont_read_policy_definition"
+    namespace: "enterprise"
+    description: "Rely on <policy-key-entry> to determine policy definition and ignore <policy-definition-entry>"
+    bug: "335663055"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/core/java/android/app/appfunctions/AppFunctionManager.java b/core/java/android/app/appfunctions/AppFunctionManager.java
new file mode 100644
index 0000000..a01e373
--- /dev/null
+++ b/core/java/android/app/appfunctions/AppFunctionManager.java
@@ -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 android.app.appfunctions;
+
+import android.annotation.SystemService;
+import android.content.Context;
+
+/**
+ * Provides app functions related functionalities.
+ *
+ * <p>App function is a specific piece of functionality that an app offers to the system. These
+ * functionalities can be integrated into various system features.
+ *
+ * @hide
+ */
+@SystemService(Context.APP_FUNCTION_SERVICE)
+public final class AppFunctionManager {
+    private final IAppFunctionManager mService;
+    private final Context mContext;
+
+    /**
+     * TODO(b/357551503): add comments when implement this class
+     *
+     * @hide
+     */
+    public AppFunctionManager(IAppFunctionManager mService, Context context) {
+        this.mService = mService;
+        this.mContext = context;
+    }
+}
diff --git a/telephony/java/android/telephony/satellite/stub/ProvisionSubscriberId.aidl b/core/java/android/app/appfunctions/IAppFunctionManager.aidl
similarity index 72%
copy from telephony/java/android/telephony/satellite/stub/ProvisionSubscriberId.aidl
copy to core/java/android/app/appfunctions/IAppFunctionManager.aidl
index 460de8c..018bc75 100644
--- a/telephony/java/android/telephony/satellite/stub/ProvisionSubscriberId.aidl
+++ b/core/java/android/app/appfunctions/IAppFunctionManager.aidl
@@ -14,18 +14,11 @@
  * limitations under the License.
  */
 
-package android.telephony.satellite.stub;
+package android.app.appfunctions;
 
 /**
- * {@hide}
- */
-parcelable ProvisionSubscriberId {
-    /** provision subscriberId */
-    String subscriberId;
-
-    /** carrier id */
-    int mCarrierId;
-
-    /** apn */
-    String mNiddApn;
-}
+* Interface between an app and the server implementation service (AppFunctionManagerService).
+* @hide
+*/
+oneway interface IAppFunctionManager {
+}
\ No newline at end of file
diff --git a/core/java/android/app/appfunctions/OWNERS b/core/java/android/app/appfunctions/OWNERS
new file mode 100644
index 0000000..c6827cc
--- /dev/null
+++ b/core/java/android/app/appfunctions/OWNERS
@@ -0,0 +1,6 @@
+avayvod@google.com
+oadesina@google.com
+toki@google.com
+tonymak@google.com
+mingweiliao@google.com
+anothermark@google.com
diff --git a/core/java/android/app/appfunctions/flags/flags.aconfig b/core/java/android/app/appfunctions/flags/flags.aconfig
new file mode 100644
index 0000000..367effc
--- /dev/null
+++ b/core/java/android/app/appfunctions/flags/flags.aconfig
@@ -0,0 +1,11 @@
+package: "android.app.appfunctions.flags"
+container: "system"
+
+flag {
+    name: "enable_app_function_manager"
+    is_exported: true
+    is_fixed_read_only: true
+    namespace: "machine_learning"
+    description: "This flag the new App Function manager system service."
+    bug: "357551503"
+}
\ No newline at end of file
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 8365840..9dccc9a 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -16,6 +16,7 @@
 
 package android.content;
 
+import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER;
 import static android.content.flags.Flags.FLAG_ENABLE_BIND_PACKAGE_ISOLATED_PROCESS;
 
 import android.annotation.AttrRes;
@@ -51,6 +52,7 @@
 import android.app.IServiceConnection;
 import android.app.VrManager;
 import android.app.ambientcontext.AmbientContextManager;
+import android.app.appfunctions.AppFunctionManager;
 import android.app.people.PeopleManager;
 import android.app.time.TimeManager;
 import android.companion.virtual.VirtualDeviceManager;
@@ -6310,6 +6312,16 @@
 
     /**
      * Use with {@link #getSystemService(String)} to retrieve an
+     * {@link AppFunctionManager} for
+     * executing app functions.
+     *
+     * @see #getSystemService(String)
+     */
+    @FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
+    public static final String APP_FUNCTION_SERVICE = "app_function";
+
+    /**
+     * Use with {@link #getSystemService(String)} to retrieve an
      * {@link android.content.integrity.AppIntegrityManager}.
      * @hide
      */
@@ -6676,6 +6688,16 @@
     public static final String BLOCKED_NUMBERS_SERVICE = "blocked_numbers";
 
     /**
+     * Use with {@link #getSystemService(String)} to retrieve the
+     * {@link com.android.internal.protolog.ProtoLogService} for registering ProtoLog clients.
+     *
+     * @see #getSystemService(String)
+     * @see com.android.internal.protolog.ProtoLogService
+     * @hide
+     */
+    public static final String PROTOLOG_SERVICE = "protolog";
+
+    /**
      * Determine whether the given permission is allowed for a particular
      * process and user ID running in the system.
      *
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 111e6a8..cb57c7b 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -7485,7 +7485,7 @@
 
     /**
      * This flag is only used for split-screen multi-window mode. The new activity will be displayed
-     * adjacent to the one launching it. This can only be used in conjunction with
+     * adjacent to the one launching it if possible. This can only be used in conjunction with
      * {@link #FLAG_ACTIVITY_NEW_TASK}. Also, setting {@link #FLAG_ACTIVITY_MULTIPLE_TASK} is
      * required if you want a new instance of an existing activity to be created.
      */
diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING
index b0ab11f..1fab3cf 100644
--- a/core/java/android/content/pm/TEST_MAPPING
+++ b/core/java/android/content/pm/TEST_MAPPING
@@ -171,6 +171,17 @@
                     "include-filter": "android.content.pm.cts.PackageManagerShellCommandMultiUserTest"
                 }
             ]
+        },
+        {
+            "name":"CtsPackageInstallerCUJTestCases",
+            "options":[
+                {
+                    "exclude-annotation":"androidx.test.filters.FlakyTest"
+                },
+                {
+                    "exclude-annotation":"org.junit.Ignore"
+                }
+            ]
         }
     ]
 }
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 835459e..273519b 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -151,6 +151,16 @@
 }
 
 flag {
+    name: "fix_avatar_cross_user_leak"
+    namespace: "multiuser"
+    description: "Fix cross-user picture uri leak for avatar picker apps."
+    bug: "341688848"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
     name: "fix_get_user_property_cache"
     namespace: "multiuser"
     description: "Cache is not optimised for getUserProperty for values below 0, eg. UserHandler.USER_NULL or UserHandle.USER_ALL"
@@ -278,6 +288,13 @@
 }
 
 flag {
+    name: "stop_previous_user_apps"
+    namespace: "multiuser"
+    description: "Stop the previous user apps early in a user switch"
+    bug: "323200731"
+}
+
+flag {
     name: "disable_private_space_items_on_home"
     namespace: "profile_experiences"
     description: "Disables adding items belonging to Private Space on Home Screen manually as well as automatically"
@@ -381,8 +398,12 @@
 }
 
 flag {
-  name: "unicorn_mode_refactoring_for_hsum"
+  name: "unicorn_mode_refactoring_for_hsum_read_only"
   namespace: "multiuser"
-  description: "Refactorings related to unicorn mode to work on HSUM mode"
+  description: "Refactorings related to unicorn mode to work on HSUM mode (Read only flag)"
   bug: "339201286"
-}
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
\ No newline at end of file
diff --git a/core/java/android/database/OWNERS b/core/java/android/database/OWNERS
index 53f5bb0..50b7015 100644
--- a/core/java/android/database/OWNERS
+++ b/core/java/android/database/OWNERS
@@ -1,6 +1,2 @@
 include /SQLITE_OWNERS
 
-omakoto@google.com
-jsharkey@android.com
-yamasani@google.com
-
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index 678bd6b..de1cac4 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -415,7 +415,7 @@
     @RequiresPermission(TEST_BIOMETRIC)
     public BiometricTestSession createTestSession(int sensorId) {
         try {
-            return new BiometricTestSession(mContext, sensorId,
+            return new BiometricTestSession(mContext, getSensorProperties(), sensorId,
                     (context, sensorId1, callback) -> mService
                             .createTestSession(sensorId1, callback, context.getOpPackageName()));
         } catch (RemoteException e) {
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 9007b62..b11961c 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -638,17 +638,17 @@
          * Set caller's component name for getting logo icon/description. This should only be used
          * by ConfirmDeviceCredentialActivity, see b/337082634 for more context.
          *
-         * @param componentNameForConfirmDeviceCredentialActivity set the component name for
-         *                                                        ConfirmDeviceCredentialActivity.
+         * @param realCaller set the component name of real caller for
+         *                   ConfirmDeviceCredentialActivity.
          * @return This builder.
          * @hide
          */
         @NonNull
         @RequiresPermission(anyOf = {TEST_BIOMETRIC, USE_BIOMETRIC_INTERNAL})
-        public Builder setComponentNameForConfirmDeviceCredentialActivity(
-                ComponentName componentNameForConfirmDeviceCredentialActivity) {
-            mPromptInfo.setComponentNameForConfirmDeviceCredentialActivity(
-                    componentNameForConfirmDeviceCredentialActivity);
+        public Builder setRealCallerForConfirmDeviceCredentialActivity(ComponentName realCaller) {
+            mPromptInfo.setRealCallerForConfirmDeviceCredentialActivity(realCaller);
+            mPromptInfo.setClassNameIfItIsConfirmDeviceCredentialActivity(
+                    mContext.getClass().getName());
             return this;
         }
 
diff --git a/core/java/android/hardware/biometrics/BiometricTestSession.java b/core/java/android/hardware/biometrics/BiometricTestSession.java
index 027d101..8bd3528 100644
--- a/core/java/android/hardware/biometrics/BiometricTestSession.java
+++ b/core/java/android/hardware/biometrics/BiometricTestSession.java
@@ -27,12 +27,15 @@
 import android.util.ArraySet;
 import android.util.Log;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
 /**
  * Common set of interfaces to test biometric-related APIs, including {@link BiometricPrompt} and
  * {@link android.hardware.fingerprint.FingerprintManager}.
+ *
  * @hide
  */
 @TestApi
@@ -48,21 +51,29 @@
                 @NonNull ITestSessionCallback callback) throws RemoteException;
     }
 
-    private final Context mContext;
     private final int mSensorId;
-    private final ITestSession mTestSession;
+    private final List<ITestSession> mTestSessionsForAllSensors = new ArrayList<>();
+    private ITestSession mTestSession;
 
     // Keep track of users that were tested, which need to be cleaned up when finishing.
-    @NonNull private final ArraySet<Integer> mTestedUsers;
+    @NonNull
+    private final ArraySet<Integer> mTestedUsers;
 
     // Track the users currently cleaning up, and provide a latch that gets notified when all
     // users have finished cleaning up. This is an imperfect system, as there can technically be
     // multiple cleanups per user. Theoretically we should track the cleanup's BaseClientMonitor's
     // unique ID, but it's complicated to plumb it through. This should be fine for now.
-    @Nullable private CountDownLatch mCloseLatch;
-    @NonNull private final ArraySet<Integer> mUsersCleaningUp;
+    @Nullable
+    private CountDownLatch mCloseLatch;
+    @NonNull
+    private final ArraySet<Integer> mUsersCleaningUp;
 
-    private final ITestSessionCallback mCallback = new ITestSessionCallback.Stub() {
+    private class TestSessionCallbackIml extends ITestSessionCallback.Stub {
+        private final int mSensorId;
+        private TestSessionCallbackIml(int sensorId) {
+            mSensorId = sensorId;
+        }
+
         @Override
         public void onCleanupStarted(int userId) {
             Log.d(getTag(), "onCleanupStarted, sensor: " + mSensorId + ", userId: " + userId);
@@ -76,19 +87,30 @@
             mUsersCleaningUp.remove(userId);
 
             if (mUsersCleaningUp.isEmpty() && mCloseLatch != null) {
+                Log.d(getTag(), "counting down");
                 mCloseLatch.countDown();
             }
         }
-    };
+    }
 
     /**
      * @hide
      */
-    public BiometricTestSession(@NonNull Context context, int sensorId,
-            @NonNull TestSessionProvider testSessionProvider) throws RemoteException {
-        mContext = context;
+    public BiometricTestSession(@NonNull Context context, List<SensorProperties> sensors,
+            int sensorId, @NonNull TestSessionProvider testSessionProvider) throws RemoteException {
         mSensorId = sensorId;
-        mTestSession = testSessionProvider.createTestSession(context, sensorId, mCallback);
+        // When any of the sensors should create the test session, all the other sensors should
+        // set test hal enabled too.
+        for (SensorProperties sensor : sensors) {
+            final int id = sensor.getSensorId();
+            final ITestSession session = testSessionProvider.createTestSession(context, id,
+                    new TestSessionCallbackIml(id));
+            mTestSessionsForAllSensors.add(session);
+            if (id == sensorId) {
+                mTestSession = session;
+            }
+        }
+
         mTestedUsers = new ArraySet<>();
         mUsersCleaningUp = new ArraySet<>();
         setTestHalEnabled(true);
@@ -107,8 +129,11 @@
     @RequiresPermission(TEST_BIOMETRIC)
     private void setTestHalEnabled(boolean enabled) {
         try {
-            Log.w(getTag(), "setTestHalEnabled, sensor: " + mSensorId + " enabled: " + enabled);
-            mTestSession.setTestHalEnabled(enabled);
+            for (ITestSession session : mTestSessionsForAllSensors) {
+                Log.w(getTag(), "setTestHalEnabled, sensor: " + session.getSensorId() + " enabled: "
+                        + enabled);
+                session.setTestHalEnabled(enabled);
+            }
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -175,10 +200,12 @@
     /**
      * Simulates an acquired message from the HAL.
      *
-     * @param userId User that this command applies to.
+     * @param userId      User that this command applies to.
      * @param acquireInfo See
-     * {@link BiometricPrompt.AuthenticationCallback#onAuthenticationAcquired(int)} and
-     * {@link FingerprintManager.AuthenticationCallback#onAuthenticationAcquired(int)}
+     *                    {@link
+     *                    BiometricPrompt.AuthenticationCallback#onAuthenticationAcquired(int)} and
+     *                    {@link
+     *                    FingerprintManager.AuthenticationCallback#onAuthenticationAcquired(int)}
      */
     @RequiresPermission(TEST_BIOMETRIC)
     public void notifyAcquired(int userId, int acquireInfo) {
@@ -192,10 +219,12 @@
     /**
      * Simulates an error message from the HAL.
      *
-     * @param userId User that this command applies to.
+     * @param userId    User that this command applies to.
      * @param errorCode See
-     * {@link BiometricPrompt.AuthenticationCallback#onAuthenticationError(int, CharSequence)} and
-     * {@link FingerprintManager.AuthenticationCallback#onAuthenticationError(int, CharSequence)}
+     *                  {@link BiometricPrompt.AuthenticationCallback#onAuthenticationError(int,
+     *                  CharSequence)} and
+     *                  {@link FingerprintManager.AuthenticationCallback#onAuthenticationError(int,
+     *                  CharSequence)}
      */
     @RequiresPermission(TEST_BIOMETRIC)
     public void notifyError(int userId, int errorCode) {
@@ -220,8 +249,20 @@
                 Log.w(getTag(), "Cleanup already in progress for user: " + userId);
             }
 
-            mUsersCleaningUp.add(userId);
-            mTestSession.cleanupInternalState(userId);
+            for (ITestSession session : mTestSessionsForAllSensors) {
+                mUsersCleaningUp.add(userId);
+                Log.d(getTag(), "cleanupInternalState for sensor: " + session.getSensorId());
+                mCloseLatch = new CountDownLatch(1);
+                session.cleanupInternalState(userId);
+
+                try {
+                    Log.d(getTag(), "Awaiting latch...");
+                    mCloseLatch.await(3, TimeUnit.SECONDS);
+                    Log.d(getTag(), "Finished awaiting");
+                } catch (InterruptedException e) {
+                    Log.e(getTag(), "Latch interrupted", e);
+                }
+            }
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -234,18 +275,9 @@
         // Cleanup can be performed using the test HAL, since it always responds to enumerate with
         // zero enrollments.
         if (!mTestedUsers.isEmpty()) {
-            mCloseLatch = new CountDownLatch(1);
             for (int user : mTestedUsers) {
                 cleanupInternalState(user);
             }
-
-            try {
-                Log.d(getTag(), "Awaiting latch...");
-                mCloseLatch.await(3, TimeUnit.SECONDS);
-                Log.d(getTag(), "Finished awaiting");
-            } catch (InterruptedException e) {
-                Log.e(getTag(), "Latch interrupted", e);
-            }
         }
 
         if (!mUsersCleaningUp.isEmpty()) {
diff --git a/core/java/android/hardware/biometrics/ITestSession.aidl b/core/java/android/hardware/biometrics/ITestSession.aidl
index df9f504..bd99606 100644
--- a/core/java/android/hardware/biometrics/ITestSession.aidl
+++ b/core/java/android/hardware/biometrics/ITestSession.aidl
@@ -59,4 +59,8 @@
     // HAL is disabled (e.g. to clean up after a test).
     @EnforcePermission("TEST_BIOMETRIC")
     void cleanupInternalState(int userId);
+
+    // Get the sensor id of the current test session.
+    @EnforcePermission("TEST_BIOMETRIC")
+    int getSensorId();
 }
diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java
index 901f6b7..df5d864 100644
--- a/core/java/android/hardware/biometrics/PromptInfo.java
+++ b/core/java/android/hardware/biometrics/PromptInfo.java
@@ -57,7 +57,8 @@
     private boolean mIsForLegacyFingerprintManager = false;
     private boolean mShowEmergencyCallButton = false;
     private boolean mUseParentProfileForDeviceCredential = false;
-    private ComponentName mComponentNameForConfirmDeviceCredentialActivity = null;
+    private ComponentName mRealCallerForConfirmDeviceCredentialActivity = null;
+    private String mClassNameIfItIsConfirmDeviceCredentialActivity = null;
 
     public PromptInfo() {
 
@@ -89,8 +90,9 @@
         mIsForLegacyFingerprintManager = in.readBoolean();
         mShowEmergencyCallButton = in.readBoolean();
         mUseParentProfileForDeviceCredential = in.readBoolean();
-        mComponentNameForConfirmDeviceCredentialActivity = in.readParcelable(
+        mRealCallerForConfirmDeviceCredentialActivity = in.readParcelable(
                 ComponentName.class.getClassLoader(), ComponentName.class);
+        mClassNameIfItIsConfirmDeviceCredentialActivity = in.readString();
     }
 
     public static final Creator<PromptInfo> CREATOR = new Creator<PromptInfo>() {
@@ -136,7 +138,8 @@
         dest.writeBoolean(mIsForLegacyFingerprintManager);
         dest.writeBoolean(mShowEmergencyCallButton);
         dest.writeBoolean(mUseParentProfileForDeviceCredential);
-        dest.writeParcelable(mComponentNameForConfirmDeviceCredentialActivity, 0);
+        dest.writeParcelable(mRealCallerForConfirmDeviceCredentialActivity, 0);
+        dest.writeString(mClassNameIfItIsConfirmDeviceCredentialActivity);
     }
 
     // LINT.IfChange
@@ -155,7 +158,7 @@
             return true;
         } else if (mShowEmergencyCallButton) {
             return true;
-        } else if (mComponentNameForConfirmDeviceCredentialActivity != null) {
+        } else if (mRealCallerForConfirmDeviceCredentialActivity != null) {
             return true;
         }
         return false;
@@ -321,10 +324,8 @@
         mShowEmergencyCallButton = showEmergencyCallButton;
     }
 
-    public void setComponentNameForConfirmDeviceCredentialActivity(
-            ComponentName componentNameForConfirmDeviceCredentialActivity) {
-        mComponentNameForConfirmDeviceCredentialActivity =
-                componentNameForConfirmDeviceCredentialActivity;
+    public void setRealCallerForConfirmDeviceCredentialActivity(ComponentName realCaller) {
+        mRealCallerForConfirmDeviceCredentialActivity = realCaller;
     }
 
     public void setUseParentProfileForDeviceCredential(
@@ -332,6 +333,14 @@
         mUseParentProfileForDeviceCredential = useParentProfileForDeviceCredential;
     }
 
+    /**
+     * Set the class name of ConfirmDeviceCredentialActivity.
+     */
+    void setClassNameIfItIsConfirmDeviceCredentialActivity(String className) {
+        mClassNameIfItIsConfirmDeviceCredentialActivity = className;
+    }
+
+
     // Getters
 
     /**
@@ -455,8 +464,15 @@
         return mShowEmergencyCallButton;
     }
 
-    public ComponentName getComponentNameForConfirmDeviceCredentialActivity() {
-        return mComponentNameForConfirmDeviceCredentialActivity;
+    public ComponentName getRealCallerForConfirmDeviceCredentialActivity() {
+        return mRealCallerForConfirmDeviceCredentialActivity;
     }
 
+    /**
+     * Get the class name of ConfirmDeviceCredentialActivity. Returns null if the direct caller is
+     * not ConfirmDeviceCredentialActivity.
+     */
+    public String getClassNameIfItIsConfirmDeviceCredentialActivity() {
+       return mClassNameIfItIsConfirmDeviceCredentialActivity;
+    }
 }
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 7f3c49d..1e7f70b 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -918,7 +918,7 @@
             checkIfCameraClosedOrInError();
 
             for (String physicalId : physicalCameraIdSet) {
-                if (physicalId == getId()) {
+                if (Objects.equals(physicalId, getId())) {
                     throw new IllegalStateException("Physical id matches the logical id!");
                 }
             }
diff --git a/core/java/android/hardware/camera2/params/SessionConfiguration.java b/core/java/android/hardware/camera2/params/SessionConfiguration.java
index 0c55ed5..9bd4860 100644
--- a/core/java/android/hardware/camera2/params/SessionConfiguration.java
+++ b/core/java/android/hardware/camera2/params/SessionConfiguration.java
@@ -17,8 +17,6 @@
 
 package android.hardware.camera2.params;
 
-import static com.android.internal.util.Preconditions.*;
-
 import android.annotation.CallbackExecutor;
 import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
@@ -32,8 +30,6 @@
 import android.hardware.camera2.CameraDevice.CameraDeviceSetup;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.impl.CameraMetadataNative;
-import android.hardware.camera2.params.InputConfiguration;
-import android.hardware.camera2.params.OutputConfiguration;
 import android.hardware.camera2.utils.HashCodeHelpers;
 import android.media.ImageReader;
 import android.os.Parcel;
@@ -46,6 +42,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.Executor;
 
 /**
@@ -95,8 +92,8 @@
     public @interface SessionMode {};
 
     // Camera capture session related parameters.
-    private List<OutputConfiguration> mOutputConfigurations;
-    private CameraCaptureSession.StateCallback mStateCallback;
+    private final @NonNull List<OutputConfiguration> mOutputConfigurations;
+    private CameraCaptureSession.StateCallback mStateCallback = null;
     private int mSessionType;
     private Executor mExecutor = null;
     private InputConfiguration mInputConfig = null;
@@ -268,7 +265,8 @@
      */
     @Override
     public int hashCode() {
-        return HashCodeHelpers.hashCode(mOutputConfigurations.hashCode(), mInputConfig.hashCode(),
+        return HashCodeHelpers.hashCode(mOutputConfigurations.hashCode(),
+                Objects.hashCode(mInputConfig),
                 mSessionType);
     }
 
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 903e916..7f1cac0 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -172,7 +172,7 @@
     @RequiresPermission(TEST_BIOMETRIC)
     public BiometricTestSession createTestSession(int sensorId) {
         try {
-            return new BiometricTestSession(mContext, sensorId,
+            return new BiometricTestSession(mContext, getSensorProperties(), sensorId,
                     (context, sensorId1, callback) -> mService
                             .createTestSession(sensorId1, callback, context.getOpPackageName()));
         } catch (RemoteException e) {
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 1767d64..98e11375 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -25,6 +25,7 @@
 import android.hardware.input.IInputDeviceBatteryState;
 import android.hardware.input.IKeyboardBacklightListener;
 import android.hardware.input.IKeyboardBacklightState;
+import android.hardware.input.IKeyboardSystemShortcutListener;
 import android.hardware.input.IStickyModifierStateListener;
 import android.hardware.input.ITabletModeChangedListener;
 import android.hardware.input.KeyboardLayoutSelectionResult;
@@ -239,4 +240,14 @@
     void unregisterStickyModifierStateListener(IStickyModifierStateListener listener);
 
     KeyGlyphMap getKeyGlyphMap(int deviceId);
+
+    @EnforcePermission("MONITOR_KEYBOARD_SYSTEM_SHORTCUTS")
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+            + "android.Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS)")
+    void registerKeyboardSystemShortcutListener(IKeyboardSystemShortcutListener listener);
+
+    @EnforcePermission("MONITOR_KEYBOARD_SYSTEM_SHORTCUTS")
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+            + "android.Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS)")
+    void unregisterKeyboardSystemShortcutListener(IKeyboardSystemShortcutListener listener);
 }
diff --git a/core/java/android/hardware/input/IKeyboardSystemShortcutListener.aidl b/core/java/android/hardware/input/IKeyboardSystemShortcutListener.aidl
new file mode 100644
index 0000000..8d44917
--- /dev/null
+++ b/core/java/android/hardware/input/IKeyboardSystemShortcutListener.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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 android.hardware.input;
+
+/** @hide */
+oneway interface IKeyboardSystemShortcutListener {
+
+    /**
+     * Called when the keyboard system shortcut is triggered.
+     */
+    void onKeyboardSystemShortcutTriggered(int deviceId, in int[] keycodes, int modifierState,
+                                           int shortcut);
+}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index d7952eb..6bc522b 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -1378,6 +1378,36 @@
     }
 
     /**
+     * Registers a keyboard system shortcut listener for {@link KeyboardSystemShortcut} being
+     * triggered.
+     *
+     * @param executor an executor on which the callback will be called
+     * @param listener the {@link KeyboardSystemShortcutListener}
+     * @throws IllegalArgumentException if {@code listener} has already been registered previously.
+     * @throws NullPointerException     if {@code listener} or {@code executor} is null.
+     * @hide
+     * @see #unregisterKeyboardSystemShortcutListener(KeyboardSystemShortcutListener)
+     */
+    @RequiresPermission(Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS)
+    public void registerKeyboardSystemShortcutListener(@NonNull Executor executor,
+            @NonNull KeyboardSystemShortcutListener listener) throws IllegalArgumentException {
+        mGlobal.registerKeyboardSystemShortcutListener(executor, listener);
+    }
+
+    /**
+     * Unregisters a previously added keyboard system shortcut listener.
+     *
+     * @param listener the {@link KeyboardSystemShortcutListener}
+     * @hide
+     * @see #registerKeyboardSystemShortcutListener(Executor, KeyboardSystemShortcutListener)
+     */
+    @RequiresPermission(Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS)
+    public void unregisterKeyboardSystemShortcutListener(
+            @NonNull KeyboardSystemShortcutListener listener) {
+        mGlobal.unregisterKeyboardSystemShortcutListener(listener);
+    }
+
+    /**
      * A callback used to be notified about battery state changes for an input device. The
      * {@link #onBatteryStateChanged(int, long, BatteryState)} method will be called once after the
      * listener is successfully registered to provide the initial battery state of the device.
@@ -1478,4 +1508,21 @@
          */
         void onStickyModifierStateChanged(@NonNull StickyModifierState state);
     }
+
+    /**
+     * A callback used to be notified about keyboard system shortcuts being triggered.
+     *
+     * @see #registerKeyboardSystemShortcutListener(Executor, KeyboardSystemShortcutListener)
+     * @see #unregisterKeyboardSystemShortcutListener(KeyboardSystemShortcutListener)
+     * @hide
+     */
+    public interface KeyboardSystemShortcutListener {
+        /**
+         * Called when a keyboard system shortcut is triggered.
+         *
+         * @param systemShortcut the shortcut info about the shortcut that was triggered.
+         */
+        void onKeyboardSystemShortcutTriggered(int deviceId,
+                @NonNull KeyboardSystemShortcut systemShortcut);
+    }
 }
diff --git a/core/java/android/hardware/input/InputManagerGlobal.java b/core/java/android/hardware/input/InputManagerGlobal.java
index 7b47180..f7fa557 100644
--- a/core/java/android/hardware/input/InputManagerGlobal.java
+++ b/core/java/android/hardware/input/InputManagerGlobal.java
@@ -26,6 +26,7 @@
 import android.hardware.input.InputManager.InputDeviceBatteryListener;
 import android.hardware.input.InputManager.InputDeviceListener;
 import android.hardware.input.InputManager.KeyboardBacklightListener;
+import android.hardware.input.InputManager.KeyboardSystemShortcutListener;
 import android.hardware.input.InputManager.OnTabletModeChangedListener;
 import android.hardware.input.InputManager.StickyModifierStateListener;
 import android.hardware.lights.Light;
@@ -110,6 +111,14 @@
     @Nullable
     private IStickyModifierStateListener mStickyModifierStateListener;
 
+    private final Object mKeyboardSystemShortcutListenerLock = new Object();
+    @GuardedBy("mKeyboardSystemShortcutListenerLock")
+    @Nullable
+    private ArrayList<KeyboardSystemShortcutListenerDelegate> mKeyboardSystemShortcutListeners;
+    @GuardedBy("mKeyboardSystemShortcutListenerLock")
+    @Nullable
+    private IKeyboardSystemShortcutListener mKeyboardSystemShortcutListener;
+
     // InputDeviceSensorManager gets notified synchronously from the binder thread when input
     // devices change, so it must be synchronized with the input device listeners.
     @GuardedBy("mInputDeviceListeners")
@@ -1055,6 +1064,98 @@
         }
     }
 
+    private static final class KeyboardSystemShortcutListenerDelegate {
+        final KeyboardSystemShortcutListener mListener;
+        final Executor mExecutor;
+
+        KeyboardSystemShortcutListenerDelegate(KeyboardSystemShortcutListener listener,
+                Executor executor) {
+            mListener = listener;
+            mExecutor = executor;
+        }
+
+        void onKeyboardSystemShortcutTriggered(int deviceId,
+                KeyboardSystemShortcut systemShortcut) {
+            mExecutor.execute(() ->
+                    mListener.onKeyboardSystemShortcutTriggered(deviceId, systemShortcut));
+        }
+    }
+
+    private class LocalKeyboardSystemShortcutListener extends IKeyboardSystemShortcutListener.Stub {
+
+        @Override
+        public void onKeyboardSystemShortcutTriggered(int deviceId, int[] keycodes,
+                int modifierState, int shortcut) {
+            synchronized (mKeyboardSystemShortcutListenerLock) {
+                if (mKeyboardSystemShortcutListeners == null) return;
+                final int numListeners = mKeyboardSystemShortcutListeners.size();
+                for (int i = 0; i < numListeners; i++) {
+                    mKeyboardSystemShortcutListeners.get(i)
+                            .onKeyboardSystemShortcutTriggered(deviceId,
+                                    new KeyboardSystemShortcut(keycodes, modifierState, shortcut));
+                }
+            }
+        }
+    }
+
+    /**
+     * @see InputManager#registerKeyboardSystemShortcutListener(Executor,
+     * KeyboardSystemShortcutListener)
+     */
+    @RequiresPermission(Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS)
+    void registerKeyboardSystemShortcutListener(@NonNull Executor executor,
+            @NonNull KeyboardSystemShortcutListener listener) throws IllegalArgumentException {
+        Objects.requireNonNull(executor, "executor should not be null");
+        Objects.requireNonNull(listener, "listener should not be null");
+
+        synchronized (mKeyboardSystemShortcutListenerLock) {
+            if (mKeyboardSystemShortcutListener == null) {
+                mKeyboardSystemShortcutListeners = new ArrayList<>();
+                mKeyboardSystemShortcutListener = new LocalKeyboardSystemShortcutListener();
+
+                try {
+                    mIm.registerKeyboardSystemShortcutListener(mKeyboardSystemShortcutListener);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+            final int numListeners = mKeyboardSystemShortcutListeners.size();
+            for (int i = 0; i < numListeners; i++) {
+                if (mKeyboardSystemShortcutListeners.get(i).mListener == listener) {
+                    throw new IllegalArgumentException("Listener has already been registered!");
+                }
+            }
+            KeyboardSystemShortcutListenerDelegate delegate =
+                    new KeyboardSystemShortcutListenerDelegate(listener, executor);
+            mKeyboardSystemShortcutListeners.add(delegate);
+        }
+    }
+
+    /**
+     * @see InputManager#unregisterKeyboardSystemShortcutListener(KeyboardSystemShortcutListener)
+     */
+    @RequiresPermission(Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS)
+    void unregisterKeyboardSystemShortcutListener(
+            @NonNull KeyboardSystemShortcutListener listener) {
+        Objects.requireNonNull(listener, "listener should not be null");
+
+        synchronized (mKeyboardSystemShortcutListenerLock) {
+            if (mKeyboardSystemShortcutListeners == null) {
+                return;
+            }
+            mKeyboardSystemShortcutListeners.removeIf((delegate) -> delegate.mListener == listener);
+            if (mKeyboardSystemShortcutListeners.isEmpty()) {
+                try {
+                    mIm.unregisterKeyboardSystemShortcutListener(mKeyboardSystemShortcutListener);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+                mKeyboardSystemShortcutListeners = null;
+                mKeyboardSystemShortcutListener = null;
+            }
+        }
+    }
+
     /**
      * TODO(b/330517633): Cleanup the unsupported API
      */
diff --git a/core/java/android/hardware/input/KeyboardSystemShortcut.java b/core/java/android/hardware/input/KeyboardSystemShortcut.java
new file mode 100644
index 0000000..89cf877
--- /dev/null
+++ b/core/java/android/hardware/input/KeyboardSystemShortcut.java
@@ -0,0 +1,522 @@
+/*
+ * 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 android.hardware.input;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.util.DataClass;
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Provides information about the keyboard shortcut being triggered by an external keyboard.
+ *
+ * @hide
+ */
+@DataClass(genToString = true, genEqualsHashCode = true)
+public class KeyboardSystemShortcut {
+
+    private static final String TAG = "KeyboardSystemShortcut";
+
+    @NonNull
+    private final int[] mKeycodes;
+    private final int mModifierState;
+    @SystemShortcut
+    private final int mSystemShortcut;
+
+
+    public static final int SYSTEM_SHORTCUT_UNSPECIFIED =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__UNSPECIFIED;
+    public static final int SYSTEM_SHORTCUT_HOME =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__HOME;
+    public static final int SYSTEM_SHORTCUT_RECENT_APPS =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__RECENT_APPS;
+    public static final int SYSTEM_SHORTCUT_BACK =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BACK;
+    public static final int SYSTEM_SHORTCUT_APP_SWITCH =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__APP_SWITCH;
+    public static final int SYSTEM_SHORTCUT_LAUNCH_ASSISTANT =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_ASSISTANT;
+    public static final int SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_VOICE_ASSISTANT;
+    public static final int SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_SYSTEM_SETTINGS;
+    public static final int SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_NOTIFICATION_PANEL;
+    public static final int SYSTEM_SHORTCUT_TOGGLE_TASKBAR =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_TASKBAR;
+    public static final int SYSTEM_SHORTCUT_TAKE_SCREENSHOT =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TAKE_SCREENSHOT;
+    public static final int SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__OPEN_SHORTCUT_HELPER;
+    public static final int SYSTEM_SHORTCUT_BRIGHTNESS_UP =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BRIGHTNESS_UP;
+    public static final int SYSTEM_SHORTCUT_BRIGHTNESS_DOWN =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BRIGHTNESS_DOWN;
+    public static final int SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__KEYBOARD_BACKLIGHT_UP;
+    public static final int SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__KEYBOARD_BACKLIGHT_DOWN;
+    public static final int SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__KEYBOARD_BACKLIGHT_TOGGLE;
+    public static final int SYSTEM_SHORTCUT_VOLUME_UP =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__VOLUME_UP;
+    public static final int SYSTEM_SHORTCUT_VOLUME_DOWN =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__VOLUME_DOWN;
+    public static final int SYSTEM_SHORTCUT_VOLUME_MUTE =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__VOLUME_MUTE;
+    public static final int SYSTEM_SHORTCUT_ALL_APPS =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__ALL_APPS;
+    public static final int SYSTEM_SHORTCUT_LAUNCH_SEARCH =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_SEARCH;
+    public static final int SYSTEM_SHORTCUT_LANGUAGE_SWITCH =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LANGUAGE_SWITCH;
+    public static final int SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__ACCESSIBILITY_ALL_APPS;
+    public static final int SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_CAPS_LOCK;
+    public static final int SYSTEM_SHORTCUT_SYSTEM_MUTE =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SYSTEM_MUTE;
+    public static final int SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SPLIT_SCREEN_NAVIGATION;
+    public static final int SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__CHANGE_SPLITSCREEN_FOCUS;
+    public static final int SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TRIGGER_BUG_REPORT;
+    public static final int SYSTEM_SHORTCUT_LOCK_SCREEN =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LOCK_SCREEN;
+    public static final int SYSTEM_SHORTCUT_OPEN_NOTES =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__OPEN_NOTES;
+    public static final int SYSTEM_SHORTCUT_TOGGLE_POWER =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_POWER;
+    public static final int SYSTEM_SHORTCUT_SYSTEM_NAVIGATION =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SYSTEM_NAVIGATION;
+    public static final int SYSTEM_SHORTCUT_SLEEP =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SLEEP;
+    public static final int SYSTEM_SHORTCUT_WAKEUP =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__WAKEUP;
+    public static final int SYSTEM_SHORTCUT_MEDIA_KEY =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__MEDIA_KEY;
+    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_BROWSER;
+    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_EMAIL;
+    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CONTACTS;
+    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CALENDAR;
+    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CALCULATOR;
+    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MUSIC;
+    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MAPS;
+    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MESSAGING;
+    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_GALLERY;
+    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_FILES;
+    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_WEATHER;
+    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_FITNESS;
+    public static final int SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_APPLICATION_BY_PACKAGE_NAME;
+    public static final int SYSTEM_SHORTCUT_DESKTOP_MODE =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__DESKTOP_MODE;
+    public static final int SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__MULTI_WINDOW_NAVIGATION;
+
+
+
+    // Code below generated by codegen v1.0.23.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/hardware/input/KeyboardSystemShortcut.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    @IntDef(prefix = "SYSTEM_SHORTCUT_", value = {
+        SYSTEM_SHORTCUT_UNSPECIFIED,
+        SYSTEM_SHORTCUT_HOME,
+        SYSTEM_SHORTCUT_RECENT_APPS,
+        SYSTEM_SHORTCUT_BACK,
+        SYSTEM_SHORTCUT_APP_SWITCH,
+        SYSTEM_SHORTCUT_LAUNCH_ASSISTANT,
+        SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT,
+        SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS,
+        SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL,
+        SYSTEM_SHORTCUT_TOGGLE_TASKBAR,
+        SYSTEM_SHORTCUT_TAKE_SCREENSHOT,
+        SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER,
+        SYSTEM_SHORTCUT_BRIGHTNESS_UP,
+        SYSTEM_SHORTCUT_BRIGHTNESS_DOWN,
+        SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP,
+        SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN,
+        SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE,
+        SYSTEM_SHORTCUT_VOLUME_UP,
+        SYSTEM_SHORTCUT_VOLUME_DOWN,
+        SYSTEM_SHORTCUT_VOLUME_MUTE,
+        SYSTEM_SHORTCUT_ALL_APPS,
+        SYSTEM_SHORTCUT_LAUNCH_SEARCH,
+        SYSTEM_SHORTCUT_LANGUAGE_SWITCH,
+        SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS,
+        SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK,
+        SYSTEM_SHORTCUT_SYSTEM_MUTE,
+        SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION,
+        SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS,
+        SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT,
+        SYSTEM_SHORTCUT_LOCK_SCREEN,
+        SYSTEM_SHORTCUT_OPEN_NOTES,
+        SYSTEM_SHORTCUT_TOGGLE_POWER,
+        SYSTEM_SHORTCUT_SYSTEM_NAVIGATION,
+        SYSTEM_SHORTCUT_SLEEP,
+        SYSTEM_SHORTCUT_WAKEUP,
+        SYSTEM_SHORTCUT_MEDIA_KEY,
+        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER,
+        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL,
+        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS,
+        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR,
+        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR,
+        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC,
+        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS,
+        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING,
+        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY,
+        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES,
+        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER,
+        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS,
+        SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME,
+        SYSTEM_SHORTCUT_DESKTOP_MODE,
+        SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @DataClass.Generated.Member
+    public @interface SystemShortcut {}
+
+    @DataClass.Generated.Member
+    public static String systemShortcutToString(@SystemShortcut int value) {
+        switch (value) {
+            case SYSTEM_SHORTCUT_UNSPECIFIED:
+                    return "SYSTEM_SHORTCUT_UNSPECIFIED";
+            case SYSTEM_SHORTCUT_HOME:
+                    return "SYSTEM_SHORTCUT_HOME";
+            case SYSTEM_SHORTCUT_RECENT_APPS:
+                    return "SYSTEM_SHORTCUT_RECENT_APPS";
+            case SYSTEM_SHORTCUT_BACK:
+                    return "SYSTEM_SHORTCUT_BACK";
+            case SYSTEM_SHORTCUT_APP_SWITCH:
+                    return "SYSTEM_SHORTCUT_APP_SWITCH";
+            case SYSTEM_SHORTCUT_LAUNCH_ASSISTANT:
+                    return "SYSTEM_SHORTCUT_LAUNCH_ASSISTANT";
+            case SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT:
+                    return "SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT";
+            case SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS:
+                    return "SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS";
+            case SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL:
+                    return "SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL";
+            case SYSTEM_SHORTCUT_TOGGLE_TASKBAR:
+                    return "SYSTEM_SHORTCUT_TOGGLE_TASKBAR";
+            case SYSTEM_SHORTCUT_TAKE_SCREENSHOT:
+                    return "SYSTEM_SHORTCUT_TAKE_SCREENSHOT";
+            case SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER:
+                    return "SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER";
+            case SYSTEM_SHORTCUT_BRIGHTNESS_UP:
+                    return "SYSTEM_SHORTCUT_BRIGHTNESS_UP";
+            case SYSTEM_SHORTCUT_BRIGHTNESS_DOWN:
+                    return "SYSTEM_SHORTCUT_BRIGHTNESS_DOWN";
+            case SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP:
+                    return "SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP";
+            case SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN:
+                    return "SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN";
+            case SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE:
+                    return "SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE";
+            case SYSTEM_SHORTCUT_VOLUME_UP:
+                    return "SYSTEM_SHORTCUT_VOLUME_UP";
+            case SYSTEM_SHORTCUT_VOLUME_DOWN:
+                    return "SYSTEM_SHORTCUT_VOLUME_DOWN";
+            case SYSTEM_SHORTCUT_VOLUME_MUTE:
+                    return "SYSTEM_SHORTCUT_VOLUME_MUTE";
+            case SYSTEM_SHORTCUT_ALL_APPS:
+                    return "SYSTEM_SHORTCUT_ALL_APPS";
+            case SYSTEM_SHORTCUT_LAUNCH_SEARCH:
+                    return "SYSTEM_SHORTCUT_LAUNCH_SEARCH";
+            case SYSTEM_SHORTCUT_LANGUAGE_SWITCH:
+                    return "SYSTEM_SHORTCUT_LANGUAGE_SWITCH";
+            case SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS:
+                    return "SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS";
+            case SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK:
+                    return "SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK";
+            case SYSTEM_SHORTCUT_SYSTEM_MUTE:
+                    return "SYSTEM_SHORTCUT_SYSTEM_MUTE";
+            case SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION:
+                    return "SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION";
+            case SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS:
+                    return "SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS";
+            case SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT:
+                    return "SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT";
+            case SYSTEM_SHORTCUT_LOCK_SCREEN:
+                    return "SYSTEM_SHORTCUT_LOCK_SCREEN";
+            case SYSTEM_SHORTCUT_OPEN_NOTES:
+                    return "SYSTEM_SHORTCUT_OPEN_NOTES";
+            case SYSTEM_SHORTCUT_TOGGLE_POWER:
+                    return "SYSTEM_SHORTCUT_TOGGLE_POWER";
+            case SYSTEM_SHORTCUT_SYSTEM_NAVIGATION:
+                    return "SYSTEM_SHORTCUT_SYSTEM_NAVIGATION";
+            case SYSTEM_SHORTCUT_SLEEP:
+                    return "SYSTEM_SHORTCUT_SLEEP";
+            case SYSTEM_SHORTCUT_WAKEUP:
+                    return "SYSTEM_SHORTCUT_WAKEUP";
+            case SYSTEM_SHORTCUT_MEDIA_KEY:
+                    return "SYSTEM_SHORTCUT_MEDIA_KEY";
+            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER:
+                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER";
+            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL:
+                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL";
+            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS:
+                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS";
+            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR:
+                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR";
+            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR:
+                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR";
+            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC:
+                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC";
+            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS:
+                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS";
+            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING:
+                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING";
+            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY:
+                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY";
+            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES:
+                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES";
+            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER:
+                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER";
+            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS:
+                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS";
+            case SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME:
+                    return "SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME";
+            case SYSTEM_SHORTCUT_DESKTOP_MODE:
+                    return "SYSTEM_SHORTCUT_DESKTOP_MODE";
+            case SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION:
+                    return "SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION";
+            default: return Integer.toHexString(value);
+        }
+    }
+
+    @DataClass.Generated.Member
+    public KeyboardSystemShortcut(
+            @NonNull int[] keycodes,
+            int modifierState,
+            @SystemShortcut int systemShortcut) {
+        this.mKeycodes = keycodes;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mKeycodes);
+        this.mModifierState = modifierState;
+        this.mSystemShortcut = systemShortcut;
+
+        if (!(mSystemShortcut == SYSTEM_SHORTCUT_UNSPECIFIED)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_HOME)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_RECENT_APPS)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_BACK)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_APP_SWITCH)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_ASSISTANT)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_TOGGLE_TASKBAR)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_TAKE_SCREENSHOT)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_BRIGHTNESS_UP)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_BRIGHTNESS_DOWN)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_VOLUME_UP)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_VOLUME_DOWN)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_VOLUME_MUTE)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_ALL_APPS)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_SEARCH)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_LANGUAGE_SWITCH)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_SYSTEM_MUTE)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_LOCK_SCREEN)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_OPEN_NOTES)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_TOGGLE_POWER)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_SYSTEM_NAVIGATION)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_SLEEP)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_WAKEUP)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_MEDIA_KEY)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_DESKTOP_MODE)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION)) {
+            throw new java.lang.IllegalArgumentException(
+                    "systemShortcut was " + mSystemShortcut + " but must be one of: "
+                            + "SYSTEM_SHORTCUT_UNSPECIFIED(" + SYSTEM_SHORTCUT_UNSPECIFIED + "), "
+                            + "SYSTEM_SHORTCUT_HOME(" + SYSTEM_SHORTCUT_HOME + "), "
+                            + "SYSTEM_SHORTCUT_RECENT_APPS(" + SYSTEM_SHORTCUT_RECENT_APPS + "), "
+                            + "SYSTEM_SHORTCUT_BACK(" + SYSTEM_SHORTCUT_BACK + "), "
+                            + "SYSTEM_SHORTCUT_APP_SWITCH(" + SYSTEM_SHORTCUT_APP_SWITCH + "), "
+                            + "SYSTEM_SHORTCUT_LAUNCH_ASSISTANT(" + SYSTEM_SHORTCUT_LAUNCH_ASSISTANT + "), "
+                            + "SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT(" + SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT + "), "
+                            + "SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS(" + SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS + "), "
+                            + "SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL(" + SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL + "), "
+                            + "SYSTEM_SHORTCUT_TOGGLE_TASKBAR(" + SYSTEM_SHORTCUT_TOGGLE_TASKBAR + "), "
+                            + "SYSTEM_SHORTCUT_TAKE_SCREENSHOT(" + SYSTEM_SHORTCUT_TAKE_SCREENSHOT + "), "
+                            + "SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER(" + SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER + "), "
+                            + "SYSTEM_SHORTCUT_BRIGHTNESS_UP(" + SYSTEM_SHORTCUT_BRIGHTNESS_UP + "), "
+                            + "SYSTEM_SHORTCUT_BRIGHTNESS_DOWN(" + SYSTEM_SHORTCUT_BRIGHTNESS_DOWN + "), "
+                            + "SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP(" + SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP + "), "
+                            + "SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN(" + SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN + "), "
+                            + "SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE(" + SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE + "), "
+                            + "SYSTEM_SHORTCUT_VOLUME_UP(" + SYSTEM_SHORTCUT_VOLUME_UP + "), "
+                            + "SYSTEM_SHORTCUT_VOLUME_DOWN(" + SYSTEM_SHORTCUT_VOLUME_DOWN + "), "
+                            + "SYSTEM_SHORTCUT_VOLUME_MUTE(" + SYSTEM_SHORTCUT_VOLUME_MUTE + "), "
+                            + "SYSTEM_SHORTCUT_ALL_APPS(" + SYSTEM_SHORTCUT_ALL_APPS + "), "
+                            + "SYSTEM_SHORTCUT_LAUNCH_SEARCH(" + SYSTEM_SHORTCUT_LAUNCH_SEARCH + "), "
+                            + "SYSTEM_SHORTCUT_LANGUAGE_SWITCH(" + SYSTEM_SHORTCUT_LANGUAGE_SWITCH + "), "
+                            + "SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS(" + SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS + "), "
+                            + "SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK(" + SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK + "), "
+                            + "SYSTEM_SHORTCUT_SYSTEM_MUTE(" + SYSTEM_SHORTCUT_SYSTEM_MUTE + "), "
+                            + "SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION(" + SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION + "), "
+                            + "SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS(" + SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS + "), "
+                            + "SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT(" + SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT + "), "
+                            + "SYSTEM_SHORTCUT_LOCK_SCREEN(" + SYSTEM_SHORTCUT_LOCK_SCREEN + "), "
+                            + "SYSTEM_SHORTCUT_OPEN_NOTES(" + SYSTEM_SHORTCUT_OPEN_NOTES + "), "
+                            + "SYSTEM_SHORTCUT_TOGGLE_POWER(" + SYSTEM_SHORTCUT_TOGGLE_POWER + "), "
+                            + "SYSTEM_SHORTCUT_SYSTEM_NAVIGATION(" + SYSTEM_SHORTCUT_SYSTEM_NAVIGATION + "), "
+                            + "SYSTEM_SHORTCUT_SLEEP(" + SYSTEM_SHORTCUT_SLEEP + "), "
+                            + "SYSTEM_SHORTCUT_WAKEUP(" + SYSTEM_SHORTCUT_WAKEUP + "), "
+                            + "SYSTEM_SHORTCUT_MEDIA_KEY(" + SYSTEM_SHORTCUT_MEDIA_KEY + "), "
+                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER + "), "
+                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL + "), "
+                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS + "), "
+                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR + "), "
+                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR + "), "
+                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC + "), "
+                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS + "), "
+                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING + "), "
+                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY + "), "
+                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES + "), "
+                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER + "), "
+                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS + "), "
+                            + "SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME(" + SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME + "), "
+                            + "SYSTEM_SHORTCUT_DESKTOP_MODE(" + SYSTEM_SHORTCUT_DESKTOP_MODE + "), "
+                            + "SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION(" + SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION + ")");
+        }
+
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public @NonNull int[] getKeycodes() {
+        return mKeycodes;
+    }
+
+    @DataClass.Generated.Member
+    public int getModifierState() {
+        return mModifierState;
+    }
+
+    @DataClass.Generated.Member
+    public @SystemShortcut int getSystemShortcut() {
+        return mSystemShortcut;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "KeyboardSystemShortcut { " +
+                "keycodes = " + java.util.Arrays.toString(mKeycodes) + ", " +
+                "modifierState = " + mModifierState + ", " +
+                "systemShortcut = " + systemShortcutToString(mSystemShortcut) +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(@Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(KeyboardSystemShortcut other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        KeyboardSystemShortcut that = (KeyboardSystemShortcut) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && java.util.Arrays.equals(mKeycodes, that.mKeycodes)
+                && mModifierState == that.mModifierState
+                && mSystemShortcut == that.mSystemShortcut;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + java.util.Arrays.hashCode(mKeycodes);
+        _hash = 31 * _hash + mModifierState;
+        _hash = 31 * _hash + mSystemShortcut;
+        return _hash;
+    }
+
+    @DataClass.Generated(
+            time = 1722890917041L,
+            codegenVersion = "1.0.23",
+            sourceFile = "frameworks/base/core/java/android/hardware/input/KeyboardSystemShortcut.java",
+            inputSignatures = "private static final  java.lang.String TAG\nprivate final @android.annotation.NonNull int[] mKeycodes\nprivate final  int mModifierState\nprivate final @android.hardware.input.KeyboardSystemShortcut.SystemShortcut int mSystemShortcut\npublic static final  int SYSTEM_SHORTCUT_UNSPECIFIED\npublic static final  int SYSTEM_SHORTCUT_HOME\npublic static final  int SYSTEM_SHORTCUT_RECENT_APPS\npublic static final  int SYSTEM_SHORTCUT_BACK\npublic static final  int SYSTEM_SHORTCUT_APP_SWITCH\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_ASSISTANT\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS\npublic static final  int SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL\npublic static final  int SYSTEM_SHORTCUT_TOGGLE_TASKBAR\npublic static final  int SYSTEM_SHORTCUT_TAKE_SCREENSHOT\npublic static final  int SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER\npublic static final  int SYSTEM_SHORTCUT_BRIGHTNESS_UP\npublic static final  int SYSTEM_SHORTCUT_BRIGHTNESS_DOWN\npublic static final  int SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP\npublic static final  int SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN\npublic static final  int SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE\npublic static final  int SYSTEM_SHORTCUT_VOLUME_UP\npublic static final  int SYSTEM_SHORTCUT_VOLUME_DOWN\npublic static final  int SYSTEM_SHORTCUT_VOLUME_MUTE\npublic static final  int SYSTEM_SHORTCUT_ALL_APPS\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_SEARCH\npublic static final  int SYSTEM_SHORTCUT_LANGUAGE_SWITCH\npublic static final  int SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS\npublic static final  int SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK\npublic static final  int SYSTEM_SHORTCUT_SYSTEM_MUTE\npublic static final  int SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION\npublic static final  int SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS\npublic static final  int SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT\npublic static final  int SYSTEM_SHORTCUT_LOCK_SCREEN\npublic static final  int SYSTEM_SHORTCUT_OPEN_NOTES\npublic static final  int SYSTEM_SHORTCUT_TOGGLE_POWER\npublic static final  int SYSTEM_SHORTCUT_SYSTEM_NAVIGATION\npublic static final  int SYSTEM_SHORTCUT_SLEEP\npublic static final  int SYSTEM_SHORTCUT_WAKEUP\npublic static final  int SYSTEM_SHORTCUT_MEDIA_KEY\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME\npublic static final  int SYSTEM_SHORTCUT_DESKTOP_MODE\npublic static final  int SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION\nclass KeyboardSystemShortcut extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 00ce949..ff737a4 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -58,8 +58,6 @@
 import static android.view.inputmethod.Flags.ctrlShiftShortcut;
 import static android.view.inputmethod.Flags.predictiveBackIme;
 
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
 import android.annotation.AnyThread;
 import android.annotation.CallSuper;
 import android.annotation.DrawableRes;
@@ -500,43 +498,53 @@
     public static final int BACK_DISPOSITION_ADJUST_NOTHING = 3;
 
     /**
-     * Enum flag to be used for {@link #setBackDisposition(int)}.
+     * The disposition mode that indicates the expected affordance for the back button.
      *
      * @hide
      */
-    @Retention(SOURCE)
-    @IntDef(value = {BACK_DISPOSITION_DEFAULT, BACK_DISPOSITION_WILL_NOT_DISMISS,
-            BACK_DISPOSITION_WILL_DISMISS, BACK_DISPOSITION_ADJUST_NOTHING},
-            prefix = "BACK_DISPOSITION_")
+    @IntDef(prefix = { "BACK_DISPOSITION_" }, value = {
+            BACK_DISPOSITION_DEFAULT,
+            BACK_DISPOSITION_WILL_NOT_DISMISS,
+            BACK_DISPOSITION_WILL_DISMISS,
+            BACK_DISPOSITION_ADJUST_NOTHING,
+    })
+    @Retention(RetentionPolicy.SOURCE)
     public @interface BackDispositionMode {}
 
     /**
+     * The IME is active, and ready to accept touch/key events. It may or may not be visible.
+     *
      * @hide
-     * The IME is active.  It may or may not be visible.
      */
-    public static final int IME_ACTIVE = 0x1;
+    public static final int IME_ACTIVE = 1 << 0;
 
     /**
-     * @hide
      * The IME is perceptibly visible to the user.
+     *
+     * @hide
      */
-    public static final int IME_VISIBLE = 0x2;
+    public static final int IME_VISIBLE = 1 << 1;
 
     /**
-     * @hide
-     * The IME is active and ready with views but set invisible.
-     * This flag cannot be combined with {@link #IME_VISIBLE}.
-     */
-    public static final int IME_INVISIBLE = 0x4;
-
-    /**
-     * @hide
      * The IME is visible, but not yet perceptible to the user (e.g. fading in)
      * by {@link android.view.WindowInsetsController}.
      *
      * @see InputMethodManager#reportPerceptible
+     * @hide
      */
-    public static final int IME_VISIBLE_IMPERCEPTIBLE = 0x8;
+    public static final int IME_VISIBLE_IMPERCEPTIBLE = 1 << 2;
+
+    /**
+     * The IME window visibility state.
+     *
+     * @hide
+     */
+    @IntDef(flag = true, prefix = { "IME_" }, value = {
+            IME_ACTIVE,
+            IME_VISIBLE,
+            IME_VISIBLE_IMPERCEPTIBLE,
+    })
+    public @interface ImeWindowVisibility {}
 
     // Min and max values for back disposition.
     private static final int BACK_DISPOSITION_MIN = BACK_DISPOSITION_DEFAULT;
@@ -1349,7 +1357,8 @@
         mImeSurfaceRemoverRunnable = null;
     }
 
-    private void setImeWindowStatus(int visibilityFlags, int backDisposition) {
+    private void setImeWindowStatus(@ImeWindowVisibility int visibilityFlags,
+            @BackDispositionMode int backDisposition) {
         mPrivOps.setImeWindowStatusAsync(visibilityFlags, backDisposition);
     }
 
@@ -2796,6 +2805,9 @@
      * <p>This dismisses the {@link #getStylusHandwritingWindow ink window} and stops intercepting
      * stylus {@code MotionEvent}s.
      *
+     * <p>Connectionless handwriting sessions should be finished using {@link
+     * #finishConnectionlessStylusHandwriting(CharSequence)}.
+     *
      * <p>Note for IME developers: Call this method at any time to finish the current handwriting
      * session. Generally, this should be invoked after a short timeout, giving the user enough time
      * to start the next stylus stroke, if any. By default, system will time-out after few seconds.
@@ -2803,9 +2815,6 @@
      *
      * <p>Handwriting session will be finished by framework on next {@link #onFinishInput()}.
      */
-    // TODO(b/300979854): Once connectionless APIs are finalised, update documentation to add:
-    // <p>Connectionless handwriting sessions should be finished using {@link
-    // #finishConnectionlessStylusHandwriting(CharSequence)}.
     public final void finishStylusHandwriting() {
         if (DEBUG) Log.v(TAG, "finishStylusHandwriting()");
         if (mInkWindow == null) {
@@ -3125,7 +3134,7 @@
         mInShowWindow = true;
         final int previousImeWindowStatus =
                 (mDecorViewVisible ? IME_ACTIVE : 0) | (isInputViewShown()
-                        ? (!mWindowVisible ? IME_INVISIBLE : IME_VISIBLE) : 0);
+                        ? (!mWindowVisible ? -1 : IME_VISIBLE) : 0);
         startViews(prepareWindow(showInput));
         final int nextImeWindowStatus = mapToImeWindowStatus();
         if (previousImeWindowStatus != nextImeWindowStatus) {
@@ -3308,7 +3317,7 @@
         ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_IME_HIDE_WINDOW);
         ImeTracing.getInstance().triggerServiceDump("InputMethodService#hideWindow", mDumper,
                 null /* icProto */);
-        setImeWindowStatus(0, mBackDisposition);
+        setImeWindowStatus(0 /* visibilityFlags */, mBackDisposition);
         if (android.view.inputmethod.Flags.refactorInsetsController()) {
             // The ImeInsetsSourceProvider need the statsToken when dispatching the control. We
             // send the token here, so that another request in the provider can be cancelled.
@@ -4483,6 +4492,7 @@
         };
     }
 
+    @ImeWindowVisibility
     private int mapToImeWindowStatus() {
         return IME_ACTIVE
                 | (isInputViewShown() ? IME_VISIBLE : 0);
diff --git a/core/java/android/inputmethodservice/navigationbar/KeyButtonView.java b/core/java/android/inputmethodservice/navigationbar/KeyButtonView.java
index 540243c..bfa95f7 100644
--- a/core/java/android/inputmethodservice/navigationbar/KeyButtonView.java
+++ b/core/java/android/inputmethodservice/navigationbar/KeyButtonView.java
@@ -22,6 +22,7 @@
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK;
 
+import android.annotation.Nullable;
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Paint;
@@ -126,15 +127,29 @@
     @Override
     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
         super.onInitializeAccessibilityNodeInfo(info);
-        if (mCode != KEYCODE_UNKNOWN) {
+        if (isClickable()) {
             info.addAction(new AccessibilityNodeInfo.AccessibilityAction(ACTION_CLICK, null));
             if (isLongClickable()) {
                 info.addAction(
-                        new AccessibilityNodeInfo.AccessibilityAction(ACTION_LONG_CLICK, null));
+                        new AccessibilityNodeInfo.AccessibilityAction(ACTION_LONG_CLICK,
+                                getAccessibilityLongClickActionLabel()));
             }
         }
     }
 
+    /**
+     * Gets the accessibility long click action label for the button, or {@code null} for no label.
+     */
+    @Nullable
+    private CharSequence getAccessibilityLongClickActionLabel() {
+        if (Flags.imeSwitcherRevamp()
+                && getId() == com.android.internal.R.id.input_method_nav_ime_switcher) {
+            return getContext().getText(
+                    com.android.internal.R.string.input_method_ime_switch_long_click_action_desc);
+        }
+        return null;
+    }
+
     @Override
     protected void onWindowVisibilityChanged(int visibility) {
         super.onWindowVisibilityChanged(visibility);
diff --git a/core/java/android/os/ExternalVibrationScale.aidl b/core/java/android/os/ExternalVibrationScale.aidl
index cf6f8ed..644bece 100644
--- a/core/java/android/os/ExternalVibrationScale.aidl
+++ b/core/java/android/os/ExternalVibrationScale.aidl
@@ -33,12 +33,24 @@
         SCALE_VERY_HIGH = 2
     }
 
+    // TODO(b/345186129): remove this once we finish migrating to scale factor.
     /**
      * The scale level that will be applied to external vibrations.
      */
     ScaleLevel scaleLevel = ScaleLevel.SCALE_NONE;
 
     /**
+     * The scale factor that will be applied to external vibrations.
+     *
+     * Values in (0,1) will scale down the vibrations, values > 1 will scale up vibrations within
+     * hardware limits. A zero scale factor indicates the external vibration should be muted.
+     *
+     * TODO(b/345186129): update this once we finish migrating, negative should not be expected.
+     * Negative values should be ignored in favour of the legacy ScaleLevel.
+     */
+    float scaleFactor = -1f; // undefined
+
+    /**
      * The adaptive haptics scale that will be applied to external vibrations.
      */
     float adaptiveHapticsScale = 1f;
diff --git a/core/java/android/os/IVibratorManagerService.aidl b/core/java/android/os/IVibratorManagerService.aidl
index 97993b6..6aa9852 100644
--- a/core/java/android/os/IVibratorManagerService.aidl
+++ b/core/java/android/os/IVibratorManagerService.aidl
@@ -42,4 +42,12 @@
     // vibrate/isVibrating/cancel.
     oneway void performHapticFeedback(int uid, int deviceId, String opPkg, int constant,
             String reason, int flags, int privFlags);
+
+    // Similar to performHapticFeedback but the effect is customized to the input device. The
+    // customization for each constant is defined on a device basis, and the behavior will be the
+    // same as performHapticFeedback when no customization is provided for a given constant and
+    // device.
+    oneway void performHapticFeedbackForInputDevice(int uid, int deviceId, String opPkg,
+            int constant, int inputDeviceId, int inputSource, String reason, int flags,
+            int privFlags);
 }
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index 5339d73..011a3ee 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -215,6 +215,17 @@
     }
 
     @Override
+    public void performHapticFeedbackForInputDevice(int constant, int inputDeviceId,
+            int inputSource, String reason, int flags, int privFlags) {
+        if (mVibratorManager == null) {
+            Log.w(TAG, "Failed to perform haptic feedback for input device; no vibrator manager.");
+            return;
+        }
+        mVibratorManager.performHapticFeedbackForInputDevice(constant, inputDeviceId, inputSource,
+                reason, flags, privFlags);
+    }
+
+    @Override
     public void cancel() {
         if (mVibratorManager == null) {
             Log.w(TAG, "Failed to cancel vibrate; no vibrator manager.");
diff --git a/core/java/android/os/SystemVibratorManager.java b/core/java/android/os/SystemVibratorManager.java
index a9846ba..58ab5b6 100644
--- a/core/java/android/os/SystemVibratorManager.java
+++ b/core/java/android/os/SystemVibratorManager.java
@@ -161,6 +161,22 @@
     }
 
     @Override
+    public void performHapticFeedbackForInputDevice(int constant, int inputDeviceId,
+            int inputSource, String reason, int flags, int privFlags) {
+        if (mService == null) {
+            Log.w(TAG, "Failed to perform haptic feedback for input device;"
+                            + " no vibrator manager service.");
+            return;
+        }
+        try {
+            mService.performHapticFeedbackForInputDevice(mUid, mContext.getDeviceId(), mPackageName,
+                    constant, inputDeviceId, inputSource, reason, flags, privFlags);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to perform haptic feedback for input device.", e);
+        }
+    }
+
+    @Override
     public void cancel() {
         cancelVibration(VibrationAttributes.USAGE_FILTER_MATCH_ALL);
     }
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index edb3a64..4a37e0a 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -520,8 +520,20 @@
      * @param counterValue The counter value.
      */
     public static void setCounter(@NonNull String counterName, long counterValue) {
-        if (isTagEnabled(TRACE_TAG_APP)) {
-            nativeTraceCounter(TRACE_TAG_APP, counterName, counterValue);
+        setCounter(TRACE_TAG_APP, counterName, counterValue);
+    }
+
+    /**
+     * Writes trace message to indicate the value of a given counter under a given trace tag.
+     *
+     * @param traceTag The trace tag.
+     * @param counterName The counter name to appear in the trace.
+     * @param counterValue The counter value.
+     * @hide
+     */
+    public static void setCounter(long traceTag, @NonNull String counterName, long counterValue) {
+        if (isTagEnabled(traceTag)) {
+            nativeTraceCounter(traceTag, counterName, counterValue);
         }
     }
 
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 06c516a..28f2c25 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -4824,6 +4824,7 @@
      * <p>Note that this does not alter the user's pre-existing user restrictions.
      *
      * @param userId the id of the user to become admin
+     * @throws SecurityException if changing ADMIN status of the user is not allowed
      * @hide
      */
     @RequiresPermission(allOf = {
@@ -4844,6 +4845,7 @@
      * <p>Note that this does not alter the user's pre-existing user restrictions.
      *
      * @param userId the id of the user to revoke admin rights from
+     * @throws SecurityException if changing ADMIN status of the user is not allowed
      * @hide
      */
     @RequiresPermission(allOf = {
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index f3ef9e1..e68b746 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -663,6 +663,15 @@
      * @hide
      */
     public static float scale(float intensity, float scaleFactor) {
+        if (Flags.hapticsScaleV2Enabled()) {
+            if (Float.compare(scaleFactor, 1) <= 0 || Float.compare(intensity, 0) == 0) {
+                // Scaling down or scaling zero intensity is straightforward.
+                return scaleFactor * intensity;
+            }
+            // Using S * x / (1 + (S - 1) * x^2) as the scale up function to converge to 1.0.
+            return (scaleFactor * intensity) / (1 + (scaleFactor - 1) * intensity * intensity);
+        }
+
         // Applying gamma correction to the scale factor, which is the same as encoding the input
         // value, scaling it, then decoding the scaled value.
         float scale = MathUtils.pow(scaleFactor, 1f / SCALE_GAMMA);
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index 161cce0..36233b7 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -553,6 +553,31 @@
     }
 
     /**
+     * Performs a haptic feedback. Similar to {@link #performHapticFeedback} but also take into the
+     * consideration the {@link InputDevice} that triggered the haptic
+     *
+     * <p>A haptic feedback is a short vibration feedback. The type of feedback is identified via
+     * the {@code constant}, which should be one of the effect constants provided in
+     * {@link HapticFeedbackConstants}. The haptic feedback provided for a given effect ID is
+     * consistent across all usages on the same device.
+     *
+     * @param constant      the ID for the haptic feedback. This should be one of the constants
+     *                      defined in {@link HapticFeedbackConstants}.
+     * @param inputDeviceId the integer id of the input device that triggered the haptic feedback.
+     * @param inputSource   the {@link InputDevice.Source} that triggered the haptic feedback.
+     * @param reason        the reason for this haptic feedback.
+     * @param flags         Additional flags as per {@link HapticFeedbackConstants}.
+     * @param privFlags     Additional private flags as per {@link HapticFeedbackConstants}.
+     * @hide
+     */
+    public void performHapticFeedbackForInputDevice(
+            int constant, int inputDeviceId, int inputSource, String reason,
+            @HapticFeedbackConstants.Flags int flags,
+            @HapticFeedbackConstants.PrivateFlags int privFlags) {
+        Log.w(TAG, "performHapticFeedbackForInputDevice is not supported");
+    }
+
+    /**
      * Query whether the vibrator natively supports the given effects.
      *
      * <p>If an effect is not supported, the system may still automatically fall back to playing
diff --git a/core/java/android/os/VibratorManager.java b/core/java/android/os/VibratorManager.java
index 2c7a852..0428876 100644
--- a/core/java/android/os/VibratorManager.java
+++ b/core/java/android/os/VibratorManager.java
@@ -155,6 +155,27 @@
     }
 
     /**
+     * Performs a haptic feedback. Similar to {@link #performHapticFeedback} but also take input
+     * into consideration.
+     *
+     * @param constant      the ID of the requested haptic feedback. Should be one of the constants
+     *                      defined in {@link HapticFeedbackConstants}.
+     * @param inputDeviceId the integer id of the input device that customizes the haptic feedback
+     *                      corresponding to the {@code constant}.
+     * @param inputSource   the {@link InputDevice.Source} that customizes the haptic feedback
+     *                      corresponding to the {@code constant}.
+     * @param reason        the reason for this haptic feedback.
+     * @param flags         Additional flags as per {@link HapticFeedbackConstants}.
+     * @param privFlags     Additional private flags as per {@link HapticFeedbackConstants}.
+     * @hide
+     */
+    public void performHapticFeedbackForInputDevice(int constant, int inputDeviceId,
+            int inputSource, String reason, @HapticFeedbackConstants.Flags int flags,
+            @HapticFeedbackConstants.PrivateFlags int privFlags) {
+        Log.w(TAG, "performHapticFeedbackForInputDevice is not supported");
+    }
+
+    /**
      * Turn all the vibrators off.
      */
     @RequiresPermission(android.Manifest.permission.VIBRATE)
diff --git a/core/java/android/os/vibrator/VibrationConfig.java b/core/java/android/os/vibrator/VibrationConfig.java
index a4164e9..e6e5a27 100644
--- a/core/java/android/os/vibrator/VibrationConfig.java
+++ b/core/java/android/os/vibrator/VibrationConfig.java
@@ -49,8 +49,22 @@
  */
 public class VibrationConfig {
 
+    /**
+     * Hardcoded default scale level gain to be applied between each scale level to define their
+     * scale factor value.
+     *
+     * <p>Default gain defined as 3 dBs.
+     */
+    private static final float DEFAULT_SCALE_LEVEL_GAIN = 1.4f;
+
+    /**
+     * Hardcoded default amplitude to be used when device config is invalid, i.e. not in [1,255].
+     */
+    private static final int DEFAULT_AMPLITUDE = 255;
+
     // TODO(b/191150049): move these to vibrator static config file
     private final float mHapticChannelMaxVibrationAmplitude;
+    private final int mDefaultVibrationAmplitude;
     private final int mRampStepDurationMs;
     private final int mRampDownDurationMs;
     private final int mRequestVibrationParamsTimeoutMs;
@@ -75,8 +89,10 @@
 
     /** @hide */
     public VibrationConfig(@Nullable Resources resources) {
+        mDefaultVibrationAmplitude = resources.getInteger(
+                com.android.internal.R.integer.config_defaultVibrationAmplitude);
         mHapticChannelMaxVibrationAmplitude = loadFloat(resources,
-                com.android.internal.R.dimen.config_hapticChannelMaxVibrationAmplitude, 0);
+                com.android.internal.R.dimen.config_hapticChannelMaxVibrationAmplitude);
         mRampDownDurationMs = loadInteger(resources,
                 com.android.internal.R.integer.config_vibrationWaveformRampDownDuration, 0);
         mRampStepDurationMs = loadInteger(resources,
@@ -87,9 +103,9 @@
                 com.android.internal.R.array.config_requestVibrationParamsForUsages);
 
         mIgnoreVibrationsOnWirelessCharger = loadBoolean(resources,
-                com.android.internal.R.bool.config_ignoreVibrationsOnWirelessCharger, false);
+                com.android.internal.R.bool.config_ignoreVibrationsOnWirelessCharger);
         mKeyboardVibrationSettingsSupported = loadBoolean(resources,
-                com.android.internal.R.bool.config_keyboardVibrationSettingsSupported, false);
+                com.android.internal.R.bool.config_keyboardVibrationSettingsSupported);
 
         mDefaultAlarmVibrationIntensity = loadDefaultIntensity(resources,
                 com.android.internal.R.integer.config_defaultAlarmVibrationIntensity);
@@ -115,16 +131,16 @@
         return value;
     }
 
-    private static float loadFloat(@Nullable Resources res, int resId, float defaultValue) {
-        return res != null ? res.getFloat(resId) : defaultValue;
+    private static float loadFloat(@Nullable Resources res, int resId) {
+        return res != null ? res.getFloat(resId) : 0f;
     }
 
     private static int loadInteger(@Nullable Resources res, int resId, int defaultValue) {
         return res != null ? res.getInteger(resId) : defaultValue;
     }
 
-    private static boolean loadBoolean(@Nullable Resources res, int resId, boolean defaultValue) {
-        return res != null ? res.getBoolean(resId) : defaultValue;
+    private static boolean loadBoolean(@Nullable Resources res, int resId) {
+        return res != null && res.getBoolean(resId);
     }
 
     private static int[] loadIntArray(@Nullable Resources res, int resId) {
@@ -145,6 +161,26 @@
     }
 
     /**
+     * Return the device default vibration amplitude value to replace the
+     * {@link android.os.VibrationEffect#DEFAULT_AMPLITUDE} constant.
+     */
+    public int getDefaultVibrationAmplitude() {
+        if (mDefaultVibrationAmplitude < 1 || mDefaultVibrationAmplitude > 255) {
+            return DEFAULT_AMPLITUDE;
+        }
+        return mDefaultVibrationAmplitude;
+    }
+
+    /**
+     * Return the device default gain to be applied between scale levels to define the scale factor
+     * for each level.
+     */
+    public float getDefaultVibrationScaleLevelGain() {
+        // TODO(b/356407380): add device config for this
+        return DEFAULT_SCALE_LEVEL_GAIN;
+    }
+
+    /**
      * The duration, in milliseconds, that should be applied to the ramp to turn off the vibrator
      * when a vibration is cancelled or finished at non-zero amplitude.
      */
@@ -233,6 +269,7 @@
     public String toString() {
         return "VibrationConfig{"
                 + "mIgnoreVibrationsOnWirelessCharger=" + mIgnoreVibrationsOnWirelessCharger
+                + ", mDefaultVibrationAmplitude=" + mDefaultVibrationAmplitude
                 + ", mHapticChannelMaxVibrationAmplitude=" + mHapticChannelMaxVibrationAmplitude
                 + ", mRampStepDurationMs=" + mRampStepDurationMs
                 + ", mRampDownDurationMs=" + mRampDownDurationMs
@@ -258,6 +295,7 @@
         pw.println("VibrationConfig:");
         pw.increaseIndent();
         pw.println("ignoreVibrationsOnWirelessCharger = " + mIgnoreVibrationsOnWirelessCharger);
+        pw.println("defaultVibrationAmplitude = " + mDefaultVibrationAmplitude);
         pw.println("hapticChannelMaxAmplitude = " + mHapticChannelMaxVibrationAmplitude);
         pw.println("rampStepDurationMs = " + mRampStepDurationMs);
         pw.println("rampDownDurationMs = " + mRampDownDurationMs);
diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig
index 40dd91f..53a1a67d 100644
--- a/core/java/android/os/vibrator/flags.aconfig
+++ b/core/java/android/os/vibrator/flags.aconfig
@@ -3,13 +3,6 @@
 
 flag {
     namespace: "haptics"
-    name: "use_vibrator_haptic_feedback"
-    description: "Enables performHapticFeedback to directly use the vibrator service instead of going through the window session"
-    bug: "295459081"
-}
-
-flag {
-    namespace: "haptics"
     name: "haptic_feedback_vibration_oem_customization_enabled"
     description: "Enables OEMs/devices to customize vibrations for haptic feedback"
     # Make read only. This is because the flag is used only once, and this could happen before
@@ -20,13 +13,6 @@
 
 flag {
     namespace: "haptics"
-    name: "keyboard_category_enabled"
-    description: "Enables the independent keyboard vibration settings feature"
-    bug: "289107579"
-}
-
-flag {
-    namespace: "haptics"
     name: "adaptive_haptics_enabled"
     description: "Enables the adaptive haptics feature"
     bug: "305961689"
@@ -95,3 +81,35 @@
       purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    namespace: "haptics"
+    name: "load_haptic_feedback_vibration_customization_from_resources"
+    description: "Load haptic feedback vibrations customization from resources."
+    is_fixed_read_only: true
+    bug: "295142743"
+    metadata {
+        purpose: PURPOSE_FEATURE
+    }
+}
+
+flag {
+    namespace: "haptics"
+    name: "haptic_feedback_input_source_customization_enabled"
+    description: "Enabled the extended haptic feedback customization by input source."
+    bug: "331819348"
+    is_fixed_read_only: true
+    metadata {
+        purpose: PURPOSE_FEATURE
+    }
+}
+
+flag {
+    namespace: "haptics"
+    name: "haptics_scale_v2_enabled"
+    description: "Enables new haptics scaling function across all usages"
+    bug: "345186129"
+    metadata {
+        purpose: PURPOSE_FEATURE
+    }
+}
diff --git a/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java b/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java
index a26c6f4..a95ce79 100644
--- a/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java
+++ b/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java
@@ -104,7 +104,7 @@
     public static void serialize(@NonNull VibrationEffect effect, @NonNull Writer writer,
             @Flags int flags) throws IOException {
         // Serialize effect first to fail early.
-        XmlSerializedVibration<VibrationEffect> serializedVibration =
+        XmlSerializedVibration<? extends VibrationEffect> serializedVibration =
                 toSerializedVibration(effect, flags);
         TypedXmlSerializer xmlSerializer = Xml.newFastSerializer();
         xmlSerializer.setFeature(XML_FEATURE_INDENT_OUTPUT, (flags & FLAG_PRETTY_PRINT) != 0);
@@ -114,9 +114,9 @@
         xmlSerializer.endDocument();
     }
 
-    private static XmlSerializedVibration<VibrationEffect> toSerializedVibration(
+    private static XmlSerializedVibration<? extends VibrationEffect> toSerializedVibration(
             VibrationEffect effect, @Flags int flags) throws SerializationFailedException {
-        XmlSerializedVibration<VibrationEffect> serializedVibration;
+        XmlSerializedVibration<? extends VibrationEffect> serializedVibration;
         int serializerFlags = 0;
         if ((flags & FLAG_ALLOW_HIDDEN_APIS) != 0) {
             serializerFlags |= XmlConstants.FLAG_ALLOW_HIDDEN_APIS;
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 5703f69..7ca40ea 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2287,6 +2287,26 @@
             "android.settings.MANAGE_MORE_DEFAULT_APPS_SETTINGS";
 
     /**
+     * Activity Action: Show Other NFC services settings.
+     * <p>
+     * If a Settings activity handles this intent action, an "Other NFC services" entry will be
+     * shown in the Default payment app settings, and clicking it will launch that activity.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you safeguard against this.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     *
+     * @hide
+     */
+    @FlaggedApi(android.nfc.Flags.FLAG_NFC_ACTION_MANAGE_SERVICES_SETTINGS)
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    @SystemApi
+    public static final String ACTION_MANAGE_OTHER_NFC_SERVICES_SETTINGS =
+            "android.settings.MANAGE_OTHER_NFC_SERVICES_SETTINGS";
+
+    /**
      * Activity Action: Show app screen size list settings for user to override app aspect
      * ratio.
      * <p>
@@ -12543,6 +12563,19 @@
                 "launcher_taskbar_education_showing";
 
         /**
+         * Whether any Compat UI Education is currently showing.
+         *
+         * <p>1 if true, 0 or unset otherwise.
+         *
+         * <p>This setting is used to inform other components that the Compat UI Education is
+         * currently showing, which can prevent them from showing something else to the user.
+         *
+         * @hide
+         */
+        public static final String COMPAT_UI_EDUCATION_SHOWING =
+                "compat_ui_education_showing";
+
+        /**
          * Whether or not adaptive charging feature is enabled by user.
          * Type: int (0 for false, 1 for true)
          * Default: 1
@@ -20158,6 +20191,36 @@
              */
             public static final int PHONE_SWITCHING_STATUS_IN_PROGRESS_MIGRATION_SUCCESS = 11;
 
+            /**
+             * Phone switching request source
+             * @hide
+             */
+            public static final String PHONE_SWITCHING_REQUEST_SOURCE =
+                    "phone_switching_request_source";
+
+            /**
+             * No phone switching request source
+             * @hide
+             */
+            public static final int PHONE_SWITCHING_REQUEST_SOURCE_NONE = 0;
+
+            /**
+             * Phone switching triggered by watch
+             * @hide
+             */
+            public static final int PHONE_SWITCHING_REQUEST_SOURCE_WATCH = 1;
+
+            /**
+             * Phone switching triggered by companion, user confirmation required
+             * @hide
+             */
+            public static final int PHONE_SWITCHING_REQUEST_SOURCE_COMPANION_USER_CONFIRMATION = 2;
+
+            /**
+             * Phone switching triggered by companion, user confirmation not required
+             * @hide
+             */
+            public static final int PHONE_SWITCHING_REQUEST_SOURCE_COMPANION = 3;
 
             /**
              * Whether the device has enabled the feature to reduce motion and animation
@@ -20205,14 +20268,6 @@
             public static final int TETHERED_CONFIG_RESTRICTED = 3;
 
             /**
-             * Whether phone switching is supported.
-             *
-             * (0 = false, 1 = true)
-             * @hide
-             */
-            public static final String PHONE_SWITCHING_SUPPORTED = "phone_switching_supported";
-
-            /**
              * Setting indicating the name of the Wear OS package that hosts the Media Controls UI.
              *
              * @hide
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index d019bad..6eaef78f 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -4985,6 +4985,16 @@
          */
         public static final String COLUMN_SATELLITE_ESOS_SUPPORTED = "satellite_esos_supported";
 
+        /**
+         * TelephonyProvider column name for satellite provisioned status. The value of this
+         * column is set based on whether carrier roaming or OEM-enabled NB-IOT satellite service is
+         * provisioned or not. By default, it's disabled.
+         *
+         * @hide
+         */
+        public static final String COLUMN_IS_SATELLITE_PROVISIONED_FOR_NON_IP_DATAGRAM =
+                "is_satellite_provisioned_for_non_ip_datagram";
+
         /** All columns in {@link SimInfo} table. */
         private static final List<String> ALL_COLUMNS = List.of(
                 COLUMN_UNIQUE_KEY_SUBSCRIPTION_ID,
@@ -5061,7 +5071,8 @@
                 COLUMN_TRANSFER_STATUS,
                 COLUMN_SATELLITE_ENTITLEMENT_STATUS,
                 COLUMN_SATELLITE_ENTITLEMENT_PLMNS,
-                COLUMN_SATELLITE_ESOS_SUPPORTED
+                COLUMN_SATELLITE_ESOS_SUPPORTED,
+                COLUMN_IS_SATELLITE_PROVISIONED_FOR_NON_IP_DATAGRAM
         );
 
         /**
diff --git a/core/java/android/security/net/config/SystemCertificateSource.java b/core/java/android/security/net/config/SystemCertificateSource.java
index 3a254c1..bdda42a 100644
--- a/core/java/android/security/net/config/SystemCertificateSource.java
+++ b/core/java/android/security/net/config/SystemCertificateSource.java
@@ -19,6 +19,8 @@
 import android.os.Environment;
 import android.os.UserHandle;
 
+import com.android.internal.util.ArrayUtils;
+
 import java.io.File;
 
 /**
@@ -45,7 +47,7 @@
         }
         File updatable_dir = new File("/apex/com.android.conscrypt/cacerts");
         if (updatable_dir.exists()
-                && !(updatable_dir.list().length == 0)) {
+                && !(ArrayUtils.isEmpty(updatable_dir.list()))) {
             return updatable_dir;
         }
         return new File(System.getenv("ANDROID_ROOT") + "/etc/security/cacerts");
diff --git a/core/java/android/service/dreams/DreamOverlayService.java b/core/java/android/service/dreams/DreamOverlayService.java
index 17d2790..013ec5f 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;
 
 
 /**
@@ -52,43 +54,51 @@
     // 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 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 {
             mDreamComponent = ComponentName.unflattenFromString(dreamComponent);
             mShowComplications = shouldShowComplications;
             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);
             }
         }
 
@@ -161,17 +171,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 +212,12 @@
         }
     }
 
+    @Override
+    public void onDestroy() {
+        mCurrentClient = null;
+        super.onDestroy();
+    }
+
     @Nullable
     @Override
     public final IBinder onBind(@NonNull Intent intent) {
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 06e53ac..133b3d1 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -267,10 +267,10 @@
     private boolean mDozing;
     private boolean mWindowless;
     private boolean mPreviewMode;
-    private volatile int mDozeScreenState = Display.STATE_UNKNOWN;
-    private volatile @Display.StateReason int mDozeScreenStateReason = Display.STATE_REASON_UNKNOWN;
-    private volatile int mDozeScreenBrightness = PowerManager.BRIGHTNESS_DEFAULT;
-    private volatile float mDozeScreenBrightnessFloat = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+    private int mDozeScreenState = Display.STATE_UNKNOWN;
+    private @Display.StateReason int mDozeScreenStateReason = Display.STATE_REASON_UNKNOWN;
+    private int mDozeScreenBrightness = PowerManager.BRIGHTNESS_DEFAULT;
+    private float mDozeScreenBrightnessFloat = PowerManager.BRIGHTNESS_INVALID_FLOAT;
 
     private boolean mDebug = false;
 
@@ -913,13 +913,15 @@
      */
     @UnsupportedAppUsage
     public void startDozing() {
-        if (mCanDoze && !mDozing) {
-            mDozing = true;
-            updateDoze();
+        synchronized (this) {
+            if (mCanDoze && !mDozing) {
+                mDozing = true;
+                updateDoze();
+            }
         }
     }
 
-    private void updateDoze() {
+    private synchronized void updateDoze() {
         if (mDreamToken == null) {
             Slog.w(mTag, "Updating doze without a dream token.");
             return;
@@ -927,6 +929,9 @@
 
         if (mDozing) {
             try {
+                Slog.v(mTag, "UpdateDoze mDozeScreenState=" + mDozeScreenState
+                        + " mDozeScreenBrightness=" + mDozeScreenBrightness
+                        + " mDozeScreenBrightnessFloat=" + mDozeScreenBrightnessFloat);
                 if (startAndStopDozingInBackground()) {
                     mDreamManager.startDozingOneway(
                             mDreamToken, mDozeScreenState, mDozeScreenStateReason,
@@ -1048,10 +1053,12 @@
      */
     @UnsupportedAppUsage
     public void setDozeScreenState(int state, @Display.StateReason int reason) {
-        if (mDozeScreenState != state) {
-            mDozeScreenState = state;
-            mDozeScreenStateReason = reason;
-            updateDoze();
+        synchronized (this) {
+            if (mDozeScreenState != state) {
+                mDozeScreenState = state;
+                mDozeScreenStateReason = reason;
+                updateDoze();
+            }
         }
     }
 
@@ -1103,9 +1110,11 @@
         if (brightness != PowerManager.BRIGHTNESS_DEFAULT) {
             brightness = clampAbsoluteBrightness(brightness);
         }
-        if (mDozeScreenBrightness != brightness) {
-            mDozeScreenBrightness = brightness;
-            updateDoze();
+        synchronized (this) {
+            if (mDozeScreenBrightness != brightness) {
+                mDozeScreenBrightness = brightness;
+                updateDoze();
+            }
         }
     }
 
@@ -1141,9 +1150,12 @@
         if (!Float.isNaN(brightness)) {
             brightness = clampAbsoluteBrightnessFloat(brightness);
         }
-        if (!BrightnessSynchronizer.floatEquals(mDozeScreenBrightnessFloat, brightness)) {
-            mDozeScreenBrightnessFloat = brightness;
-            updateDoze();
+
+        synchronized (this) {
+            if (!BrightnessSynchronizer.floatEquals(mDozeScreenBrightnessFloat, brightness)) {
+                mDozeScreenBrightnessFloat = brightness;
+                updateDoze();
+            }
         }
     }
 
diff --git a/core/java/android/service/notification/DeviceEffectsApplier.java b/core/java/android/service/notification/DeviceEffectsApplier.java
index 5194cdd..2472860 100644
--- a/core/java/android/service/notification/DeviceEffectsApplier.java
+++ b/core/java/android/service/notification/DeviceEffectsApplier.java
@@ -16,8 +16,6 @@
 
 package android.service.notification;
 
-import android.service.notification.ZenModeConfig.ConfigChangeOrigin;
-
 /**
  * Responsible for making any service calls needed to apply the set of {@link ZenDeviceEffects} that
  * make sense for the current platform.
@@ -43,5 +41,5 @@
      *               changing as a result of an explicit user action, then it makes sense to
      *               apply them immediately regardless.
      */
-    void apply(ZenDeviceEffects effects, @ConfigChangeOrigin int source);
+    void apply(ZenDeviceEffects effects, @ZenModeConfig.ConfigOrigin int source);
 }
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 7ca248d..fc6c2e8 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -25,6 +25,7 @@
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF;
 import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+import static android.service.notification.SystemZenRules.PACKAGE_ANDROID;
 import static android.service.notification.ZenAdapters.peopleTypeToPrioritySenders;
 import static android.service.notification.ZenAdapters.prioritySendersToPeopleType;
 import static android.service.notification.ZenAdapters.zenPolicyConversationSendersToNotificationPolicy;
@@ -74,8 +75,9 @@
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
+import androidx.annotation.VisibleForTesting;
+
 import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.XmlUtils;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
@@ -112,68 +114,83 @@
     private static final String TAG = "ZenModeConfig";
 
     /**
-     * The {@link ZenModeConfig} is being updated because of an unknown reason.
+     * The {@link ZenModeConfig} is updated because of an unknown reason.
      */
-    public static final int UPDATE_ORIGIN_UNKNOWN = 0;
+    public static final int ORIGIN_UNKNOWN = 0;
 
     /**
-     * The {@link ZenModeConfig} is being updated because of system initialization (i.e. load from
+     * The {@link ZenModeConfig} is updated because of system initialization (i.e. load from
      * storage, on device boot).
      */
-    public static final int UPDATE_ORIGIN_INIT = 1;
+    public static final int ORIGIN_INIT = 1;
 
-    /** The {@link ZenModeConfig} is being updated (replaced) because of a user switch or unlock. */
-    public static final int UPDATE_ORIGIN_INIT_USER = 2;
+    /** The {@link ZenModeConfig} is updated (replaced) because of a user switch or unlock. */
+    public static final int ORIGIN_INIT_USER = 2;
 
-    /** The {@link ZenModeConfig} is being updated because of a user action, for example:
+    /**
+     * The {@link ZenModeConfig} is updated because of a <em>user action</em> performed from a
+     * system surface, such as:
      * <ul>
-     *     <li>{@link NotificationManager#setAutomaticZenRuleState} with a
-     *     {@link Condition#source} equal to {@link Condition#SOURCE_USER_ACTION}.</li>
-     *     <li>Adding, updating, or removing a rule from Settings.</li>
-     *     <li>Directly activating or deactivating/snoozing a rule through some UI affordance (e.g.
-     *     Quick Settings).</li>
+     *     <li>Adding, updating, or removing a rule from Settings.
+     *     <li>Activating or deactivating a rule through the System (e.g. from Settings/Modes).
+     *     <li>Activating or deactivating a rule through SystemUi (e.g. with Quick Settings).
      * </ul>
+     *
+     * <p>This does <em>not</em> include user actions from apps ({@link #ORIGIN_USER_IN_APP} nor
+     * non-user actions from the system ({@link #ORIGIN_SYSTEM}).
      */
-    public static final int UPDATE_ORIGIN_USER = 3;
+    public static final int ORIGIN_USER_IN_SYSTEMUI = 3;
 
     /**
-     * The {@link ZenModeConfig} is being "independently" updated by an app, and not as a result of
-     * a user's action inside that app (for example, activating an {@link AutomaticZenRule} based on
-     * a previously set schedule).
+     * The {@link ZenModeConfig} is updated by an app, but (probably) not as a result of a user
+     * action (for example, activating an {@link AutomaticZenRule} based on a previously set
+     * schedule).
+     *
+     * <p>Note that {@code ORIGIN_APP} is the only option for all public APIs except
+     * {@link NotificationManager#setAutomaticZenRuleState} -- apps cannot claim to be adding or
+     * updating a rule on behalf of the user.
      */
-    public static final int UPDATE_ORIGIN_APP = 4;
+    public static final int ORIGIN_APP = 4;
 
     /**
-     * The {@link ZenModeConfig} is being updated by the System or SystemUI. Note that this only
-     * includes cases where the call is coming from the System/SystemUI but the change is not due to
-     * a user action (e.g. automatically activating a schedule-based rule). If the change is a
-     * result of a user action (e.g. activating a rule by tapping on its QS tile) then
-     * {@link #UPDATE_ORIGIN_USER} is used instead.
+     * The {@link ZenModeConfig} is updated by the System (or SystemUI). This only includes cases
+     * where the call is coming from the System/SystemUI but the change is not due to a user action
+     * (e.g. automatically activating a schedule-based rule, or some service toggling Do Not
+     * Disturb). See {@link #ORIGIN_USER_IN_SYSTEMUI}.
      */
-    public static final int UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI = 5;
+    public static final int ORIGIN_SYSTEM = 5;
 
     /**
      * The {@link ZenModeConfig} is being updated (replaced) because the user's DND configuration
      * is being restored from a backup.
      */
-    public static final int UPDATE_ORIGIN_RESTORE_BACKUP = 6;
+    public static final int ORIGIN_RESTORE_BACKUP = 6;
 
-    @IntDef(prefix = { "UPDATE_ORIGIN_" }, value = {
-            UPDATE_ORIGIN_UNKNOWN,
-            UPDATE_ORIGIN_INIT,
-            UPDATE_ORIGIN_INIT_USER,
-            UPDATE_ORIGIN_USER,
-            UPDATE_ORIGIN_APP,
-            UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
-            UPDATE_ORIGIN_RESTORE_BACKUP
+    /**
+     * The {@link ZenModeConfig} is updated from an app, and the app reports it's the result
+     * of a user action (e.g. tapping a button in the Wellbeing App to start Bedtime Mode).
+     * Corresponds to {@link NotificationManager#setAutomaticZenRuleState} with a
+     * {@link Condition#source} equal to {@link Condition#SOURCE_USER_ACTION}.</li>
+     */
+    public static final int ORIGIN_USER_IN_APP = 7;
+
+    @IntDef(prefix = { "ORIGIN_" }, value = {
+            ORIGIN_UNKNOWN,
+            ORIGIN_INIT,
+            ORIGIN_INIT_USER,
+            ORIGIN_USER_IN_SYSTEMUI,
+            ORIGIN_APP,
+            ORIGIN_SYSTEM,
+            ORIGIN_RESTORE_BACKUP,
+            ORIGIN_USER_IN_APP
     })
     @Retention(RetentionPolicy.SOURCE)
-    public @interface ConfigChangeOrigin {}
+    public @interface ConfigOrigin {}
 
     public static final int SOURCE_ANYONE = Policy.PRIORITY_SENDERS_ANY;
     public static final int SOURCE_CONTACT = Policy.PRIORITY_SENDERS_CONTACTS;
     public static final int SOURCE_STAR = Policy.PRIORITY_SENDERS_STARRED;
-    public static final int MAX_SOURCE = SOURCE_STAR;
+    private static final int MAX_SOURCE = SOURCE_STAR;
     private static final int DEFAULT_SOURCE = SOURCE_STAR;
     private static final int DEFAULT_CALLS_SOURCE = SOURCE_STAR;
 
@@ -439,7 +456,7 @@
             newRule.conditionId = Uri.EMPTY;
             newRule.allowManualInvocation = true;
             newRule.zenPolicy = getDefaultZenPolicy();
-            newRule.pkg = "android";
+            newRule.pkg = PACKAGE_ANDROID;
             manualRule = newRule;
         }
     }
@@ -942,15 +959,9 @@
         rt.user = safeInt(parser, ZEN_ATT_USER, rt.user);
         boolean readSuppressedEffects = false;
         boolean readManualRule = false;
+        boolean readManualRuleWithoutPolicy = false;
         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
             tag = parser.getName();
-            if (type == XmlPullParser.END_TAG && ZEN_TAG.equals(tag)) {
-                if (Flags.modesUi() && !readManualRule) {
-                    // migrate from fields on config into manual rule
-                    rt.manualRule.zenPolicy = rt.toZenPolicy();
-                }
-                return rt;
-            }
             if (type == XmlPullParser.START_TAG) {
                 if (ALLOW_TAG.equals(tag)) {
                     rt.allowCalls = safeBoolean(parser, ALLOW_ATT_CALLS,
@@ -1019,9 +1030,17 @@
                     rt.suppressedVisualEffects = safeInt(parser, DISALLOW_ATT_VISUAL_EFFECTS,
                             DEFAULT_SUPPRESSED_VISUAL_EFFECTS);
                 } else if (MANUAL_TAG.equals(tag)) {
-                    rt.manualRule = readRuleXml(parser);
-                    if (rt.manualRule != null) {
+                    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;
+                        }
                     }
                 } else if (AUTOMATIC_TAG.equals(tag)
                         || (Flags.modesApi() && AUTOMATIC_DELETED_TAG.equals(tag))) {
@@ -1043,6 +1062,23 @@
                             STATE_ATT_CHANNELS_BYPASSING_DND, DEFAULT_CHANNELS_BYPASSING_DND);
                 }
             }
+            if (type == XmlPullParser.END_TAG && ZEN_TAG.equals(tag)) {
+                if (Flags.modesUi() && (!readManualRule || readManualRuleWithoutPolicy)) {
+                    // migrate from fields on config into manual rule
+                    rt.manualRule.zenPolicy = rt.toZenPolicy();
+                    if (readManualRuleWithoutPolicy) {
+                        // indicates that the xml represents a pre-modes_ui XML with an enabled
+                        // manual rule; set rule active, and fill in other fields as would be done
+                        // in ensureManualZenRule() and setManualZenMode().
+                        rt.manualRule.pkg = PACKAGE_ANDROID;
+                        rt.manualRule.type = AutomaticZenRule.TYPE_OTHER;
+                        rt.manualRule.condition = new Condition(
+                                rt.manualRule.conditionId != null ? rt.manualRule.conditionId
+                                        : Uri.EMPTY, "", Condition.STATE_TRUE);
+                    }
+                }
+                return rt;
+            }
         }
         throw new IllegalStateException("Failed to reach END_DOCUMENT");
     }
@@ -1174,7 +1210,7 @@
             }
             if (Flags.modesUi()) {
                 rt.disabledOrigin = safeInt(parser, RULE_ATT_DISABLED_ORIGIN,
-                        UPDATE_ORIGIN_UNKNOWN);
+                        ORIGIN_UNKNOWN);
                 rt.legacySuppressedEffects = safeInt(parser,
                         RULE_ATT_LEGACY_SUPPRESSED_EFFECTS, 0);
             }
@@ -2504,10 +2540,34 @@
     }
 
     public static class ZenRule implements Parcelable {
+
+        /** No manual override. Rule owner can decide its state. */
+        public static final int OVERRIDE_NONE = 0;
+        /**
+         * User has manually activated a mode. This will temporarily overrule the rule owner's
+         * decision to deactivate it (see {@link #reconsiderConditionOverride}).
+         */
+        public static final int OVERRIDE_ACTIVATE = 1;
+        /**
+         * User has manually deactivated an active mode, or setting ZEN_MODE_OFF (for the few apps
+         * still allowed to do that) snoozed the mode. This will temporarily overrule the rule
+         * owner's decision to activate it (see {@link #reconsiderConditionOverride}).
+         */
+        public static final int OVERRIDE_DEACTIVATE = 2;
+
+        @IntDef(prefix = { "OVERRIDE" }, value = {
+                OVERRIDE_NONE,
+                OVERRIDE_ACTIVATE,
+                OVERRIDE_DEACTIVATE
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface ConditionOverride {}
+
         @UnsupportedAppUsage
         public boolean enabled;
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-        public boolean snoozing;         // user manually disabled this instance
+        @Deprecated
+        public boolean snoozing; // user manually disabled this instance. Obsolete with MODES_UI
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
         public String name;              // required for automatic
         @UnsupportedAppUsage
@@ -2537,11 +2597,21 @@
         @ZenDeviceEffects.ModifiableField public int zenDeviceEffectsUserModifiedFields;
         @Nullable public Instant deletionInstant; // Only set on deleted rules.
         @FlaggedApi(Flags.FLAG_MODES_UI)
-        @ConfigChangeOrigin public int disabledOrigin = UPDATE_ORIGIN_UNKNOWN;
+        @ConfigOrigin
+        public int disabledOrigin = ORIGIN_UNKNOWN;
         // The obsolete suppressed effects in NM.Policy (SCREEN_ON, SCREEN_OFF) cannot be put in a
         // ZenPolicy, so we store them here, only for the manual rule.
         @FlaggedApi(Flags.FLAG_MODES_UI)
         int legacySuppressedEffects;
+        /**
+         * Signals a user's action to (temporarily or permanently) activate or deactivate this
+         * rule, overruling the condition set by the owner. This value is not stored to disk, as
+         * it shouldn't survive reboots or be involved in B&R. It might be reset by certain
+         * owner-provided state transitions as well.
+         */
+        @FlaggedApi(Flags.FLAG_MODES_UI)
+        @ConditionOverride
+        int conditionOverride = OVERRIDE_NONE;
 
         public ZenRule() { }
 
@@ -2583,6 +2653,7 @@
                 if (Flags.modesUi()) {
                     disabledOrigin = source.readInt();
                     legacySuppressedEffects = source.readInt();
+                    conditionOverride = source.readInt();
                 }
             }
         }
@@ -2661,6 +2732,7 @@
                 if (Flags.modesUi()) {
                     dest.writeInt(disabledOrigin);
                     dest.writeInt(legacySuppressedEffects);
+                    dest.writeInt(conditionOverride);
                 }
             }
         }
@@ -2671,9 +2743,16 @@
                     .append("id=").append(id)
                     .append(",state=").append(condition == null ? "STATE_FALSE"
                             : Condition.stateToString(condition.state))
-                    .append(",enabled=").append(String.valueOf(enabled).toUpperCase())
-                    .append(",snoozing=").append(snoozing)
-                    .append(",name=").append(name)
+                    .append(",enabled=").append(String.valueOf(enabled).toUpperCase());
+
+            if (Flags.modesUi()) {
+                sb.append(",conditionOverride=")
+                        .append(conditionOverrideToString(conditionOverride));
+            } else {
+                sb.append(",snoozing=").append(snoozing);
+            }
+
+            sb.append(",name=").append(name)
                     .append(",zenMode=").append(Global.zenModeToString(zenMode))
                     .append(",conditionId=").append(conditionId)
                     .append(",pkg=").append(pkg)
@@ -2716,6 +2795,15 @@
             return sb.append(']').toString();
         }
 
+        private static String conditionOverrideToString(@ConditionOverride int value) {
+            return switch(value) {
+                case OVERRIDE_ACTIVATE -> "OVERRIDE_ACTIVATE";
+                case OVERRIDE_DEACTIVATE -> "OVERRIDE_DEACTIVATE";
+                case OVERRIDE_NONE -> "OVERRIDE_NONE";
+                default -> "UNKNOWN";
+            };
+        }
+
         /** @hide */
         // TODO: add configuration activity
         public void dumpDebug(ProtoOutputStream proto, long fieldId) {
@@ -2726,7 +2814,11 @@
             proto.write(ZenRuleProto.CREATION_TIME_MS, creationTime);
             proto.write(ZenRuleProto.ENABLED, enabled);
             proto.write(ZenRuleProto.ENABLER, enabler);
-            proto.write(ZenRuleProto.IS_SNOOZING, snoozing);
+            if (Flags.modesApi() && Flags.modesUi()) {
+                proto.write(ZenRuleProto.IS_SNOOZING, conditionOverride == OVERRIDE_DEACTIVATE);
+            } else {
+                proto.write(ZenRuleProto.IS_SNOOZING, snoozing);
+            }
             proto.write(ZenRuleProto.ZEN_MODE, zenMode);
             if (conditionId != null) {
                 proto.write(ZenRuleProto.CONDITION_ID, conditionId.toString());
@@ -2779,7 +2871,8 @@
                 if (Flags.modesUi()) {
                     finalEquals = finalEquals
                             && other.disabledOrigin == disabledOrigin
-                            && other.legacySuppressedEffects == legacySuppressedEffects;
+                            && other.legacySuppressedEffects == legacySuppressedEffects
+                            && other.conditionOverride == conditionOverride;
                 }
             }
 
@@ -2795,7 +2888,8 @@
                             zenDeviceEffects, modified, allowManualInvocation, iconResName,
                             triggerDescription, type, userModifiedFields,
                             zenPolicyUserModifiedFields, zenDeviceEffectsUserModifiedFields,
-                            deletionInstant, disabledOrigin, legacySuppressedEffects);
+                            deletionInstant, disabledOrigin, legacySuppressedEffects,
+                            conditionOverride);
                 } else {
                     return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition,
                             component, configurationActivity, pkg, id, enabler, zenPolicy,
@@ -2821,8 +2915,74 @@
             }
         }
 
+        // TODO: b/333527800 - Rename to isActive()
         public boolean isAutomaticActive() {
-            return enabled && !snoozing && getPkg() != null && isTrueOrUnknown();
+            if (Flags.modesApi() && Flags.modesUi()) {
+                if (!enabled || getPkg() == null) {
+                    return false;
+                } else if (conditionOverride == OVERRIDE_ACTIVATE) {
+                    return true;
+                } else if (conditionOverride == OVERRIDE_DEACTIVATE) {
+                    return false;
+                } else {
+                    return isTrueOrUnknown();
+                }
+            } else {
+                return enabled && !snoozing && getPkg() != null && isTrueOrUnknown();
+            }
+        }
+
+        @VisibleForTesting(otherwise = VisibleForTesting.NONE)
+        @ConditionOverride
+        public int getConditionOverride() {
+            if (Flags.modesApi() && Flags.modesUi()) {
+                return conditionOverride;
+            } else {
+                return snoozing ? OVERRIDE_DEACTIVATE : OVERRIDE_NONE;
+            }
+        }
+
+        public void setConditionOverride(@ConditionOverride int value) {
+            if (Flags.modesApi() && Flags.modesUi()) {
+                conditionOverride = value;
+            } else {
+                if (value == OVERRIDE_ACTIVATE) {
+                    Slog.wtf(TAG, "Shouldn't set OVERRIDE_ACTIVATE if MODES_UI is off");
+                } else if (value == OVERRIDE_DEACTIVATE) {
+                    snoozing = true;
+                } else if (value == OVERRIDE_NONE) {
+                    snoozing = false;
+                }
+            }
+        }
+
+        public void resetConditionOverride() {
+            setConditionOverride(OVERRIDE_NONE);
+        }
+
+        /**
+         * Possibly remove the override, depending on the rule owner's intended state.
+         *
+         * <p>This allows rule owners to "take over" manually-provided state with their smartness,
+         * but only once both agree.
+         *
+         * <p>For example, a manually activated rule wins over rule owner's opinion that it should
+         * be off, until the owner says it should be on, at which point it will turn off (without
+         * manual intervention) when the rule owner says it should be off. And symmetrically for
+         * manual deactivation (which used to be called "snoozing").
+         */
+        public void reconsiderConditionOverride() {
+            if (Flags.modesApi() && Flags.modesUi()) {
+                if (conditionOverride == OVERRIDE_ACTIVATE && isTrueOrUnknown()) {
+                    resetConditionOverride();
+                } else if (conditionOverride == OVERRIDE_DEACTIVATE && !isTrueOrUnknown()) {
+                    resetConditionOverride();
+                }
+            } else {
+                if (snoozing && !isTrueOrUnknown()) {
+                    snoozing = false;
+                }
+            }
         }
 
         public String getPkg() {
diff --git a/core/java/android/service/notification/ZenModeDiff.java b/core/java/android/service/notification/ZenModeDiff.java
index a37e227..05c2a9c 100644
--- a/core/java/android/service/notification/ZenModeDiff.java
+++ b/core/java/android/service/notification/ZenModeDiff.java
@@ -454,6 +454,8 @@
      */
     public static class RuleDiff extends BaseDiff {
         public static final String FIELD_ENABLED = "enabled";
+        public static final String FIELD_CONDITION_OVERRIDE = "conditionOverride";
+        @Deprecated
         public static final String FIELD_SNOOZING = "snoozing";
         public static final String FIELD_NAME = "name";
         public static final String FIELD_ZEN_MODE = "zenMode";
@@ -507,8 +509,15 @@
             if (from.enabled != to.enabled) {
                 addField(FIELD_ENABLED, new FieldDiff<>(from.enabled, to.enabled));
             }
-            if (from.snoozing != to.snoozing) {
-                addField(FIELD_SNOOZING, new FieldDiff<>(from.snoozing, to.snoozing));
+            if (Flags.modesApi() && Flags.modesUi()) {
+                if (from.conditionOverride != to.conditionOverride) {
+                    addField(FIELD_CONDITION_OVERRIDE,
+                            new FieldDiff<>(from.conditionOverride, to.conditionOverride));
+                }
+            } else {
+                if (from.snoozing != to.snoozing) {
+                    addField(FIELD_SNOOZING, new FieldDiff<>(from.snoozing, to.snoozing));
+                }
             }
             if (!Objects.equals(from.name, to.name)) {
                 addField(FIELD_NAME, new FieldDiff<>(from.name, to.name));
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index 88a1b9c..bb3f6c9 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -118,6 +118,13 @@
 }
 
 flag {
+  name: "insert_mode_highlight_range"
+  namespace: "text"
+  description: "Make the highlight range stick after editing, this handles the corner cases where the entire text is replaced with itself(or transformed by developer) after each editing."
+  bug: "355137282"
+}
+
+flag {
   name: "insert_mode_not_update_selection"
   namespace: "text"
   description: "Fix that InputService#onUpdateSelection is not called when insert mode gesture is performed."
@@ -259,4 +266,14 @@
   metadata {
     purpose: PURPOSE_BUGFIX
   }
-}
\ No newline at end of file
+}
+
+flag {
+  name: "handwriting_gesture_with_transformation"
+  namespace: "text"
+  description: "Fix handwriting gesture is not working when view has transformation."
+  bug: "342619429"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/core/java/android/text/method/InsertModeTransformationMethod.java b/core/java/android/text/method/InsertModeTransformationMethod.java
index 59b80f3..6c6576f 100644
--- a/core/java/android/text/method/InsertModeTransformationMethod.java
+++ b/core/java/android/text/method/InsertModeTransformationMethod.java
@@ -36,6 +36,7 @@
 
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.Preconditions;
+import com.android.text.flags.Flags;
 
 import java.lang.reflect.Array;
 
@@ -171,9 +172,15 @@
                 // The text change is before the highlight start, move the highlight start.
                 mStart += diff;
             } else {
-                // The text change covers the highlight start. Extend the highlight start to the
-                // change start. This should be a rare case.
-                mStart = start;
+                if (Flags.insertModeHighlightRange()) {
+                    // The text change covers the highlight start. Don't change the start except
+                    // when it's out of range.
+                    mStart = Math.min(mStart, s.length());
+                } else {
+                    // The text change covers the highlight start. Extend the highlight start to the
+                    // change start. This should be a rare case.
+                    mStart = start;
+                }
             }
         }
 
@@ -181,9 +188,15 @@
             // The text change is before the highlight end, move the highlight end.
             mEnd += diff;
         } else if (start < mEnd) {
-            // The text change covers the highlight end. Extend the highlight end to the
-            // change end. This should be a rare case.
-            mEnd = start + count;
+            if (Flags.insertModeHighlightRange()) {
+                // The text change covers the highlight end. Don't change the end except when it's
+                // out of range.
+                mEnd = Math.min(mEnd, s.length());
+            } else {
+                // The text change covers the highlight end. Extend the highlight end to the
+                // change end. This should be a rare case.
+                mEnd = start + count;
+            }
         }
     }
 
diff --git a/core/java/android/text/style/AccessibilityClickableSpan.java b/core/java/android/text/style/AccessibilityClickableSpan.java
index 534ce63..ee8d156 100644
--- a/core/java/android/text/style/AccessibilityClickableSpan.java
+++ b/core/java/android/text/style/AccessibilityClickableSpan.java
@@ -156,4 +156,12 @@
                     return new AccessibilityClickableSpan[size];
                 }
             };
+
+    /**
+     * @return the ID of the original clickable span that this is applied to.
+     * @hide
+     */
+    public int getOriginalClickableSpanId() {
+        return mOriginalClickableSpanId;
+    }
 }
diff --git a/core/java/android/text/style/BulletSpan.java b/core/java/android/text/style/BulletSpan.java
index b3e7bda..f70e6c5 100644
--- a/core/java/android/text/style/BulletSpan.java
+++ b/core/java/android/text/style/BulletSpan.java
@@ -119,7 +119,10 @@
         this(gapWidth, color, true, bulletRadius);
     }
 
-    private BulletSpan(int gapWidth, @ColorInt int color, boolean wantColor,
+    /**
+     * @hide
+     */
+    public BulletSpan(int gapWidth, @ColorInt int color, boolean wantColor,
             @IntRange(from = 0) int bulletRadius) {
         mGapWidth = gapWidth;
         mBulletRadius = bulletRadius;
@@ -199,6 +202,14 @@
         return mColor;
     }
 
+    /**
+     * @return true if the bullet should apply the color.
+     * @hide
+     */
+    public boolean getWantColor() {
+        return mWantColor;
+    }
+
     @Override
     public void drawLeadingMargin(@NonNull Canvas canvas, @NonNull Paint paint, int x, int dir,
             int top, int baseline, int bottom,
diff --git a/core/java/android/text/style/SuggestionSpan.java b/core/java/android/text/style/SuggestionSpan.java
index ad044af..0cf96f6 100644
--- a/core/java/android/text/style/SuggestionSpan.java
+++ b/core/java/android/text/style/SuggestionSpan.java
@@ -248,21 +248,42 @@
     }
 
     public SuggestionSpan(Parcel src) {
-        mSuggestions = src.readStringArray();
-        mFlags = src.readInt();
-        mLocaleStringForCompatibility = src.readString();
-        mLanguageTag = src.readString();
-        mHashCode = src.readInt();
-        mEasyCorrectUnderlineColor = src.readInt();
-        mEasyCorrectUnderlineThickness = src.readFloat();
-        mMisspelledUnderlineColor = src.readInt();
-        mMisspelledUnderlineThickness = src.readFloat();
-        mAutoCorrectionUnderlineColor = src.readInt();
-        mAutoCorrectionUnderlineThickness = src.readFloat();
-        mGrammarErrorUnderlineColor = src.readInt();
-        mGrammarErrorUnderlineThickness = src.readFloat();
+        this(/* suggestions= */ src.readStringArray(), /* flags= */ src.readInt(),
+                /* localStringForCompatibility= */ src.readString(),
+                /* languageTag= */ src.readString(), /* hashCode= */ src.readInt(),
+                /* easyCorrectUnderlineColor= */ src.readInt(),
+                /* easyCorrectUnderlineThickness= */ src.readFloat(),
+                /* misspelledUnderlineColor= */ src.readInt(),
+                /* misspelledUnderlineThickness= */ src.readFloat(),
+                /* autoCorrectionUnderlineColor= */ src.readInt(),
+                /* autoCorrectionUnderlineThickness= */ src.readFloat(),
+                /* grammarErrorUnderlineColor= */ src.readInt(),
+                /* grammarErrorUnderlineThickness= */ src.readFloat());
     }
 
+    /** @hide */
+    public SuggestionSpan(String[] suggestions, int flags, String localeStringForCompatibility,
+            String languageTag, int hashCode, int easyCorrectUnderlineColor,
+            float easyCorrectUnderlineThickness, int misspelledUnderlineColor,
+            float misspelledUnderlineThickness, int autoCorrectionUnderlineColor,
+            float autoCorrectionUnderlineThickness, int grammarErrorUnderlineColor,
+            float grammarErrorUnderlineThickness) {
+        mSuggestions = suggestions;
+        mFlags = flags;
+        mLocaleStringForCompatibility = localeStringForCompatibility;
+        mLanguageTag = languageTag;
+        mHashCode = hashCode;
+        mEasyCorrectUnderlineColor = easyCorrectUnderlineColor;
+        mEasyCorrectUnderlineThickness = easyCorrectUnderlineThickness;
+        mMisspelledUnderlineColor = misspelledUnderlineColor;
+        mMisspelledUnderlineThickness = misspelledUnderlineThickness;
+        mAutoCorrectionUnderlineColor = autoCorrectionUnderlineColor;
+        mAutoCorrectionUnderlineThickness = autoCorrectionUnderlineThickness;
+        mGrammarErrorUnderlineColor = grammarErrorUnderlineColor;
+        mGrammarErrorUnderlineThickness = grammarErrorUnderlineThickness;
+    }
+
+
     /**
      * @return an array of suggestion texts for this span
      */
@@ -452,4 +473,44 @@
     public void notifySelection(Context context, String original, int index) {
         Log.w(TAG, "notifySelection() is deprecated.  Does nothing.");
     }
+
+    /** @hide */
+    public float getEasyCorrectUnderlineThickness() {
+        return mEasyCorrectUnderlineThickness;
+    }
+
+    /** @hide */
+    public int getEasyCorrectUnderlineColor() {
+        return mEasyCorrectUnderlineColor;
+    }
+
+    /** @hide */
+    public float getMisspelledUnderlineThickness() {
+        return mMisspelledUnderlineThickness;
+    }
+
+    /** @hide */
+    public int getMisspelledUnderlineColor() {
+        return mMisspelledUnderlineColor;
+    }
+
+    /** @hide */
+    public float getAutoCorrectionUnderlineThickness() {
+        return mAutoCorrectionUnderlineThickness;
+    }
+
+    /** @hide */
+    public int getAutoCorrectionUnderlineColor() {
+        return mAutoCorrectionUnderlineColor;
+    }
+
+    /** @hide */
+    public float getGrammarErrorUnderlineThickness() {
+        return mGrammarErrorUnderlineThickness;
+    }
+
+    /** @hide */
+    public int getGrammarErrorUnderlineColor() {
+        return mGrammarErrorUnderlineColor;
+    }
 }
diff --git a/core/java/android/text/style/TextAppearanceSpan.java b/core/java/android/text/style/TextAppearanceSpan.java
index d61228b..245a9db 100644
--- a/core/java/android/text/style/TextAppearanceSpan.java
+++ b/core/java/android/text/style/TextAppearanceSpan.java
@@ -233,36 +233,59 @@
     }
 
     public TextAppearanceSpan(Parcel src) {
-        mFamilyName = src.readString();
-        mStyle = src.readInt();
-        mTextSize = src.readInt();
-        if (src.readInt() != 0) {
-            mTextColor = ColorStateList.CREATOR.createFromParcel(src);
-        } else {
-            mTextColor = null;
-        }
-        if (src.readInt() != 0) {
-            mTextColorLink = ColorStateList.CREATOR.createFromParcel(src);
-        } else {
-            mTextColorLink = null;
-        }
-        mTypeface = LeakyTypefaceStorage.readTypefaceFromParcel(src);
+        this(/* familyName= */ src.readString(),
+                /* style= */ src.readInt(),
+                /* textSize= */ src.readInt(),
+                /* textColor= */ (src.readInt() != 0)
+                        ? ColorStateList.CREATOR.createFromParcel(src) : null,
+                /* textColorLink= */ (src.readInt() != 0)
+                        ? ColorStateList.CREATOR.createFromParcel(src) : null,
+                /* typeface= */ LeakyTypefaceStorage.readTypefaceFromParcel(src),
+                /* textFontWeight= */ src.readInt(),
+                /* textLocales= */
+                src.readParcelable(LocaleList.class.getClassLoader(), LocaleList.class),
+                /* shadowRadius= */ src.readFloat(),
+                /* shadowDx= */ src.readFloat(),
+                /* shadowDy= */ src.readFloat(),
+                /* shadowColor= */ src.readInt(),
+                /* hasElegantTextHeight= */ src.readBoolean(),
+                /* elegantTextHeight= */ src.readBoolean(),
+                /* hasLetterSpacing= */ src.readBoolean(),
+                /* letterSpacing= */ src.readFloat(),
+                /* fontFeatureSettings= */ src.readString(),
+                /* fontVariationSettings= */ src.readString());
+    }
 
-        mTextFontWeight = src.readInt();
-        mTextLocales = src.readParcelable(LocaleList.class.getClassLoader(), android.os.LocaleList.class);
+    /** @hide */
+    public TextAppearanceSpan(@Nullable String familyName, int style, int textSize,
+            @Nullable ColorStateList textColor, @Nullable ColorStateList textColorLink,
+            @Nullable Typeface typeface,
+            int textFontWeight, @Nullable LocaleList textLocales, float shadowRadius,
+            float shadowDx, float shadowDy, int shadowColor, boolean hasElegantTextHeight,
+            boolean elegantTextHeight, boolean hasLetterSpacing, float letterSpacing,
+            @Nullable String fontFeatureSettings, @Nullable String fontVariationSettings) {
+        mFamilyName = familyName;
+        mStyle = style;
+        mTextSize = textSize;
+        mTextColor = textColor;
+        mTextColorLink = textColorLink;
+        mTypeface = typeface;
 
-        mShadowRadius = src.readFloat();
-        mShadowDx = src.readFloat();
-        mShadowDy = src.readFloat();
-        mShadowColor = src.readInt();
+        mTextFontWeight = textFontWeight;
+        mTextLocales = textLocales;
 
-        mHasElegantTextHeight = src.readBoolean();
-        mElegantTextHeight = src.readBoolean();
-        mHasLetterSpacing = src.readBoolean();
-        mLetterSpacing = src.readFloat();
+        mShadowRadius = shadowRadius;
+        mShadowDx = shadowDx;
+        mShadowDy = shadowDy;
+        mShadowColor = shadowColor;
 
-        mFontFeatureSettings = src.readString();
-        mFontVariationSettings = src.readString();
+        mHasElegantTextHeight = hasElegantTextHeight;
+        mElegantTextHeight = elegantTextHeight;
+        mHasLetterSpacing = hasLetterSpacing;
+        mLetterSpacing = letterSpacing;
+
+        mFontFeatureSettings = fontFeatureSettings;
+        mFontVariationSettings = fontVariationSettings;
     }
 
     public int getSpanTypeId() {
@@ -564,4 +587,14 @@
                 + ", fontVariationSettings='" + getFontVariationSettings() + '\''
                 + '}';
     }
+
+    /** @hide */
+    public boolean hasElegantTextHeight() {
+        return mHasElegantTextHeight;
+    }
+
+    /** @hide */
+    public boolean hasLetterSpacing() {
+        return mHasLetterSpacing;
+    }
 }
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 12dbc5a..157cec8 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -708,7 +708,7 @@
      */
     @Nullable
     public Display.Mode findDefaultModeByRefreshRate(float refreshRate) {
-        Display.Mode[] modes = supportedModes;
+        Display.Mode[] modes = appsSupportedModes;
         Display.Mode defaultMode = getDefaultMode();
         for (int i = 0; i < modes.length; i++) {
             if (modes[i].matches(
@@ -723,7 +723,7 @@
      * Returns the list of supported refresh rates in the default mode.
      */
     public float[] getDefaultRefreshRates() {
-        Display.Mode[] modes = supportedModes;
+        Display.Mode[] modes = appsSupportedModes;
         ArraySet<Float> rates = new ArraySet<>();
         Display.Mode defaultMode = getDefaultMode();
         for (int i = 0; i < modes.length; i++) {
diff --git a/core/java/android/view/HapticScrollFeedbackProvider.java b/core/java/android/view/HapticScrollFeedbackProvider.java
index 6b354a0..0001176 100644
--- a/core/java/android/view/HapticScrollFeedbackProvider.java
+++ b/core/java/android/view/HapticScrollFeedbackProvider.java
@@ -100,8 +100,12 @@
 
         if (Math.abs(mTotalScrollPixels) >= mTickIntervalPixels) {
             mTotalScrollPixels %= mTickIntervalPixels;
-            // TODO(b/239594271): create a new `performHapticFeedbackForDevice` and use that here.
-            mView.performHapticFeedback(HapticFeedbackConstants.SCROLL_TICK);
+            if (android.os.vibrator.Flags.hapticFeedbackInputSourceCustomizationEnabled()) {
+                mView.performHapticFeedbackForInputDevice(
+                        HapticFeedbackConstants.SCROLL_TICK, inputDeviceId, source, /* flags= */ 0);
+            } else {
+                mView.performHapticFeedback(HapticFeedbackConstants.SCROLL_TICK);
+            }
         }
     }
 
@@ -115,9 +119,12 @@
         if (!mCanPlayLimitFeedback) {
             return;
         }
-
-        // TODO(b/239594271): create a new `performHapticFeedbackForDevice` and use that here.
-        mView.performHapticFeedback(HapticFeedbackConstants.SCROLL_LIMIT);
+        if (android.os.vibrator.Flags.hapticFeedbackInputSourceCustomizationEnabled()) {
+            mView.performHapticFeedbackForInputDevice(
+                    HapticFeedbackConstants.SCROLL_LIMIT, inputDeviceId, source, /* flags= */ 0);
+        } else {
+            mView.performHapticFeedback(HapticFeedbackConstants.SCROLL_LIMIT);
+        }
 
         mCanPlayLimitFeedback = false;
     }
@@ -128,22 +135,28 @@
         if (!mHapticScrollFeedbackEnabled) {
             return;
         }
-        // TODO(b/239594271): create a new `performHapticFeedbackForDevice` and use that here.
-        mView.performHapticFeedback(HapticFeedbackConstants.SCROLL_ITEM_FOCUS);
+        if (android.os.vibrator.Flags.hapticFeedbackInputSourceCustomizationEnabled()) {
+            mView.performHapticFeedbackForInputDevice(
+                    HapticFeedbackConstants.SCROLL_ITEM_FOCUS, inputDeviceId, source,
+                    /* flags= */ 0);
+        } else {
+            mView.performHapticFeedback(HapticFeedbackConstants.SCROLL_ITEM_FOCUS);
+        }
         mCanPlayLimitFeedback = true;
     }
 
     private void maybeUpdateCurrentConfig(int deviceId, int source, int axis) {
         if (mAxis != axis || mSource != source || mDeviceId != deviceId) {
+            mSource = source;
+            mAxis = axis;
+            mDeviceId = deviceId;
+
             if (mDisabledIfViewPlaysScrollHaptics
                     && (source == InputDevice.SOURCE_ROTARY_ENCODER)
                     && mViewConfig.isViewBasedRotaryEncoderHapticScrollFeedbackEnabled()) {
                 mHapticScrollFeedbackEnabled = false;
                 return;
             }
-            mSource = source;
-            mAxis = axis;
-            mDeviceId = deviceId;
 
             mHapticScrollFeedbackEnabled =
                     mViewConfig.isHapticScrollFeedbackEnabled(deviceId, axis, source);
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 762a302..11a3168 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -140,15 +140,6 @@
     oneway void finishDrawing(IWindow window, in SurfaceControl.Transaction postDrawTransaction,
             int seqId);
 
-    @UnsupportedAppUsage
-    boolean performHapticFeedback(int effectId, int flags, int privFlags);
-
-    /**
-     * Called by attached views to perform predefined haptic feedback without requiring VIBRATE
-     * permission.
-     */
-    oneway void performHapticFeedbackAsync(int effectId, int flags, int privFlags);
-
     /**
      * Initiate the drag operation itself
      *
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index d83f344..7896cbd 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -685,9 +685,6 @@
      */
     private @InsetsType int mCancelledForNewAnimationTypes;
 
-    private final Runnable mInvokeControllableInsetsChangedListeners =
-            this::invokeControllableInsetsChangedListeners;
-
     private final InsetsState.OnTraverseCallbacks mRemoveGoneSources =
             new InsetsState.OnTraverseCallbacks() {
 
@@ -2206,7 +2203,6 @@
      * @return The types that are now animating due to a listener invoking control/show/hide
      */
     private @InsetsType int invokeControllableInsetsChangedListeners() {
-        mHandler.removeCallbacks(mInvokeControllableInsetsChangedListeners);
         mLastStartedAnimTypes = 0;
         @InsetsType int types = calculateControllableTypes();
         int size = mControllableInsetsChangedListeners.size();
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index a7641c0..9e4b27d 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -3574,7 +3574,7 @@
             checkPreconditions(sc);
             if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
                 SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
-                        "reparent", this, sc,
+                        "setColor", this, sc,
                         "r=" + color[0] + " g=" + color[1] + " b=" + color[2]);
             }
             nativeSetColor(mNativeObject, sc.mNativeObject, color);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 5f8bea1..dbd65de 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -142,7 +142,6 @@
 import android.os.SystemClock;
 import android.os.Trace;
 import android.os.Vibrator;
-import android.os.vibrator.Flags;
 import android.service.credentials.CredentialProviderService;
 import android.sysprop.DisplayProperties;
 import android.text.InputType;
@@ -5712,9 +5711,6 @@
      */
     private PointerIcon mMousePointerIcon;
 
-    /** Vibrator for haptic feedback. */
-    private Vibrator mVibrator;
-
     /**
      * @hide
      */
@@ -28667,37 +28663,63 @@
      * @param flags Additional flags as per {@link HapticFeedbackConstants}.
      */
     public boolean performHapticFeedback(int feedbackConstant, int flags) {
-        if (feedbackConstant == HapticFeedbackConstants.NO_HAPTICS
-                || mAttachInfo == null) {
+        if (isPerformHapticFeedbackSuppressed(feedbackConstant, flags)) {
             return false;
         }
+
+        int privFlags = computeHapticFeedbackPrivateFlags();
+        return mAttachInfo.mRootCallbacks.performHapticFeedback(feedbackConstant, flags, privFlags);
+    }
+
+    /**
+     * <p>Provide haptic feedback to the user for this view.
+     *
+     * <p>Call this method (vs {@link #performHapticFeedback(int)}) to specify more details about
+     * the {@link InputDevice} that caused this haptic feedback. The framework will choose and
+     * provide a haptic feedback based on these details.
+     *
+     * <p>The feedback will only be performed if {@link #isHapticFeedbackEnabled()} is {@code true}.
+     *
+     * @param feedbackConstant One of the constants defined in {@link HapticFeedbackConstants}.
+     * @param inputDeviceId The ID of the {@link InputDevice} that generated the event which
+     *          triggered this haptic feedback request.
+     * @param inputSource The input source of the event which triggered this haptic feedback
+     *          request, defined as {@code InputDevice#SOURCE_*}.
+     *
+     * @hide
+     */
+    public void performHapticFeedbackForInputDevice(int feedbackConstant, int inputDeviceId,
+            int inputSource, int flags) {
+        if (isPerformHapticFeedbackSuppressed(feedbackConstant, flags)) {
+            return;
+        }
+
+        int privFlags = computeHapticFeedbackPrivateFlags();
+        mAttachInfo.mRootCallbacks.performHapticFeedbackForInputDevice(
+                feedbackConstant, inputDeviceId, inputSource, flags, privFlags);
+    }
+
+    private boolean isPerformHapticFeedbackSuppressed(int feedbackConstant, int flags) {
+        if (feedbackConstant == HapticFeedbackConstants.NO_HAPTICS
+                || mAttachInfo == null
+                || mAttachInfo.mSession == null) {
+            return true;
+        }
         //noinspection SimplifiableIfStatement
         if ((flags & HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING) == 0
                 && !isHapticFeedbackEnabled()) {
-            return false;
+            return true;
         }
+        return false;
+    }
 
+    private int computeHapticFeedbackPrivateFlags() {
         int privFlags = 0;
         if (mAttachInfo.mViewRootImpl != null
                 && mAttachInfo.mViewRootImpl.mWindowAttributes.type == TYPE_INPUT_METHOD) {
             privFlags = HapticFeedbackConstants.PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS;
         }
-        if (Flags.useVibratorHapticFeedback()) {
-            if (!mAttachInfo.canPerformHapticFeedback()) {
-                return false;
-            }
-            getSystemVibrator().performHapticFeedback(feedbackConstant,
-                    "View#performHapticFeedback", flags, privFlags);
-            return true;
-        }
-        return mAttachInfo.mRootCallbacks.performHapticFeedback(feedbackConstant, flags, privFlags);
-    }
-
-    private Vibrator getSystemVibrator() {
-        if (mVibrator != null) {
-            return mVibrator;
-        }
-        return mVibrator = mContext.getSystemService(Vibrator.class);
+        return privFlags;
     }
 
     /**
@@ -31731,6 +31753,11 @@
             boolean performHapticFeedback(int effectId,
                     @HapticFeedbackConstants.Flags int flags,
                     @HapticFeedbackConstants.PrivateFlags int privFlags);
+
+            void performHapticFeedbackForInputDevice(int effectId,
+                    int inputDeviceId, int inputSource,
+                    @HapticFeedbackConstants.Flags int flags,
+                    @HapticFeedbackConstants.PrivateFlags int privFlags);
         }
 
         /**
@@ -32297,11 +32324,6 @@
             return events;
         }
 
-        private boolean canPerformHapticFeedback() {
-            return mSession != null
-                    && (mDisplay.getFlags() & Display.FLAG_TOUCH_FEEDBACK_DISABLED) == 0;
-        }
-
         @Nullable
         ScrollCaptureInternal getScrollCaptureInternal() {
             if (mScrollCaptureInternal != null) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 9518abf..1c0700f 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -207,6 +207,7 @@
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
+import android.os.Vibrator;
 import android.provider.Settings;
 import android.sysprop.DisplayProperties;
 import android.sysprop.ViewProperties;
@@ -362,14 +363,6 @@
     private static final boolean ENABLE_INPUT_LATENCY_TRACKING = true;
 
     /**
-     * Controls whether to use the new oneway performHapticFeedback call. This returns
-     * true in a few more conditions, but doesn't affect which haptics happen. Notably, it
-     * makes the call to performHapticFeedback non-blocking, which reduces potential UI jank.
-     * This is intended as a temporary flag, ultimately becoming permanently 'true'.
-     */
-    private static final boolean USE_ASYNC_PERFORM_HAPTIC_FEEDBACK = true;
-
-    /**
      * Whether the client (system UI) is handling the transient gesture and the corresponding
      * animation.
      * @hide
@@ -956,6 +949,11 @@
      */
     AudioManager mAudioManager;
 
+    /**
+     * see {@link #performHapticFeedback(int, int, int)}
+     */
+    Vibrator mVibrator;
+
     final AccessibilityManager mAccessibilityManager;
 
     AccessibilityInteractionController mAccessibilityInteractionController;
@@ -2822,6 +2820,12 @@
         if (mAttachInfo.mThreadedRenderer != null) {
             mAttachInfo.mThreadedRenderer.setSurfaceControl(null, null);
         }
+
+        // Also reset the VRR relevant values.
+        mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_DEFAULT;
+        mLastPreferredFrameRateCategory = FRAME_RATE_CATEGORY_DEFAULT;
+        mPreferredFrameRate = 0;
+        mLastPreferredFrameRate = 0;
     }
 
     /**
@@ -9236,6 +9240,13 @@
         return mAudioManager;
     }
 
+    private Vibrator getSystemVibrator() {
+        if (mVibrator == null) {
+            mVibrator = mContext.getSystemService(Vibrator.class);
+        }
+        return mVibrator;
+    }
+
     private @Nullable AutofillManager getAutofillManager() {
         if (mView instanceof ViewGroup) {
             ViewGroup decorView = (ViewGroup) mView;
@@ -9662,17 +9673,23 @@
             return false;
         }
 
-        try {
-            if (USE_ASYNC_PERFORM_HAPTIC_FEEDBACK) {
-                mWindowSession.performHapticFeedbackAsync(effectId, flags, privFlags);
-                return true;
-            } else {
-                // Original blocking binder call path.
-                return mWindowSession.performHapticFeedback(effectId, flags, privFlags);
-            }
-        } catch (RemoteException e) {
-            return false;
+        getSystemVibrator().performHapticFeedback(
+                effectId, "ViewRootImpl#performHapticFeedback", flags, privFlags);
+        return true;
+    }
+
+    @Override
+    public void performHapticFeedbackForInputDevice(int effectId,
+            int inputDeviceId, int inputSource,
+            @HapticFeedbackConstants.Flags int flags,
+            @HapticFeedbackConstants.PrivateFlags int privFlags) {
+        if ((mDisplay.getFlags() & Display.FLAG_TOUCH_FEEDBACK_DISABLED) != 0) {
+            return;
         }
+
+        getSystemVibrator().performHapticFeedbackForInputDevice(effectId,
+                inputDeviceId, inputSource, "ViewRootImpl#performHapticFeedbackForInputDevice",
+                flags, privFlags);
     }
 
     /**
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 85d4ec0..017e004 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -132,6 +132,7 @@
 import android.window.TaskFpsCallback;
 import android.window.TrustedPresentationThresholds;
 
+import com.android.internal.R;
 import com.android.window.flags.Flags;
 
 import java.lang.annotation.ElementType;
@@ -482,6 +483,11 @@
      * @hide
      */
     int TRANSIT_PREPARE_BACK_NAVIGATION = 13;
+    /**
+     * An Activity was going to be invisible from back navigation.
+     * @hide
+     */
+    int TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION = 14;
 
     /**
      * The first slot for custom transition types. Callers (like Shell) can make use of custom
@@ -512,6 +518,7 @@
             TRANSIT_WAKE,
             TRANSIT_SLEEP,
             TRANSIT_PREPARE_BACK_NAVIGATION,
+            TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION,
             TRANSIT_FIRST_CUSTOM
     })
     @Retention(RetentionPolicy.SOURCE)
@@ -1926,6 +1933,7 @@
             case TRANSIT_WAKE: return "WAKE";
             case TRANSIT_SLEEP: return "SLEEP";
             case TRANSIT_PREPARE_BACK_NAVIGATION: return "PREDICTIVE_BACK";
+            case TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION: return "CLOSE_PREDICTIVE_BACK";
             case TRANSIT_FIRST_CUSTOM: return "FIRST_CUSTOM";
             default:
                 if (type > TRANSIT_FIRST_CUSTOM) {
@@ -3468,6 +3476,13 @@
         public static final int PRIVATE_FLAG_CONSUME_IME_INSETS = 1 << 25;
 
         /**
+         * Flag to indicate that the window has the
+         * {@link R.styleable.Window_windowOptOutEdgeToEdgeEnforcement} flag set.
+         * @hide
+         */
+        public static final int PRIVATE_FLAG_OPT_OUT_EDGE_TO_EDGE = 1 << 26;
+
+        /**
          * Flag to indicate that the window is controlling how it fits window insets on its own.
          * So we don't need to adjust its attributes for fitting window insets.
          * @hide
@@ -3540,6 +3555,7 @@
                 PRIVATE_FLAG_NOT_MAGNIFIABLE,
                 PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC,
                 PRIVATE_FLAG_CONSUME_IME_INSETS,
+                PRIVATE_FLAG_OPT_OUT_EDGE_TO_EDGE,
                 PRIVATE_FLAG_FIT_INSETS_CONTROLLED,
                 PRIVATE_FLAG_TRUSTED_OVERLAY,
                 PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME,
@@ -3644,6 +3660,10 @@
                         equals = PRIVATE_FLAG_CONSUME_IME_INSETS,
                         name = "CONSUME_IME_INSETS"),
                 @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_OPT_OUT_EDGE_TO_EDGE,
+                        equals = PRIVATE_FLAG_OPT_OUT_EDGE_TO_EDGE,
+                        name = "OPTOUT_EDGE_TO_EDGE"),
+                @ViewDebug.FlagToString(
                         mask = PRIVATE_FLAG_FIT_INSETS_CONTROLLED,
                         equals = PRIVATE_FLAG_FIT_INSETS_CONTROLLED,
                         name = "FIT_INSETS_CONTROLLED"),
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index 961a9c4..f50ea91 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -17,6 +17,8 @@
 package android.view;
 
 import static android.view.Display.INVALID_DISPLAY;
+import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPT_OUT_EDGE_TO_EDGE;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 
 import android.animation.ValueAnimator;
@@ -26,6 +28,7 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.res.Configuration;
+import android.content.res.TypedArray;
 import android.graphics.HardwareRenderer;
 import android.os.Binder;
 import android.os.Build;
@@ -45,6 +48,7 @@
 import android.window.InputTransferToken;
 import android.window.TrustedPresentationThresholds;
 
+import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.FastPrintWriter;
 
@@ -356,12 +360,12 @@
         }
 
         final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
+        final Context context = view.getContext();
         if (parentWindow != null) {
             parentWindow.adjustLayoutParamsForSubWindow(wparams);
         } else {
             // If there's no parent, then hardware acceleration for this view is
             // set from the application's hardware acceleration setting.
-            final Context context = view.getContext();
             if (context != null
                     && (context.getApplicationInfo().flags
                     & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
@@ -369,6 +373,14 @@
             }
         }
 
+        if (context != null && wparams.type > LAST_APPLICATION_WINDOW) {
+            final TypedArray styles = context.obtainStyledAttributes(R.styleable.Window);
+            if (styles.getBoolean(R.styleable.Window_windowOptOutEdgeToEdgeEnforcement, false)) {
+                wparams.privateFlags |= PRIVATE_FLAG_OPT_OUT_EDGE_TO_EDGE;
+            }
+            styles.recycle();
+        }
+
         ViewRootImpl root;
         View panelParentView = null;
 
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 0d027f1..d2747e4 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -504,16 +504,6 @@
     }
 
     @Override
-    public boolean performHapticFeedback(int effectId, int flags, int privFlags) {
-        return false;
-    }
-
-    @Override
-    public void performHapticFeedbackAsync(int effectId, int flags, int privFlags) {
-        performHapticFeedback(effectId, flags, privFlags);
-    }
-
-    @Override
     public android.os.IBinder performDrag(android.view.IWindow window, int flags,
             android.view.SurfaceControl surface, int touchSource, int touchDeviceId,
             int touchPointerId, float touchX, float touchY, float thumbCenterX, float thumbCenterY,
diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java
index 09306c7..288be9c 100644
--- a/core/java/android/view/animation/Animation.java
+++ b/core/java/android/view/animation/Animation.java
@@ -28,6 +28,7 @@
 import android.os.SystemProperties;
 import android.util.AttributeSet;
 import android.util.TypedValue;
+import android.view.WindowInsets;
 
 import dalvik.system.CloseGuard;
 
@@ -881,12 +882,13 @@
     }
 
     /**
-     * @return if a window animation has outsets applied to it.
+     * @return the edges to which outsets should be applied if run as a windoow animation.
      *
      * @hide
      */
-    public boolean hasExtension() {
-        return false;
+    @WindowInsets.Side.InsetsSide
+    public int getExtensionEdges() {
+        return 0x0;
     }
 
     /**
diff --git a/core/java/android/view/animation/AnimationSet.java b/core/java/android/view/animation/AnimationSet.java
index 5aaa994..bbdc9d0 100644
--- a/core/java/android/view/animation/AnimationSet.java
+++ b/core/java/android/view/animation/AnimationSet.java
@@ -21,6 +21,7 @@
 import android.graphics.RectF;
 import android.os.Build;
 import android.util.AttributeSet;
+import android.view.WindowInsets;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -540,12 +541,12 @@
 
     /** @hide */
     @Override
-    public boolean hasExtension() {
+    @WindowInsets.Side.InsetsSide
+    public int getExtensionEdges() {
+        int edge = 0x0;
         for (Animation animation : mAnimations) {
-            if (animation.hasExtension()) {
-                return true;
-            }
+            edge |= animation.getExtensionEdges();
         }
-        return false;
+        return edge;
     }
 }
diff --git a/core/java/android/view/animation/ExtendAnimation.java b/core/java/android/view/animation/ExtendAnimation.java
index 210eb8a..1aeee07 100644
--- a/core/java/android/view/animation/ExtendAnimation.java
+++ b/core/java/android/view/animation/ExtendAnimation.java
@@ -20,6 +20,7 @@
 import android.content.res.TypedArray;
 import android.graphics.Insets;
 import android.util.AttributeSet;
+import android.view.WindowInsets;
 
 /**
  * An animation that controls the outset of an object.
@@ -151,9 +152,12 @@
 
     /** @hide */
     @Override
-    public boolean hasExtension() {
-        return mFromInsets.left < 0 || mFromInsets.top < 0 || mFromInsets.right < 0
-                || mFromInsets.bottom < 0;
+    @WindowInsets.Side.InsetsSide
+    public int getExtensionEdges() {
+        return (mFromInsets.left < 0 || mToInsets.left < 0 ?  WindowInsets.Side.LEFT : 0)
+            | (mFromInsets.right < 0 || mToInsets.right < 0 ?  WindowInsets.Side.RIGHT : 0)
+            | (mFromInsets.top < 0 || mToInsets.top < 0 ?  WindowInsets.Side.TOP : 0)
+            | (mFromInsets.bottom < 0 || mToInsets.bottom < 0 ? WindowInsets.Side.BOTTOM : 0);
     }
 
     @Override
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 4ab6758..b281015 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -2887,6 +2887,11 @@
      * initiation delegation was previously requested using
      * {@link #prepareStylusHandwritingDelegation(View)} from the delegator.
      *
+     * <p>Otherwise, if the delegator view previously started delegation using {@link
+     * #startConnectionlessStylusHandwritingForDelegation(View, CursorAnchorInfo, Executor,
+     * ConnectionlessHandwritingCallback)}, requests the IME to commit the recognised handwritten
+     * text from the connectionless session to the delegate view.
+     *
      * <p>Note: If delegator and delegate are in different application packages, use
      * {@link #acceptStylusHandwritingDelegation(View, String)} instead.</p>
      *
@@ -2895,14 +2900,9 @@
      *  {@link #prepareStylusHandwritingDelegation(View)} and delegation is accepted
      * @see #prepareStylusHandwritingDelegation(View)
      * @see #acceptStylusHandwritingDelegation(View, String)
+     * @see #startConnectionlessStylusHandwritingForDelegation(View, CursorAnchorInfo, Executor,
+     *     ConnectionlessHandwritingCallback)
      */
-    // TODO(b/300979854): Once connectionless APIs are finalised, update documentation to add:
-    // <p>Otherwise, if the delegator view previously started delegation using {@link
-    // #startConnectionlessStylusHandwritingForDelegation(View, ResultReceiver, CursorAnchorInfo)},
-    // requests the IME to commit the recognised handwritten text from the connectionless session to
-    // the delegate view.
-    // @see #startConnectionlessStylusHandwritingForDelegation(View, ResultReceiver,
-    //     CursorAnchorInfo)
     public boolean acceptStylusHandwritingDelegation(@NonNull View delegateView) {
         return startStylusHandwritingInternal(
                 delegateView, delegateView.getContext().getOpPackageName(),
@@ -2915,6 +2915,11 @@
      * {@link #prepareStylusHandwritingDelegation(View, String)} from the delegator and the view
      * belongs to a specified delegate package.
      *
+     * <p>Otherwise, if the delegator view previously started delegation using {@link
+     * #startConnectionlessStylusHandwritingForDelegation(View, CursorAnchorInfo, String, Executor,
+     * ConnectionlessHandwritingCallback)}, requests the IME to commit the recognised handwritten
+     * text from the connectionless session to the delegate view.
+     *
      * <p>Note: If delegator and delegate are in the same application package, use
      * {@link #acceptStylusHandwritingDelegation(View)} instead.</p>
      *
@@ -2924,15 +2929,10 @@
      *     #prepareStylusHandwritingDelegation(View, String)} and delegation is accepted
      * @see #prepareStylusHandwritingDelegation(View, String)
      * @see #acceptStylusHandwritingDelegation(View)
+     * @see #startConnectionlessStylusHandwritingForDelegation(View, CursorAnchorInfo, String,
+     *     Executor, ConnectionlessHandwritingCallback)
      * TODO (b/293640003): deprecate this method once flag is enabled.
      */
-    // TODO(b/300979854): Once connectionless APIs are finalised, update documentation to add:
-    // <p>Otherwise, if the delegator view previously started delegation using {@link
-    // #startConnectionlessStylusHandwritingForDelegation(View, ResultReceiver, CursorAnchorInfo,
-    // String)}, requests the IME to commit the recognised handwritten text from the connectionless
-    // session to the delegate view.
-    // @see #startConnectionlessStylusHandwritingForDelegation(View, ResultReceiver,
-    //     CursorAnchorInfo, String)
     public boolean acceptStylusHandwritingDelegation(
             @NonNull View delegateView, @NonNull String delegatorPackageName) {
         Objects.requireNonNull(delegatorPackageName);
@@ -2946,6 +2946,11 @@
      * {@link #prepareStylusHandwritingDelegation(View, String)} from the delegator and the view
      * belongs to a specified delegate package.
      *
+     * <p>Otherwise, if the delegator view previously started delegation using {@link
+     * #startConnectionlessStylusHandwritingForDelegation(View, CursorAnchorInfo, String, Executor,
+     * ConnectionlessHandwritingCallback)}, requests the IME to commit the recognised handwritten
+     * text from the connectionless session to the delegate view.
+     *
      * @param delegateView delegate view capable of receiving input via {@link InputConnection}
      *  on which {@link #startStylusHandwriting(View)} will be called.
      * @param delegatorPackageName package name of the delegator that handled initial stylus stroke.
@@ -2957,6 +2962,8 @@
      *                The framework only holds a weak reference.
      * @see #prepareStylusHandwritingDelegation(View, String)
      * @see #acceptStylusHandwritingDelegation(View)
+     * @see #startConnectionlessStylusHandwritingForDelegation(View, CursorAnchorInfo, String,
+     *     Executor, ConnectionlessHandwritingCallback)
      */
     @FlaggedApi(Flags.FLAG_USE_ZERO_JANK_PROXY)
     public void acceptStylusHandwritingDelegation(
@@ -2977,6 +2984,11 @@
      * #prepareStylusHandwritingDelegation(View, String)} from the delegator and the view belongs to
      * a specified delegate package.
      *
+     * <p>Otherwise, if the delegator view previously started delegation using {@link
+     * #startConnectionlessStylusHandwritingForDelegation(View, CursorAnchorInfo, String, Executor,
+     * ConnectionlessHandwritingCallback)}, requests the IME to commit the recognised handwritten
+     * text from the connectionless session to the delegate view.
+     *
      * <p>Note: If delegator and delegate are in the same application package, use {@link
      * #acceptStylusHandwritingDelegation(View)} instead.
      *
@@ -2988,15 +3000,9 @@
      *                 hold a reference to the callback. The framework only holds a weak reference.
      * @see #prepareStylusHandwritingDelegation(View, String)
      * @see #acceptStylusHandwritingDelegation(View)
+     * @see #startConnectionlessStylusHandwritingForDelegation(View, CursorAnchorInfo, String,
+     *     Executor, ConnectionlessHandwritingCallback)
      */
-    // TODO(b/300979854): Once connectionless APIs are finalised, update documentation to add:
-    // <p>Otherwise, if the delegator view previously started delegation using {@link
-    // #startConnectionlessStylusHandwritingForDelegation(View, ResultReceiver, CursorAnchorInfo,
-    // String)}, requests the IME to commit the recognised handwritten text from the connectionless
-    // session to the delegate view.
-    // @see #startConnectionlessStylusHandwritingForDelegation(View, ResultReceiver,
-    //     CursorAnchorInfo, String)
-    //
     @FlaggedApi(FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR)
     public void acceptStylusHandwritingDelegation(
             @NonNull View delegateView, @NonNull String delegatorPackageName,
diff --git a/core/java/android/widget/Chronometer.java b/core/java/android/widget/Chronometer.java
index 0b67cad..9931aea 100644
--- a/core/java/android/widget/Chronometer.java
+++ b/core/java/android/widget/Chronometer.java
@@ -328,7 +328,7 @@
             if (running) {
                 updateText(SystemClock.elapsedRealtime());
                 dispatchChronometerTick();
-                postDelayed(mTickRunnable, 1000);
+                postTickOnNextSecond();
             } else {
                 removeCallbacks(mTickRunnable);
             }
@@ -342,11 +342,17 @@
             if (mRunning) {
                 updateText(SystemClock.elapsedRealtime());
                 dispatchChronometerTick();
-                postDelayed(mTickRunnable, 1000);
+                postTickOnNextSecond();
             }
         }
     };
 
+    private void postTickOnNextSecond() {
+        long nowMillis = SystemClock.elapsedRealtime();
+        int millis = (int) ((nowMillis - mBase) % 1000);
+        postDelayed(mTickRunnable, 1000 - millis);
+    }
+
     void dispatchChronometerTick() {
         if (mOnChronometerTickListener != null) {
             mOnChronometerTickListener.onChronometerTick(this);
diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java
index 63f8ee7..ed6ec32 100644
--- a/core/java/android/widget/CompoundButton.java
+++ b/core/java/android/widget/CompoundButton.java
@@ -267,7 +267,7 @@
          * @param buttonView The compound button view whose state has changed.
          * @param isChecked  The new checked state of buttonView.
          */
-        void onCheckedChanged(CompoundButton buttonView, boolean isChecked);
+        void onCheckedChanged(@NonNull CompoundButton buttonView, boolean isChecked);
     }
 
     /**
diff --git a/core/java/android/widget/RadioGroup.java b/core/java/android/widget/RadioGroup.java
index d445fdc..70fe6d5 100644
--- a/core/java/android/widget/RadioGroup.java
+++ b/core/java/android/widget/RadioGroup.java
@@ -366,7 +366,7 @@
          * @param group the group in which the checked radio button has changed
          * @param checkedId the unique identifier of the newly checked radio button
          */
-        public void onCheckedChanged(RadioGroup group, @IdRes int checkedId);
+        void onCheckedChanged(@NonNull RadioGroup group, @IdRes int checkedId);
     }
 
     private class CheckedStateTracker implements CompoundButton.OnCheckedChangeListener {
diff --git a/core/java/android/widget/RemoteViewsSerializers.java b/core/java/android/widget/RemoteViewsSerializers.java
index 600fea4..080f22e 100644
--- a/core/java/android/widget/RemoteViewsSerializers.java
+++ b/core/java/android/widget/RemoteViewsSerializers.java
@@ -15,12 +15,55 @@
  */
 package android.widget;
 
+import static com.android.text.flags.Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN;
+import static com.android.text.flags.Flags.noBreakNoHyphenationSpan;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import android.annotation.FlaggedApi;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.BlendMode;
 import android.graphics.drawable.Icon;
+import android.graphics.text.LineBreakConfig;
+import android.os.LocaleList;
+import android.os.PersistableBundle;
+import android.text.Annotation;
+import android.text.Layout;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.style.AbsoluteSizeSpan;
+import android.text.style.AccessibilityClickableSpan;
+import android.text.style.AccessibilityReplacementSpan;
+import android.text.style.AccessibilityURLSpan;
+import android.text.style.AlignmentSpan;
+import android.text.style.BackgroundColorSpan;
+import android.text.style.BulletSpan;
+import android.text.style.CharacterStyle;
+import android.text.style.EasyEditSpan;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.LeadingMarginSpan;
+import android.text.style.LineBackgroundSpan;
+import android.text.style.LineBreakConfigSpan;
+import android.text.style.LineHeightSpan;
+import android.text.style.LocaleSpan;
+import android.text.style.QuoteSpan;
+import android.text.style.RelativeSizeSpan;
+import android.text.style.ScaleXSpan;
+import android.text.style.SpellCheckSpan;
+import android.text.style.StrikethroughSpan;
+import android.text.style.StyleSpan;
+import android.text.style.SubscriptSpan;
+import android.text.style.SuggestionRangeSpan;
+import android.text.style.SuggestionSpan;
+import android.text.style.SuperscriptSpan;
+import android.text.style.TextAppearanceSpan;
+import android.text.style.TtsSpan;
+import android.text.style.TypefaceSpan;
+import android.text.style.URLSpan;
+import android.text.style.UnderlineSpan;
 import android.util.Log;
 import android.util.LongSparseArray;
 import android.util.proto.ProtoInputStream;
@@ -29,7 +72,11 @@
 
 import androidx.annotation.NonNull;
 
+import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.function.Function;
 
 /**
@@ -59,12 +106,13 @@
                 break;
             case Icon.TYPE_ADAPTIVE_BITMAP:
                 final ByteArrayOutputStream adaptiveBitmapBytes = new ByteArrayOutputStream();
-                icon.getBitmap().compress(Bitmap.CompressFormat.WEBP_LOSSLESS, 100,
-                        adaptiveBitmapBytes);
+                icon.getBitmap()
+                        .compress(Bitmap.CompressFormat.WEBP_LOSSLESS, 100, adaptiveBitmapBytes);
                 out.write(RemoteViewsProto.Icon.ADAPTIVE_BITMAP, adaptiveBitmapBytes.toByteArray());
                 break;
             case Icon.TYPE_RESOURCE:
-                out.write(RemoteViewsProto.Icon.RESOURCE,
+                out.write(
+                        RemoteViewsProto.Icon.RESOURCE,
                         appResources.getResourceName(icon.getResId()));
                 break;
             case Icon.TYPE_DATA:
@@ -91,7 +139,8 @@
         while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
             switch (in.getFieldNumber()) {
                 case (int) RemoteViewsProto.Icon.BLEND_MODE:
-                    values.put(RemoteViewsProto.Icon.BLEND_MODE,
+                    values.put(
+                            RemoteViewsProto.Icon.BLEND_MODE,
                             in.readInt(RemoteViewsProto.Icon.BLEND_MODE));
                     break;
                 case (int) RemoteViewsProto.Icon.TINT_LIST:
@@ -101,7 +150,8 @@
                     break;
                 case (int) RemoteViewsProto.Icon.BITMAP:
                     byte[] bitmapData = in.readBytes(RemoteViewsProto.Icon.BITMAP);
-                    values.put(RemoteViewsProto.Icon.BITMAP,
+                    values.put(
+                            RemoteViewsProto.Icon.BITMAP,
                             BitmapFactory.decodeByteArray(bitmapData, 0, bitmapData.length));
                     break;
                 case (int) RemoteViewsProto.Icon.ADAPTIVE_BITMAP:
@@ -112,23 +162,27 @@
                                     bitmapAdaptiveData.length));
                     break;
                 case (int) RemoteViewsProto.Icon.RESOURCE:
-                    values.put(RemoteViewsProto.Icon.RESOURCE,
+                    values.put(
+                            RemoteViewsProto.Icon.RESOURCE,
                             in.readString(RemoteViewsProto.Icon.RESOURCE));
                     break;
                 case (int) RemoteViewsProto.Icon.DATA:
-                    values.put(RemoteViewsProto.Icon.DATA,
-                            in.readBytes(RemoteViewsProto.Icon.DATA));
+                    values.put(
+                            RemoteViewsProto.Icon.DATA, in.readBytes(RemoteViewsProto.Icon.DATA));
                     break;
                 case (int) RemoteViewsProto.Icon.URI:
                     values.put(RemoteViewsProto.Icon.URI, in.readString(RemoteViewsProto.Icon.URI));
                     break;
                 case (int) RemoteViewsProto.Icon.URI_ADAPTIVE_BITMAP:
-                    values.put(RemoteViewsProto.Icon.URI_ADAPTIVE_BITMAP,
+                    values.put(
+                            RemoteViewsProto.Icon.URI_ADAPTIVE_BITMAP,
                             in.readString(RemoteViewsProto.Icon.URI_ADAPTIVE_BITMAP));
                     break;
                 default:
-                    Log.w(TAG, "Unhandled field while reading Icon proto!\n"
-                            + ProtoUtils.currentFieldToString(in));
+                    Log.w(
+                            TAG,
+                            "Unhandled field while reading Icon proto!\n"
+                                    + ProtoUtils.currentFieldToString(in));
             }
         }
 
@@ -174,4 +228,1279 @@
             return icon;
         };
     }
+
+    public static void writeCharSequenceToProto(@NonNull ProtoOutputStream out,
+            @NonNull CharSequence cs) {
+        out.write(RemoteViewsProto.CharSequence.TEXT, cs.toString());
+        if (!(cs instanceof Spanned sp)) return;
+
+        Object[] os = sp.getSpans(0, cs.length(), Object.class);
+        for (Object original : os) {
+            Object prop = original;
+            if (prop instanceof CharacterStyle) {
+                prop = ((CharacterStyle) prop).getUnderlying();
+            }
+
+            final long spansToken = out.start(RemoteViewsProto.CharSequence.SPANS);
+            out.write(RemoteViewsProto.CharSequence.Span.START, sp.getSpanStart(original));
+            out.write(RemoteViewsProto.CharSequence.Span.END, sp.getSpanEnd(original));
+            out.write(RemoteViewsProto.CharSequence.Span.FLAGS, sp.getSpanFlags(original));
+
+            if (prop instanceof AbsoluteSizeSpan span) {
+                final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.ABSOLUTE_SIZE);
+                writeAbsoluteSizeSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof AccessibilityClickableSpan span) {
+                final long spanToken = out.start(
+                        RemoteViewsProto.CharSequence.Span.ACCESSIBILITY_CLICKABLE);
+                writeAccessibilityClickableSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof AccessibilityReplacementSpan span) {
+                final long spanToken = out.start(
+                        RemoteViewsProto.CharSequence.Span.ACCESSIBILITY_REPLACEMENT);
+                writeAccessibilityReplacementSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof AccessibilityURLSpan span) {
+                final long spanToken = out.start(
+                        RemoteViewsProto.CharSequence.Span.ACCESSIBILITY_URL);
+                writeAccessibilityURLSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof Annotation span) {
+                final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.ANNOTATION);
+                writeAnnotationToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof BackgroundColorSpan span) {
+                final long spanToken = out.start(
+                        RemoteViewsProto.CharSequence.Span.BACKGROUND_COLOR);
+                writeBackgroundColorSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof BulletSpan span) {
+                final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.BULLET);
+                writeBulletSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof EasyEditSpan span) {
+                final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.EASY_EDIT);
+                writeEasyEditSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof ForegroundColorSpan span) {
+                final long spanToken = out.start(
+                        RemoteViewsProto.CharSequence.Span.FOREGROUND_COLOR);
+                writeForegroundColorSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (noBreakNoHyphenationSpan() && prop instanceof LineBreakConfigSpan span) {
+                final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.LINE_BREAK);
+                writeLineBreakConfigSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof LocaleSpan span) {
+                final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.LOCALE);
+                writeLocaleSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof QuoteSpan span) {
+                final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.QUOTE);
+                writeQuoteSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof RelativeSizeSpan span) {
+                final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.RELATIVE_SIZE);
+                writeRelativeSizeSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof ScaleXSpan span) {
+                final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.SCALE_X);
+                writeScaleXSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof SpellCheckSpan span) {
+                final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.SPELL_CHECK);
+                writeSpellCheckSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof LineBackgroundSpan.Standard span) {
+                final long spanToken = out.start(
+                        RemoteViewsProto.CharSequence.Span.LINE_BACKGROUND);
+                writeLineBackgroundSpanStandardToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof LineHeightSpan.Standard span) {
+                final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.LINE_HEIGHT);
+                writeLineHeightSpanStandardToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof LeadingMarginSpan.Standard span) {
+                final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.LEADING_MARGIN);
+                writeLeadingMarginSpanStandardToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof AlignmentSpan.Standard span) {
+                final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.ALIGNMENT);
+                writeAlignmentSpanStandardToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof StrikethroughSpan span) {
+                final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.STRIKETHROUGH);
+                writeStrikethroughSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof StyleSpan span) {
+                final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.STYLE);
+                writeStyleSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof SubscriptSpan span) {
+                final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.SUBSCRIPT);
+                writeSubscriptSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof SuggestionRangeSpan span) {
+                final long spanToken = out.start(
+                        RemoteViewsProto.CharSequence.Span.SUGGESTION_RANGE);
+                writeSuggestionRangeSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof SuggestionSpan span) {
+                final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.SUGGESTION);
+                writeSuggestionSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof SuperscriptSpan span) {
+                final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.SUPERSCRIPT);
+                writeSuperscriptSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof TextAppearanceSpan span) {
+                final long spanToken = out.start(
+                        RemoteViewsProto.CharSequence.Span.TEXT_APPEARANCE);
+                writeTextAppearanceSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof TtsSpan span) {
+                final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.TTS);
+                writeTtsSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof TypefaceSpan span) {
+                final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.TYPEFACE);
+                writeTypefaceSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof URLSpan span) {
+                final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.URL);
+                writeURLSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof UnderlineSpan span) {
+                final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.UNDERLINE);
+                writeUnderlineSpanToProto(out, span);
+                out.end(spanToken);
+            }
+            out.end(spansToken);
+        }
+    }
+
+    public static CharSequence createCharSequenceFromProto(ProtoInputStream in) throws Exception {
+        SpannableStringBuilder builder = new SpannableStringBuilder();
+        boolean hasSpans = false;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.TEXT:
+                    String text = in.readString(RemoteViewsProto.CharSequence.TEXT);
+                    builder.append(text);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.SPANS:
+                    hasSpans = true;
+                    final long spansToken = in.start(RemoteViewsProto.CharSequence.SPANS);
+                    createSpanFromProto(in, builder);
+                    in.end(spansToken);
+                    break;
+                default:
+                    Log.w(TAG, "Unhandled field while reading CharSequence proto!\n"
+                            + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        return hasSpans ? builder : builder.toString();
+    }
+
+    private static void createSpanFromProto(ProtoInputStream in, SpannableStringBuilder builder)
+            throws Exception {
+        int start = 0;
+        int end = 0;
+        int flags = 0;
+        Object what = null;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span.START:
+                    start = in.readInt(RemoteViewsProto.CharSequence.Span.START);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.END:
+                    end = in.readInt(RemoteViewsProto.CharSequence.Span.END);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.FLAGS:
+                    flags = in.readInt(RemoteViewsProto.CharSequence.Span.FLAGS);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.ABSOLUTE_SIZE:
+                    final long asToken = in.start(RemoteViewsProto.CharSequence.Span.ABSOLUTE_SIZE);
+                    what = createAbsoluteSizeSpanFromProto(in);
+                    in.end(asToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.ACCESSIBILITY_CLICKABLE:
+                    final long acToken = in.start(
+                            RemoteViewsProto.CharSequence.Span.ACCESSIBILITY_CLICKABLE);
+                    what = createAccessibilityClickableSpanFromProto(in);
+                    in.end(acToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.ACCESSIBILITY_REPLACEMENT:
+                    final long arToken = in.start(
+                            RemoteViewsProto.CharSequence.Span.ACCESSIBILITY_REPLACEMENT);
+                    what = createAccessibilityReplacementSpanFromProto(in);
+                    in.end(arToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.ACCESSIBILITY_URL:
+                    final long auToken = in.start(
+                            RemoteViewsProto.CharSequence.Span.ACCESSIBILITY_URL);
+                    what = createAccessibilityURLSpanFromProto(in);
+                    in.end(auToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.ALIGNMENT:
+                    final long aToken = in.start(RemoteViewsProto.CharSequence.Span.ALIGNMENT);
+                    what = createAlignmentSpanStandardFromProto(in);
+                    in.end(aToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.ANNOTATION:
+                    final long annToken = in.start(RemoteViewsProto.CharSequence.Span.ANNOTATION);
+                    what = createAnnotationFromProto(in);
+                    in.end(annToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.BACKGROUND_COLOR:
+                    final long bcToken = in.start(
+                            RemoteViewsProto.CharSequence.Span.BACKGROUND_COLOR);
+                    what = createBackgroundColorSpanFromProto(in);
+                    in.end(bcToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.BULLET:
+                    final long bToken = in.start(RemoteViewsProto.CharSequence.Span.BULLET);
+                    what = createBulletSpanFromProto(in);
+                    in.end(bToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.EASY_EDIT:
+                    final long eeToken = in.start(RemoteViewsProto.CharSequence.Span.EASY_EDIT);
+                    what = createEasyEditSpanFromProto(in);
+                    in.end(eeToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.FOREGROUND_COLOR:
+                    final long fcToken = in.start(
+                            RemoteViewsProto.CharSequence.Span.FOREGROUND_COLOR);
+                    what = createForegroundColorSpanFromProto(in);
+                    in.end(fcToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.LEADING_MARGIN:
+                    final long lmToken = in.start(
+                            RemoteViewsProto.CharSequence.Span.LEADING_MARGIN);
+                    what = createLeadingMarginSpanStandardFromProto(in);
+                    in.end(lmToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.LINE_BACKGROUND:
+                    final long lbToken = in.start(
+                            RemoteViewsProto.CharSequence.Span.LINE_BACKGROUND);
+                    what = createLineBackgroundSpanStandardFromProto(in);
+                    in.end(lbToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.LINE_BREAK:
+                    if (!noBreakNoHyphenationSpan()) {
+                        continue;
+                    }
+                    final long lbrToken = in.start(RemoteViewsProto.CharSequence.Span.LINE_BREAK);
+                    what = createLineBreakConfigSpanFromProto(in);
+                    in.end(lbrToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.LINE_HEIGHT:
+                    final long lhToken = in.start(RemoteViewsProto.CharSequence.Span.LINE_HEIGHT);
+                    what = createLineHeightSpanStandardFromProto(in);
+                    in.end(lhToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.LOCALE:
+                    final long lToken = in.start(RemoteViewsProto.CharSequence.Span.LOCALE);
+                    what = createLocaleSpanFromProto(in);
+                    in.end(lToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.QUOTE:
+                    final long qToken = in.start(RemoteViewsProto.CharSequence.Span.QUOTE);
+                    what = createQuoteSpanFromProto(in);
+                    in.end(qToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.RELATIVE_SIZE:
+                    final long rsToken = in.start(RemoteViewsProto.CharSequence.Span.RELATIVE_SIZE);
+                    what = createRelativeSizeSpanFromProto(in);
+                    in.end(rsToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.SCALE_X:
+                    final long sxToken = in.start(RemoteViewsProto.CharSequence.Span.SCALE_X);
+                    what = createScaleXSpanFromProto(in);
+                    in.end(sxToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.SPELL_CHECK:
+                    final long scToken = in.start(RemoteViewsProto.CharSequence.Span.SPELL_CHECK);
+                    what = createSpellCheckSpanFromProto(in);
+                    in.end(scToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.STRIKETHROUGH:
+                    final long stToken = in.start(RemoteViewsProto.CharSequence.Span.STRIKETHROUGH);
+                    what = createStrikethroughSpanFromProto(in);
+                    in.end(stToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.STYLE:
+                    final long sToken = in.start(RemoteViewsProto.CharSequence.Span.STYLE);
+                    what = createStyleSpanFromProto(in);
+                    in.end(sToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.SUBSCRIPT:
+                    final long suToken = in.start(RemoteViewsProto.CharSequence.Span.SUBSCRIPT);
+                    what = createSubscriptSpanFromProto(in);
+                    in.end(suToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.SUGGESTION_RANGE:
+                    final long srToken = in.start(
+                            RemoteViewsProto.CharSequence.Span.SUGGESTION_RANGE);
+                    what = createSuggestionRangeSpanFromProto(in);
+                    in.end(srToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.SUGGESTION:
+                    final long sugToken = in.start(RemoteViewsProto.CharSequence.Span.SUGGESTION);
+                    what = createSuggestionSpanFromProto(in);
+                    in.end(sugToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.SUPERSCRIPT:
+                    final long supToken = in.start(RemoteViewsProto.CharSequence.Span.SUPERSCRIPT);
+                    what = createSuperscriptSpanFromProto(in);
+                    in.end(supToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.TEXT_APPEARANCE:
+                    final long taToken = in.start(
+                            RemoteViewsProto.CharSequence.Span.TEXT_APPEARANCE);
+                    what = createTextAppearanceSpanFromProto(in);
+                    in.end(taToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.TTS:
+                    final long ttsToken = in.start(RemoteViewsProto.CharSequence.Span.TTS);
+                    what = createTtsSpanFromProto(in);
+                    in.end(ttsToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.TYPEFACE:
+                    final long tfToken = in.start(RemoteViewsProto.CharSequence.Span.TYPEFACE);
+                    what = createTypefaceSpanFromProto(in);
+                    in.end(tfToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.UNDERLINE:
+                    final long unToken = in.start(RemoteViewsProto.CharSequence.Span.UNDERLINE);
+                    what = createUnderlineSpanFromProto(in);
+                    in.end(unToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.URL:
+                    final long urlToken = in.start(RemoteViewsProto.CharSequence.Span.URL);
+                    what = createURLSpanFromProto(in);
+                    in.end(urlToken);
+                    break;
+                default:
+                    Log.w(TAG, "Unhandled field while reading CharSequence proto!\n"
+                            + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        if (what == null) {
+            return;
+        }
+        builder.setSpan(what, start, end, flags);
+    }
+
+    public static AbsoluteSizeSpan createAbsoluteSizeSpanFromProto(@NonNull ProtoInputStream in)
+            throws Exception {
+        int size = 0;
+        boolean dip = false;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span.AbsoluteSize.SIZE:
+                    size = in.readInt(RemoteViewsProto.CharSequence.Span.AbsoluteSize.SIZE);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.AbsoluteSize.DIP:
+                    dip = in.readBoolean(RemoteViewsProto.CharSequence.Span.AbsoluteSize.DIP);
+                    break;
+                default:
+                    Log.w("AbsoluteSizeSpan",
+                            "Unhandled field while reading AbsoluteSizeSpan " + "proto!\n"
+                                    + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        return new AbsoluteSizeSpan(size, dip);
+    }
+
+    public static void writeAbsoluteSizeSpanToProto(@NonNull ProtoOutputStream out,
+            AbsoluteSizeSpan span) {
+        out.write(RemoteViewsProto.CharSequence.Span.AbsoluteSize.SIZE, span.getSize());
+        out.write(RemoteViewsProto.CharSequence.Span.AbsoluteSize.DIP, span.getDip());
+    }
+
+    public static AccessibilityClickableSpan createAccessibilityClickableSpanFromProto(
+            @NonNull ProtoInputStream in) throws Exception {
+        int originalClickableSpanId = 0;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span
+                        .AccessibilityClickable.ORIGINAL_CLICKABLE_SPAN_ID:
+                    originalClickableSpanId = in.readInt(
+                            RemoteViewsProto.CharSequence.Span
+                                    .AccessibilityClickable.ORIGINAL_CLICKABLE_SPAN_ID);
+                    break;
+                default:
+                    Log.w("AccessibilityClickable",
+                            "Unhandled field while reading" + " AccessibilityClickableSpan proto!\n"
+                                    + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        return new AccessibilityClickableSpan(originalClickableSpanId);
+    }
+
+    public static void writeAccessibilityClickableSpanToProto(@NonNull ProtoOutputStream out,
+            AccessibilityClickableSpan span) {
+        out.write(
+                RemoteViewsProto.CharSequence.Span
+                        .AccessibilityClickable.ORIGINAL_CLICKABLE_SPAN_ID,
+                span.getOriginalClickableSpanId());
+    }
+
+    public static AccessibilityReplacementSpan createAccessibilityReplacementSpanFromProto(
+            @NonNull ProtoInputStream in) throws Exception {
+        CharSequence contentDescription = null;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span
+                        .AccessibilityReplacement.CONTENT_DESCRIPTION:
+                    final long token = in.start(
+                            RemoteViewsProto.CharSequence.Span
+                                    .AccessibilityReplacement.CONTENT_DESCRIPTION);
+                    contentDescription = createCharSequenceFromProto(in);
+                    in.end(token);
+                    break;
+                default:
+                    Log.w("AccessibilityReplacemen", "Unhandled field while reading"
+                            + " AccessibilityReplacementSpan proto!\n"
+                            + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        return new AccessibilityReplacementSpan(contentDescription);
+    }
+
+    public static void writeAccessibilityReplacementSpanToProto(@NonNull ProtoOutputStream out,
+            AccessibilityReplacementSpan span) {
+        final long token = out.start(
+                RemoteViewsProto.CharSequence.Span.AccessibilityReplacement.CONTENT_DESCRIPTION);
+        CharSequence description = span.getContentDescription();
+        if (description != null) {
+            writeCharSequenceToProto(out, description);
+        }
+        out.end(token);
+    }
+
+    public static AccessibilityURLSpan createAccessibilityURLSpanFromProto(ProtoInputStream in)
+            throws Exception {
+        String url = null;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span.AccessibilityUrl.URL:
+                    url = in.readString(RemoteViewsProto.CharSequence.Span.AccessibilityUrl.URL);
+                    break;
+                default:
+                    Log.w("AccessibilityURLSpan",
+                            "Unhandled field while reading AccessibilityURLSpan " + "proto!\n"
+                                    + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        return new AccessibilityURLSpan(new URLSpan(url));
+    }
+
+    public static void writeAccessibilityURLSpanToProto(@NonNull ProtoOutputStream out,
+            AccessibilityURLSpan span) {
+        out.write(RemoteViewsProto.CharSequence.Span.AccessibilityUrl.URL, span.getURL());
+    }
+
+    public static AlignmentSpan.Standard createAlignmentSpanStandardFromProto(
+            @NonNull ProtoInputStream in) throws Exception {
+        String alignment = null;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span.Alignment.ALIGNMENT:
+                    alignment = in.readString(
+                            RemoteViewsProto.CharSequence.Span.Alignment.ALIGNMENT);
+                    break;
+                default:
+                    Log.w("AlignmentSpan",
+                            "Unhandled field while reading AlignmentSpan " + "proto!\n"
+                                    + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        return new AlignmentSpan.Standard(Layout.Alignment.valueOf(alignment));
+    }
+
+    public static void writeAlignmentSpanStandardToProto(@NonNull ProtoOutputStream out,
+            AlignmentSpan.Standard span) {
+        out.write(RemoteViewsProto.CharSequence.Span.Alignment.ALIGNMENT,
+                span.getAlignment().name());
+    }
+
+    public static Annotation createAnnotationFromProto(@NonNull ProtoInputStream in)
+            throws Exception {
+        String key = null;
+        String value = null;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span.Annotation.KEY:
+                    key = in.readString(RemoteViewsProto.CharSequence.Span.Annotation.KEY);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.Annotation.VALUE:
+                    value = in.readString(RemoteViewsProto.CharSequence.Span.Annotation.VALUE);
+                    break;
+                default:
+                    Log.w("Annotation", "Unhandled field while reading" + " Annotation proto!\n"
+                            + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        return new Annotation(key, value);
+    }
+
+    public static void writeAnnotationToProto(@NonNull ProtoOutputStream out, Annotation span) {
+        out.write(RemoteViewsProto.CharSequence.Span.Annotation.KEY, span.getKey());
+        out.write(RemoteViewsProto.CharSequence.Span.Annotation.VALUE, span.getValue());
+    }
+
+    public static BackgroundColorSpan createBackgroundColorSpanFromProto(
+            @NonNull ProtoInputStream in) throws Exception {
+        int color = 0;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span.BackgroundColor.COLOR:
+                    color = in.readInt(RemoteViewsProto.CharSequence.Span.BackgroundColor.COLOR);
+                    break;
+                default:
+                    Log.w("BackgroundColorSpan",
+                            "Unhandled field while reading" + " BackgroundColorSpan proto!\n"
+                                    + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        return new BackgroundColorSpan(color);
+    }
+
+    public static void writeBackgroundColorSpanToProto(@NonNull ProtoOutputStream out,
+            BackgroundColorSpan span) {
+        out.write(RemoteViewsProto.CharSequence.Span.BackgroundColor.COLOR,
+                span.getBackgroundColor());
+    }
+
+    public static BulletSpan createBulletSpanFromProto(@NonNull ProtoInputStream in)
+            throws Exception {
+        int bulletRadius = 0;
+        int color = 0;
+        int gapWidth = 0;
+        boolean wantColor = false;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span.Bullet.BULLET_RADIUS:
+                    bulletRadius = in.readInt(
+                            RemoteViewsProto.CharSequence.Span.Bullet.BULLET_RADIUS);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.Bullet.COLOR:
+                    color = in.readInt(RemoteViewsProto.CharSequence.Span.Bullet.COLOR);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.Bullet.GAP_WIDTH:
+                    gapWidth = in.readInt(RemoteViewsProto.CharSequence.Span.Bullet.GAP_WIDTH);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.Bullet.WANT_COLOR:
+                    wantColor = in.readBoolean(
+                            RemoteViewsProto.CharSequence.Span.Bullet.WANT_COLOR);
+                    break;
+                default:
+                    Log.w("BulletSpan", "Unhandled field while reading BulletSpan " + "proto!\n"
+                            + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        return new BulletSpan(gapWidth, color, wantColor, bulletRadius);
+    }
+
+    public static void writeBulletSpanToProto(@NonNull ProtoOutputStream out, BulletSpan span) {
+        out.write(RemoteViewsProto.CharSequence.Span.Bullet.BULLET_RADIUS, span.getBulletRadius());
+        out.write(RemoteViewsProto.CharSequence.Span.Bullet.COLOR, span.getColor());
+        out.write(RemoteViewsProto.CharSequence.Span.Bullet.GAP_WIDTH, span.getGapWidth());
+        out.write(RemoteViewsProto.CharSequence.Span.Bullet.WANT_COLOR, span.getWantColor());
+    }
+
+    public static EasyEditSpan createEasyEditSpanFromProto(@NonNull ProtoInputStream in)
+            throws Exception {
+        return new EasyEditSpan();
+    }
+
+    public static void writeEasyEditSpanToProto(@NonNull ProtoOutputStream out, EasyEditSpan span) {
+    }
+
+    public static ForegroundColorSpan createForegroundColorSpanFromProto(
+            @NonNull ProtoInputStream in) throws Exception {
+        int color = 0;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span.BackgroundColor.COLOR:
+                    color = in.readInt(RemoteViewsProto.CharSequence.Span.BackgroundColor.COLOR);
+                    break;
+                default:
+                    Log.w("ForegroundColorSpan",
+                            "Unhandled field while reading" + " ForegroundColorSpan proto!\n"
+                                    + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        return new ForegroundColorSpan(color);
+    }
+
+    public static LeadingMarginSpan.Standard createLeadingMarginSpanStandardFromProto(
+            @NonNull ProtoInputStream in) throws Exception {
+        int first = 0;
+        int rest = 0;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span.LeadingMargin.FIRST:
+                    first = in.readInt(RemoteViewsProto.CharSequence.Span.LeadingMargin.FIRST);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.LeadingMargin.REST:
+                    rest = in.readInt(RemoteViewsProto.CharSequence.Span.LeadingMargin.REST);
+                    break;
+                default:
+                    Log.w("LeadingMarginSpan",
+                            "Unhandled field while reading LeadingMarginSpan" + "proto!\n"
+                                    + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        return new LeadingMarginSpan.Standard(first, rest);
+    }
+
+    public static void writeLeadingMarginSpanStandardToProto(@NonNull ProtoOutputStream out,
+            LeadingMarginSpan.Standard span) {
+        out.write(RemoteViewsProto.CharSequence.Span.LeadingMargin.FIRST,
+                span.getLeadingMargin(/* first= */ true));
+        out.write(RemoteViewsProto.CharSequence.Span.LeadingMargin.REST,
+                span.getLeadingMargin(/* first= */ false));
+    }
+
+    public static void writeForegroundColorSpanToProto(@NonNull ProtoOutputStream out,
+            ForegroundColorSpan span) {
+        out.write(RemoteViewsProto.CharSequence.Span.ForegroundColor.COLOR,
+                span.getForegroundColor());
+    }
+
+    public static LineBackgroundSpan.Standard createLineBackgroundSpanStandardFromProto(
+            @NonNull ProtoInputStream in) throws Exception {
+        int color = 0;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span.LineBackground.COLOR:
+                    color = in.readInt(RemoteViewsProto.CharSequence.Span.LineBackground.COLOR);
+                    break;
+                default:
+                    Log.w("LineBackgroundSpan",
+                            "Unhandled field while reading" + " LineBackgroundSpan proto!\n"
+                                    + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        return new LineBackgroundSpan.Standard(color);
+    }
+
+    public static void writeLineBackgroundSpanStandardToProto(@NonNull ProtoOutputStream out,
+            LineBackgroundSpan.Standard span) {
+        out.write(RemoteViewsProto.CharSequence.Span.LineBackground.COLOR, span.getColor());
+    }
+
+    @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+    public static LineBreakConfigSpan createLineBreakConfigSpanFromProto(
+            @NonNull ProtoInputStream in) throws Exception {
+        int lineBreakStyle = 0;
+        int lineBreakWordStyle = 0;
+        int hyphenation = 0;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span.LineBreak.LINE_BREAK_STYLE:
+                    lineBreakStyle = in.readInt(
+                            RemoteViewsProto.CharSequence.Span.LineBreak.LINE_BREAK_STYLE);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.LineBreak.LINE_BREAK_WORD_STYLE:
+                    lineBreakWordStyle = in.readInt(
+                            RemoteViewsProto.CharSequence.Span.LineBreak.LINE_BREAK_WORD_STYLE);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.LineBreak.HYPHENATION:
+                    hyphenation = in.readInt(
+                            RemoteViewsProto.CharSequence.Span.LineBreak.HYPHENATION);
+                    break;
+                default:
+                    Log.w("LineBreakConfigSpan",
+                            "Unhandled field while reading " + "LineBreakConfigSpan proto!\n"
+                                    + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        LineBreakConfig lbc = new LineBreakConfig.Builder().setLineBreakStyle(
+                lineBreakStyle).setLineBreakWordStyle(lineBreakWordStyle).setHyphenation(
+                hyphenation).build();
+        return new LineBreakConfigSpan(lbc);
+    }
+
+    @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+    public static void writeLineBreakConfigSpanToProto(@NonNull ProtoOutputStream out,
+            LineBreakConfigSpan span) {
+        out.write(RemoteViewsProto.CharSequence.Span.LineBreak.LINE_BREAK_STYLE,
+                span.getLineBreakConfig().getLineBreakStyle());
+        out.write(RemoteViewsProto.CharSequence.Span.LineBreak.LINE_BREAK_WORD_STYLE,
+                span.getLineBreakConfig().getLineBreakWordStyle());
+        out.write(RemoteViewsProto.CharSequence.Span.LineBreak.HYPHENATION,
+                span.getLineBreakConfig().getHyphenation());
+    }
+
+    public static LineHeightSpan.Standard createLineHeightSpanStandardFromProto(
+            @NonNull ProtoInputStream in) throws Exception {
+        int height = 0;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span.LineHeight.HEIGHT:
+                    height = in.readInt(RemoteViewsProto.CharSequence.Span.LineHeight.HEIGHT);
+                    break;
+                default:
+                    Log.w("LineHeightSpan.Standard",
+                            "Unhandled field while reading" + " LineHeightSpan.Standard proto!\n"
+                                    + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        return new LineHeightSpan.Standard(height);
+    }
+
+    public static void writeLineHeightSpanStandardToProto(@NonNull ProtoOutputStream out,
+            LineHeightSpan.Standard span) {
+        out.write(RemoteViewsProto.CharSequence.Span.LineHeight.HEIGHT, span.getHeight());
+    }
+
+    public static LocaleSpan createLocaleSpanFromProto(@NonNull ProtoInputStream in)
+            throws Exception {
+        String languageTags = null;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span.Locale.LANGUAGE_TAGS:
+                    languageTags = in.readString(
+                            RemoteViewsProto.CharSequence.Span.Locale.LANGUAGE_TAGS);
+                    break;
+                default:
+                    Log.w("LocaleSpan", "Unhandled field while reading" + " LocaleSpan proto!\n"
+                            + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        return new LocaleSpan(LocaleList.forLanguageTags(languageTags));
+    }
+
+    public static void writeLocaleSpanToProto(@NonNull ProtoOutputStream out, LocaleSpan span) {
+        out.write(RemoteViewsProto.CharSequence.Span.Locale.LANGUAGE_TAGS,
+                span.getLocales().toLanguageTags());
+    }
+
+    public static QuoteSpan createQuoteSpanFromProto(@NonNull ProtoInputStream in)
+            throws Exception {
+        int color = 0;
+        int stripeWidth = 0;
+        int gapWidth = 0;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span.Quote.COLOR:
+                    color = in.readInt(RemoteViewsProto.CharSequence.Span.Quote.COLOR);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.Quote.STRIPE_WIDTH:
+                    stripeWidth = in.readInt(RemoteViewsProto.CharSequence.Span.Quote.STRIPE_WIDTH);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.Quote.GAP_WIDTH:
+                    gapWidth = in.readInt(RemoteViewsProto.CharSequence.Span.Quote.GAP_WIDTH);
+                    break;
+                default:
+                    Log.w("QuoteSpan", "Unhandled field while reading QuoteSpan " + "proto!\n"
+                            + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        return new QuoteSpan(color, stripeWidth, gapWidth);
+    }
+
+    public static void writeQuoteSpanToProto(@NonNull ProtoOutputStream out, QuoteSpan span) {
+        out.write(RemoteViewsProto.CharSequence.Span.Quote.COLOR, span.getColor());
+        out.write(RemoteViewsProto.CharSequence.Span.Quote.STRIPE_WIDTH, span.getStripeWidth());
+        out.write(RemoteViewsProto.CharSequence.Span.Quote.GAP_WIDTH, span.getGapWidth());
+    }
+
+    public static RelativeSizeSpan createRelativeSizeSpanFromProto(@NonNull ProtoInputStream in)
+            throws Exception {
+        float proportion = 0;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span.RelativeSize.PROPORTION:
+                    proportion = in.readFloat(
+                            RemoteViewsProto.CharSequence.Span.RelativeSize.PROPORTION);
+                    break;
+                default:
+                    Log.w("RelativeSizeSpan",
+                            "Unhandled field while reading" + " RelativeSizeSpan proto!\n"
+                                    + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        return new RelativeSizeSpan(proportion);
+    }
+
+    public static void writeRelativeSizeSpanToProto(@NonNull ProtoOutputStream out,
+            RelativeSizeSpan span) {
+        out.write(RemoteViewsProto.CharSequence.Span.RelativeSize.PROPORTION, span.getSizeChange());
+    }
+
+    public static ScaleXSpan createScaleXSpanFromProto(@NonNull ProtoInputStream in)
+            throws Exception {
+        float proportion = 0f;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span.ScaleX.PROPORTION:
+                    proportion = in.readFloat(RemoteViewsProto.CharSequence.Span.ScaleX.PROPORTION);
+                    break;
+                default:
+                    Log.w("ScaleXSpan", "Unhandled field while reading" + " ScaleXSpan proto!\n"
+                            + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        return new ScaleXSpan(proportion);
+    }
+
+    public static void writeScaleXSpanToProto(@NonNull ProtoOutputStream out, ScaleXSpan span) {
+        out.write(RemoteViewsProto.CharSequence.Span.ScaleX.PROPORTION, span.getScaleX());
+    }
+
+    public static SpellCheckSpan createSpellCheckSpanFromProto(@NonNull ProtoInputStream in) {
+        return new SpellCheckSpan();
+    }
+
+    public static void writeSpellCheckSpanToProto(@NonNull ProtoOutputStream out,
+            SpellCheckSpan span) {
+    }
+
+    public static StrikethroughSpan createStrikethroughSpanFromProto(@NonNull ProtoInputStream in) {
+        return new StrikethroughSpan();
+    }
+
+    public static void writeStrikethroughSpanToProto(@NonNull ProtoOutputStream out,
+            StrikethroughSpan span) {
+    }
+
+    public static StyleSpan createStyleSpanFromProto(@NonNull ProtoInputStream in)
+            throws Exception {
+        int style = 0;
+        int fontWeightAdjustment = 0;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span.Style.STYLE:
+                    style = in.readInt(RemoteViewsProto.CharSequence.Span.Style.STYLE);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.Style.FONT_WEIGHT_ADJUSTMENT:
+                    fontWeightAdjustment = in.readInt(
+                            RemoteViewsProto.CharSequence.Span.Style.FONT_WEIGHT_ADJUSTMENT);
+                    break;
+                default:
+                    Log.w("StyleSpan", "Unhandled field while reading StyleSpan " + "proto!\n"
+                            + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        return new StyleSpan(style, fontWeightAdjustment);
+    }
+
+    public static void writeStyleSpanToProto(@NonNull ProtoOutputStream out, StyleSpan span) {
+        out.write(RemoteViewsProto.CharSequence.Span.Style.STYLE, span.getStyle());
+        out.write(RemoteViewsProto.CharSequence.Span.Style.FONT_WEIGHT_ADJUSTMENT,
+                span.getFontWeightAdjustment());
+    }
+
+    public static SubscriptSpan createSubscriptSpanFromProto(@NonNull ProtoInputStream in) {
+        return new SubscriptSpan();
+    }
+
+    public static void writeSubscriptSpanToProto(@NonNull ProtoOutputStream out,
+            SubscriptSpan span) {
+    }
+
+    public static SuggestionRangeSpan createSuggestionRangeSpanFromProto(
+            @NonNull ProtoInputStream in) throws Exception {
+        int backgroundColor = 0;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span.SuggestionRange.BACKGROUND_COLOR:
+                    backgroundColor = in.readInt(
+                            RemoteViewsProto.CharSequence.Span.SuggestionRange.BACKGROUND_COLOR);
+                    break;
+                default:
+                    Log.w("SuggestionRangeSpan",
+                            "Unhandled field while reading" + " SuggestionRangeSpan proto!\n"
+                                    + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        SuggestionRangeSpan span = new SuggestionRangeSpan();
+        span.setBackgroundColor(backgroundColor);
+        return span;
+    }
+
+    public static void writeSuggestionRangeSpanToProto(@NonNull ProtoOutputStream out,
+            SuggestionRangeSpan span) {
+        out.write(RemoteViewsProto.CharSequence.Span.SuggestionRange.BACKGROUND_COLOR,
+                span.getBackgroundColor());
+    }
+
+    public static SuggestionSpan createSuggestionSpanFromProto(@NonNull ProtoInputStream in)
+            throws Exception {
+        List<String> suggestions = new ArrayList<>();
+        int flags = 0;
+        String localeStringForCompatibility = null;
+        String languageTag = null;
+        int hashCode = 0;
+        int easyCorrectUnderlineColor = 0;
+        float easyCorrectUnderlineThickness = 0;
+        int misspelledUnderlineColor = 0;
+        float misspelledUnderlineThickness = 0;
+        int autoCorrectionUnderlineColor = 0;
+        float autoCorrectionUnderlineThickness = 0;
+        int grammarErrorUnderlineColor = 0;
+        float grammarErrorUnderlineThickness = 0;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span.Suggestion.SUGGESTIONS:
+                    suggestions.add(in.readString(
+                            RemoteViewsProto.CharSequence.Span.Suggestion.SUGGESTIONS));
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.Suggestion.FLAGS:
+                    flags = in.readInt(RemoteViewsProto.CharSequence.Span.Suggestion.FLAGS);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span
+                        .Suggestion.LOCALE_STRING_FOR_COMPATIBILITY:
+                    localeStringForCompatibility = in.readString(
+                            RemoteViewsProto.CharSequence.Span
+                                    .Suggestion.LOCALE_STRING_FOR_COMPATIBILITY);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.Suggestion.LANGUAGE_TAG:
+                    languageTag = in.readString(
+                            RemoteViewsProto.CharSequence.Span.Suggestion.LANGUAGE_TAG);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.Suggestion.HASH_CODE:
+                    hashCode = in.readInt(RemoteViewsProto.CharSequence.Span.Suggestion.HASH_CODE);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span
+                        .Suggestion.EASY_CORRECT_UNDERLINE_COLOR:
+                    easyCorrectUnderlineColor = in.readInt(
+                            RemoteViewsProto.CharSequence.Span
+                                    .Suggestion.EASY_CORRECT_UNDERLINE_COLOR);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span
+                        .Suggestion.EASY_CORRECT_UNDERLINE_THICKNESS:
+                    easyCorrectUnderlineThickness = in.readFloat(
+                            RemoteViewsProto.CharSequence.Span
+                                    .Suggestion.EASY_CORRECT_UNDERLINE_THICKNESS);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span
+                        .Suggestion.MISSPELLED_UNDERLINE_COLOR:
+                    misspelledUnderlineColor = in.readInt(
+                            RemoteViewsProto.CharSequence.Span
+                                    .Suggestion.MISSPELLED_UNDERLINE_COLOR);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span
+                        .Suggestion.MISSPELLED_UNDERLINE_THICKNESS:
+                    misspelledUnderlineThickness = in.readFloat(
+                            RemoteViewsProto.CharSequence.Span
+                                    .Suggestion.MISSPELLED_UNDERLINE_THICKNESS);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span
+                        .Suggestion.AUTO_CORRECTION_UNDERLINE_COLOR:
+                    autoCorrectionUnderlineColor = in.readInt(
+                            RemoteViewsProto.CharSequence.Span
+                                    .Suggestion.AUTO_CORRECTION_UNDERLINE_COLOR);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span
+                        .Suggestion.AUTO_CORRECTION_UNDERLINE_THICKNESS:
+                    autoCorrectionUnderlineThickness = in.readFloat(
+                            RemoteViewsProto.CharSequence.Span
+                                    .Suggestion.AUTO_CORRECTION_UNDERLINE_THICKNESS);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span
+                        .Suggestion.GRAMMAR_ERROR_UNDERLINE_COLOR:
+                    grammarErrorUnderlineColor = in.readInt(
+                            RemoteViewsProto.CharSequence.Span
+                                    .Suggestion.GRAMMAR_ERROR_UNDERLINE_COLOR);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span
+                        .Suggestion.GRAMMAR_ERROR_UNDERLINE_THICKNESS:
+                    grammarErrorUnderlineThickness = in.readFloat(
+                            RemoteViewsProto.CharSequence.Span
+                                    .Suggestion.GRAMMAR_ERROR_UNDERLINE_THICKNESS);
+                    break;
+                default:
+                    Log.w("SuggestionSpan",
+                            "Unhandled field while reading SuggestionSpan " + "proto!\n"
+                                    + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        String[] suggestionsArray = new String[suggestions.size()];
+        suggestions.toArray(suggestionsArray);
+        return new SuggestionSpan(suggestionsArray, flags, localeStringForCompatibility,
+                languageTag, hashCode, easyCorrectUnderlineColor, easyCorrectUnderlineThickness,
+                misspelledUnderlineColor, misspelledUnderlineThickness,
+                autoCorrectionUnderlineColor, autoCorrectionUnderlineThickness,
+                grammarErrorUnderlineColor, grammarErrorUnderlineThickness);
+    }
+
+    public static void writeSuggestionSpanToProto(@NonNull ProtoOutputStream out,
+            SuggestionSpan span) {
+        for (String suggestion : span.getSuggestions()) {
+            out.write(RemoteViewsProto.CharSequence.Span.Suggestion.SUGGESTIONS, suggestion);
+        }
+        out.write(RemoteViewsProto.CharSequence.Span.Suggestion.FLAGS, span.getFlags());
+        out.write(RemoteViewsProto.CharSequence.Span.Suggestion.LOCALE_STRING_FOR_COMPATIBILITY,
+                span.getLocale());
+        if (span.getLocaleObject() != null) {
+            out.write(RemoteViewsProto.CharSequence.Span.Suggestion.LANGUAGE_TAG,
+                    span.getLocaleObject().toLanguageTag());
+        }
+        out.write(RemoteViewsProto.CharSequence.Span.Suggestion.HASH_CODE, span.hashCode());
+        out.write(RemoteViewsProto.CharSequence.Span.Suggestion.EASY_CORRECT_UNDERLINE_COLOR,
+                span.getEasyCorrectUnderlineColor());
+        out.write(RemoteViewsProto.CharSequence.Span.Suggestion.EASY_CORRECT_UNDERLINE_THICKNESS,
+                span.getEasyCorrectUnderlineThickness());
+        out.write(RemoteViewsProto.CharSequence.Span.Suggestion.MISSPELLED_UNDERLINE_COLOR,
+                span.getMisspelledUnderlineColor());
+        out.write(RemoteViewsProto.CharSequence.Span.Suggestion.MISSPELLED_UNDERLINE_THICKNESS,
+                span.getMisspelledUnderlineThickness());
+        out.write(RemoteViewsProto.CharSequence.Span.Suggestion.AUTO_CORRECTION_UNDERLINE_COLOR,
+                span.getAutoCorrectionUnderlineColor());
+        out.write(RemoteViewsProto.CharSequence.Span.Suggestion.AUTO_CORRECTION_UNDERLINE_THICKNESS,
+                span.getAutoCorrectionUnderlineThickness());
+        out.write(RemoteViewsProto.CharSequence.Span.Suggestion.GRAMMAR_ERROR_UNDERLINE_COLOR,
+                span.getGrammarErrorUnderlineColor());
+        out.write(RemoteViewsProto.CharSequence.Span.Suggestion.GRAMMAR_ERROR_UNDERLINE_THICKNESS,
+                span.getGrammarErrorUnderlineThickness());
+    }
+
+    public static SuperscriptSpan createSuperscriptSpanFromProto(@NonNull ProtoInputStream in) {
+        return new SuperscriptSpan();
+    }
+
+    public static void writeSuperscriptSpanToProto(@NonNull ProtoOutputStream out,
+            SuperscriptSpan span) {
+    }
+
+    public static TextAppearanceSpan createTextAppearanceSpanFromProto(@NonNull ProtoInputStream in)
+            throws Exception {
+        String familyName = null;
+        int style = 0;
+        int textSize = 0;
+        ColorStateList textColor = null;
+        ColorStateList textColorLink = null;
+        int textFontWeight = 0;
+        LocaleList textLocales = null;
+        float shadowRadius = 0F;
+        float shadowDx = 0F;
+        float shadowDy = 0F;
+        int shadowColor = 0;
+        boolean hasElegantTextHeight = false;
+        boolean elegantTextHeight = false;
+        boolean hasLetterSpacing = false;
+        float letterSpacing = 0F;
+        String fontFeatureSettings = null;
+        String fontVariationSettings = null;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span.TextAppearance.FAMILY_NAME:
+                    familyName = in.readString(
+                            RemoteViewsProto.CharSequence.Span.TextAppearance.FAMILY_NAME);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.TextAppearance.STYLE:
+                    style = in.readInt(RemoteViewsProto.CharSequence.Span.TextAppearance.STYLE);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.TextAppearance.TEXT_SIZE:
+                    textSize = in.readInt(
+                            RemoteViewsProto.CharSequence.Span.TextAppearance.TEXT_SIZE);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.TextAppearance.TEXT_COLOR:
+                    final long textColorToken = in.start(
+                            RemoteViewsProto.CharSequence.Span.TextAppearance.TEXT_COLOR);
+                    textColor = ColorStateList.createFromProto(in);
+                    in.end(textColorToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.TextAppearance.TEXT_COLOR_LINK:
+                    final long textColorLinkToken = in.start(
+                            RemoteViewsProto.CharSequence.Span.TextAppearance.TEXT_COLOR_LINK);
+                    textColorLink = ColorStateList.createFromProto(in);
+                    in.end(textColorLinkToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.TextAppearance.TEXT_FONT_WEIGHT:
+                    textFontWeight = in.readInt(
+                            RemoteViewsProto.CharSequence.Span.TextAppearance.TEXT_FONT_WEIGHT);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.TextAppearance.TEXT_LOCALE:
+                    textLocales = LocaleList.forLanguageTags(in.readString(
+                            RemoteViewsProto.CharSequence.Span.TextAppearance.TEXT_LOCALE));
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.TextAppearance.SHADOW_RADIUS:
+                    shadowRadius = in.readFloat(
+                            RemoteViewsProto.CharSequence.Span.TextAppearance.SHADOW_RADIUS);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.TextAppearance.SHADOW_DX:
+                    shadowDx = in.readFloat(
+                            RemoteViewsProto.CharSequence.Span.TextAppearance.SHADOW_DX);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.TextAppearance.SHADOW_DY:
+                    shadowDy = in.readFloat(
+                            RemoteViewsProto.CharSequence.Span.TextAppearance.SHADOW_DY);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.TextAppearance.SHADOW_COLOR:
+                    shadowColor = in.readInt(
+                            RemoteViewsProto.CharSequence.Span.TextAppearance.SHADOW_COLOR);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span
+                        .TextAppearance.HAS_ELEGANT_TEXT_HEIGHT_FIELD:
+                    hasElegantTextHeight = in.readBoolean(
+                            RemoteViewsProto.CharSequence.Span
+                                    .TextAppearance.HAS_ELEGANT_TEXT_HEIGHT_FIELD);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.TextAppearance.ELEGANT_TEXT_HEIGHT:
+                    elegantTextHeight = in.readBoolean(
+                            RemoteViewsProto.CharSequence.Span.TextAppearance.ELEGANT_TEXT_HEIGHT);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span
+                        .TextAppearance.HAS_LETTER_SPACING_FIELD:
+                    hasLetterSpacing = in.readBoolean(
+                            RemoteViewsProto.CharSequence.Span
+                                    .TextAppearance.HAS_LETTER_SPACING_FIELD);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.TextAppearance.LETTER_SPACING:
+                    letterSpacing = in.readFloat(
+                            RemoteViewsProto.CharSequence.Span.TextAppearance.LETTER_SPACING);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.TextAppearance.FONT_FEATURE_SETTINGS:
+                    fontFeatureSettings = in.readString(
+                            RemoteViewsProto.CharSequence.Span
+                                    .TextAppearance.FONT_FEATURE_SETTINGS);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span
+                        .TextAppearance.FONT_VARIATION_SETTINGS:
+                    fontVariationSettings = in.readString(
+                            RemoteViewsProto.CharSequence.Span
+                                    .TextAppearance.FONT_VARIATION_SETTINGS);
+                    break;
+                default:
+                    Log.w("TextAppearanceSpan",
+                            "Unhandled field while reading TextAppearanceSpan " + "proto!\n"
+                                    + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        return new TextAppearanceSpan(familyName, style, textSize, textColor, textColorLink,
+                /* typeface= */ null, textFontWeight, textLocales, shadowRadius, shadowDx, shadowDy,
+                shadowColor, hasElegantTextHeight, elegantTextHeight, hasLetterSpacing,
+                letterSpacing, fontFeatureSettings, fontVariationSettings);
+    }
+
+    public static void writeTextAppearanceSpanToProto(@NonNull ProtoOutputStream out,
+            TextAppearanceSpan span) {
+        out.write(RemoteViewsProto.CharSequence.Span.TextAppearance.FAMILY_NAME, span.getFamily());
+        out.write(RemoteViewsProto.CharSequence.Span.TextAppearance.STYLE, span.getTextStyle());
+        out.write(RemoteViewsProto.CharSequence.Span.TextAppearance.TEXT_SIZE, span.getTextSize());
+        out.write(RemoteViewsProto.CharSequence.Span.TextAppearance.TEXT_FONT_WEIGHT,
+                span.getTextFontWeight());
+        if (span.getTextLocales() != null) {
+            out.write(RemoteViewsProto.CharSequence.Span.TextAppearance.TEXT_LOCALE,
+                    span.getTextLocales().toLanguageTags());
+        }
+        out.write(RemoteViewsProto.CharSequence.Span.TextAppearance.SHADOW_RADIUS,
+                span.getShadowRadius());
+        out.write(RemoteViewsProto.CharSequence.Span.TextAppearance.SHADOW_DX, span.getShadowDx());
+        out.write(RemoteViewsProto.CharSequence.Span.TextAppearance.SHADOW_DY, span.getShadowDy());
+        out.write(RemoteViewsProto.CharSequence.Span.TextAppearance.SHADOW_COLOR,
+                span.getShadowColor());
+        out.write(RemoteViewsProto.CharSequence.Span.TextAppearance.HAS_ELEGANT_TEXT_HEIGHT_FIELD,
+                span.hasElegantTextHeight());
+        out.write(RemoteViewsProto.CharSequence.Span.TextAppearance.ELEGANT_TEXT_HEIGHT,
+                span.isElegantTextHeight());
+        out.write(RemoteViewsProto.CharSequence.Span.TextAppearance.HAS_LETTER_SPACING_FIELD,
+                span.hasLetterSpacing());
+        out.write(RemoteViewsProto.CharSequence.Span.TextAppearance.LETTER_SPACING,
+                span.getLetterSpacing());
+        out.write(RemoteViewsProto.CharSequence.Span.TextAppearance.FONT_FEATURE_SETTINGS,
+                span.getFontFeatureSettings());
+        out.write(RemoteViewsProto.CharSequence.Span.TextAppearance.FONT_VARIATION_SETTINGS,
+                span.getFontVariationSettings());
+        if (span.getTextColor() != null) {
+            final long textColorToken = out.start(
+                    RemoteViewsProto.CharSequence.Span.TextAppearance.TEXT_COLOR);
+            span.getTextColor().writeToProto(out);
+            out.end(textColorToken);
+        }
+        if (span.getLinkTextColor() != null) {
+            final long textColorLinkToken = out.start(
+                    RemoteViewsProto.CharSequence.Span.TextAppearance.TEXT_COLOR_LINK);
+            span.getLinkTextColor().writeToProto(out);
+            out.end(textColorLinkToken);
+        }
+    }
+
+    public static TtsSpan createTtsSpanFromProto(@NonNull ProtoInputStream in) throws Exception {
+        String type = null;
+        PersistableBundle args = null;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span.Tts.TYPE:
+                    type = in.readString(RemoteViewsProto.CharSequence.Span.Tts.TYPE);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.Tts.ARGS:
+                    final byte[] data = in.readString(
+                            RemoteViewsProto.CharSequence.Span.Tts.ARGS).getBytes();
+                    args = PersistableBundle.readFromStream(new ByteArrayInputStream(data));
+                    break;
+                default:
+                    Log.w("TtsSpan", "Unhandled field while reading TtsSpan " + "proto!\n"
+                            + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        return new TtsSpan(type, args);
+    }
+
+    public static void writeTtsSpanToProto(@NonNull ProtoOutputStream out, TtsSpan span) {
+        out.write(RemoteViewsProto.CharSequence.Span.Tts.TYPE, span.getType());
+        if (span.getArgs() != null) {
+            ByteArrayOutputStream buf = new ByteArrayOutputStream();
+            try {
+                span.getArgs().writeToStream(buf);
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+            out.write(RemoteViewsProto.CharSequence.Span.Tts.ARGS, buf.toString(UTF_8));
+        }
+    }
+
+    public static TypefaceSpan createTypefaceSpanFromProto(@NonNull ProtoInputStream in)
+            throws Exception {
+        String family = null;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span.Typeface.FAMILY:
+                    family = in.readString(RemoteViewsProto.CharSequence.Span.Typeface.FAMILY);
+                    break;
+                default:
+                    Log.w("TypefaceSpan", "Unhandled field while reading" + " TypefaceSpan proto!\n"
+                            + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        return new TypefaceSpan(family);
+    }
+
+    public static void writeTypefaceSpanToProto(@NonNull ProtoOutputStream out, TypefaceSpan span) {
+        out.write(RemoteViewsProto.CharSequence.Span.Typeface.FAMILY, span.getFamily());
+    }
+
+    public static URLSpan createURLSpanFromProto(@NonNull ProtoInputStream in) throws Exception {
+        String url = null;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span.Url.URL:
+                    url = in.readString(RemoteViewsProto.CharSequence.Span.Url.URL);
+                    break;
+                default:
+                    Log.w("URLSpan", "Unhandled field while reading" + " URLSpan proto!\n"
+                            + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        return new URLSpan(url);
+    }
+
+    public static void writeURLSpanToProto(@NonNull ProtoOutputStream out, URLSpan span) {
+        out.write(RemoteViewsProto.CharSequence.Span.Url.URL, span.getURL());
+    }
+
+    public static UnderlineSpan createUnderlineSpanFromProto(@NonNull ProtoInputStream in) {
+        return new UnderlineSpan();
+    }
+
+    public static void writeUnderlineSpanToProto(@NonNull ProtoOutputStream out,
+            UnderlineSpan span) {
+    }
 }
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index ac899f4..61ecc62 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -28,10 +28,10 @@
 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
 import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
 import static android.view.inputmethod.EditorInfo.STYLUS_HANDWRITING_ENABLED_ANDROIDX_EXTRAS_KEY;
+import static android.view.inputmethod.Flags.initiationWithoutInputConnection;
 
 import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
 import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
-import static android.view.inputmethod.Flags.initiationWithoutInputConnection;
 
 import android.R;
 import android.annotation.CallSuper;
@@ -937,6 +937,7 @@
     private TextPaint mTempTextPaint;
 
     private Object mTempCursor;
+    private Matrix mTempMatrix;
 
     @UnsupportedAppUsage
     private BoringLayout.Metrics mBoring;
@@ -12106,6 +12107,22 @@
     }
 
     private PointF convertFromScreenToContentCoordinates(PointF point) {
+        if (Flags.handwritingGestureWithTransformation()) {
+            if (mTempMatrix == null) {
+                mTempMatrix = new Matrix();
+            }
+            Matrix matrix = mTempMatrix;
+            matrix.reset();
+            transformMatrixToLocal(matrix);
+            matrix.postTranslate(
+                    -viewportToContentHorizontalOffset(),
+                    -viewportToContentVerticalOffset()
+            );
+
+            float[] copy = new float[] { point.x, point.y };
+            matrix.mapPoints(copy);
+            return new PointF(copy[0], copy[1]);
+        }
         int[] screenToViewport = getLocationOnScreen();
         PointF copy = new PointF(point);
         copy.offset(
@@ -12115,6 +12132,22 @@
     }
 
     private RectF convertFromScreenToContentCoordinates(RectF rect) {
+        if (Flags.handwritingGestureWithTransformation()) {
+            if (mTempMatrix == null) {
+                mTempMatrix = new Matrix();
+            }
+            Matrix matrix = mTempMatrix;
+            matrix.reset();
+            transformMatrixToLocal(matrix);
+            matrix.postTranslate(
+                    -viewportToContentHorizontalOffset(),
+                    -viewportToContentVerticalOffset()
+            );
+
+            RectF copy = new RectF(rect);
+            matrix.mapRect(copy);
+            return copy;
+        }
         int[] screenToViewport = getLocationOnScreen();
         RectF copy = new RectF(rect);
         copy.offset(
@@ -14279,6 +14312,9 @@
     }
 
     /**
+     * Don't use, it returns wrong result when the view is scaled. This method can be removed once
+     * Flags.handwritingGestureWithTransformation is enabled.
+     * Assume
      * Helper method to set {@code rect} to this TextView's non-clipped area in its own coordinates.
      * This method obtains the view's visible rectangle whereas the method
      * {@link #getContentVisibleRect} returns the text layout's visible rectangle.
@@ -14299,6 +14335,8 @@
     }
 
     /**
+     * Don't use, it returns wrong result when view is scaled. This method can be removed once
+     * Flags.handwritingGestureWithTransformation is enabled.
      * Helper method to set {@code rect} to the text content's non-clipped area in the view's
      * coordinates.
      *
@@ -14314,6 +14352,58 @@
                 getWidth() - getCompoundPaddingRight(), getHeight() - getCompoundPaddingBottom());
     }
 
+    private boolean getEditorAndHandwritingBounds(@NonNull RectF editorBounds,
+            @Nullable RectF handwritingBounds) {
+        if (mTempRect == null) {
+            mTempRect = new Rect();
+        }
+        Rect rect = mTempRect;
+        if (!getGlobalVisibleRect(rect)) {
+            return false;
+        }
+        if (mTempMatrix == null) {
+            mTempMatrix = new Matrix();
+        }
+
+        Matrix matrix = mTempMatrix;
+        matrix.reset();
+        transformMatrixToLocal(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
+        // the global rectangle to local. And the result is wrong the View is scaled.
+        //
+        // This approach use the local transformation matrix to map the global rectangle to
+        // local instead.
+        //
+        // Note: it doesn't work well with rotation. Because Rect must be
+        // axis-aligned, when a rotated Rect becomes quadrilateral, the quadrilateral's
+        // bounding box is stored at Rect instead. It makes the returned Rect larger than
+        // the correct size.
+        matrix.mapRect(editorBounds);
+
+        if (handwritingBounds != null) {
+            // Similar to editorBounds, handwritingBounds must be computed in global coordinates
+            // and then converted back to local coordinates. Otherwise, if the view is scaled,
+            // the handwritingBoundsOffsets are also scaled, which is not the expected behavior.
+            handwritingBounds.top = rect.top -  getHandwritingBoundsOffsetTop();
+            handwritingBounds.left = rect.left - getHandwritingBoundsOffsetLeft();
+            handwritingBounds.bottom = rect.bottom + getHandwritingBoundsOffsetBottom();
+            handwritingBounds.right = rect.right + getHandwritingBoundsOffsetRight();
+            matrix.mapRect(handwritingBounds);
+        }
+        return true;
+    }
+
+    private boolean getContentVisibleRect(RectF rect) {
+        if (!getEditorAndHandwritingBounds(rect, /* handwritingBounds= */null)) {
+            return false;
+        }
+        // Clip the view's visible rect with the text layout's visible rect.
+        return rect.intersect(getCompoundPaddingLeft(), getCompoundPaddingTop(),
+                getWidth() - getCompoundPaddingRight(), getHeight() - getCompoundPaddingBottom());
+    }
+
     /**
      * Populate requested character bounds in a {@link CursorAnchorInfo.Builder}
      *
@@ -14333,9 +14423,15 @@
             // character bounds in this case yet.
             return;
         }
-        final Rect rect = new Rect();
-        getContentVisibleRect(rect);
-        final RectF visibleRect = new RectF(rect);
+        final RectF visibleRect = new RectF();
+
+        if (Flags.handwritingGestureWithTransformation()) {
+            getContentVisibleRect(visibleRect);
+        } else {
+            final Rect rect = new Rect();
+            getContentVisibleRect(rect);
+            visibleRect.set(rect);
+        }
 
         final float[] characterBounds = getCharacterBounds(startIndex, endIndex,
                 viewportToContentHorizontalOffset, viewportToContentVerticalOffset);
@@ -14438,24 +14534,26 @@
         builder.setMatrix(viewToScreenMatrix);
 
         if (includeEditorBounds) {
-            if (mTempRect == null) {
-                mTempRect = new Rect();
-            }
-            final Rect bounds = mTempRect;
-            final RectF editorBounds;
-            final RectF handwritingBounds;
-            if (getViewVisibleRect(bounds)) {
-                editorBounds = new RectF(bounds);
-                handwritingBounds = new RectF(editorBounds);
-                handwritingBounds.top -= getHandwritingBoundsOffsetTop();
-                handwritingBounds.left -= getHandwritingBoundsOffsetLeft();
-                handwritingBounds.bottom += getHandwritingBoundsOffsetBottom();
-                handwritingBounds.right += getHandwritingBoundsOffsetRight();
+            final RectF editorBounds = new RectF();
+            final RectF handwritingBounds = new RectF();
+            if (Flags.handwritingGestureWithTransformation()) {
+                getEditorAndHandwritingBounds(editorBounds, handwritingBounds);
             } else {
-                // The editor is not visible at all, return empty rectangles. We still need to
+                if (mTempRect == null) {
+                    mTempRect = new Rect();
+                }
+                final Rect bounds = mTempRect;
+
+                // If the editor is not visible at all, return empty rectangles. We still need to
                 // return an EditorBoundsInfo because IME has subscribed the EditorBoundsInfo.
-                editorBounds = new RectF();
-                handwritingBounds = new RectF();
+                if (getViewVisibleRect(bounds)) {
+                    editorBounds.set(bounds);
+                    handwritingBounds.set(editorBounds);
+                    handwritingBounds.top -= getHandwritingBoundsOffsetTop();
+                    handwritingBounds.left -= getHandwritingBoundsOffsetLeft();
+                    handwritingBounds.bottom += getHandwritingBoundsOffsetBottom();
+                    handwritingBounds.right += getHandwritingBoundsOffsetRight();
+                }
             }
             EditorBoundsInfo.Builder boundsBuilder = new EditorBoundsInfo.Builder();
             EditorBoundsInfo editorBoundsInfo = boundsBuilder.setEditorBounds(editorBounds)
@@ -14533,29 +14631,57 @@
             }
 
             if (includeVisibleLineBounds) {
-                final Rect visibleRect = new Rect();
-                if (getContentVisibleRect(visibleRect)) {
-                    // Subtract the viewportToContentVerticalOffset to convert the view
-                    // coordinates to layout coordinates.
-                    final float visibleTop =
-                            visibleRect.top - viewportToContentVerticalOffset;
-                    final float visibleBottom =
-                            visibleRect.bottom - viewportToContentVerticalOffset;
-                    final int firstLine =
-                            layout.getLineForVertical((int) Math.floor(visibleTop));
-                    final int lastLine =
-                            layout.getLineForVertical((int) Math.ceil(visibleBottom));
+                if (Flags.handwritingGestureWithTransformation()) {
+                    RectF visibleRect = new RectF();
+                    if (getContentVisibleRect(visibleRect)) {
+                        // Subtract the viewportToContentVerticalOffset to convert the view
+                        // coordinates to layout coordinates.
+                        final float visibleTop =
+                                visibleRect.top - viewportToContentVerticalOffset;
+                        final float visibleBottom =
+                                visibleRect.bottom - viewportToContentVerticalOffset;
+                        final int firstLine =
+                                layout.getLineForVertical((int) Math.floor(visibleTop));
+                        final int lastLine =
+                                layout.getLineForVertical((int) Math.ceil(visibleBottom));
 
-                    for (int line = firstLine; line <= lastLine; ++line) {
-                        final float left = layout.getLineLeft(line)
-                                + viewportToContentHorizontalOffset;
-                        final float top = layout.getLineTop(line)
-                                + viewportToContentVerticalOffset;
-                        final float right = layout.getLineRight(line)
-                                + viewportToContentHorizontalOffset;
-                        final float bottom = layout.getLineBottom(line, false)
-                                + viewportToContentVerticalOffset;
-                        builder.addVisibleLineBounds(left, top, right, bottom);
+                        for (int line = firstLine; line <= lastLine; ++line) {
+                            final float left = layout.getLineLeft(line)
+                                    + viewportToContentHorizontalOffset;
+                            final float top = layout.getLineTop(line)
+                                    + viewportToContentVerticalOffset;
+                            final float right = layout.getLineRight(line)
+                                    + viewportToContentHorizontalOffset;
+                            final float bottom = layout.getLineBottom(line, false)
+                                    + viewportToContentVerticalOffset;
+                            builder.addVisibleLineBounds(left, top, right, bottom);
+                        }
+                    }
+                } else {
+                    final Rect visibleRect = new Rect();
+                    if (getContentVisibleRect(visibleRect)) {
+                        // Subtract the viewportToContentVerticalOffset to convert the view
+                        // coordinates to layout coordinates.
+                        final float visibleTop =
+                                visibleRect.top - viewportToContentVerticalOffset;
+                        final float visibleBottom =
+                                visibleRect.bottom - viewportToContentVerticalOffset;
+                        final int firstLine =
+                                layout.getLineForVertical((int) Math.floor(visibleTop));
+                        final int lastLine =
+                                layout.getLineForVertical((int) Math.ceil(visibleBottom));
+
+                        for (int line = firstLine; line <= lastLine; ++line) {
+                            final float left = layout.getLineLeft(line)
+                                    + viewportToContentHorizontalOffset;
+                            final float top = layout.getLineTop(line)
+                                    + viewportToContentVerticalOffset;
+                            final float right = layout.getLineRight(line)
+                                    + viewportToContentHorizontalOffset;
+                            final float bottom = layout.getLineBottom(line, false)
+                                    + viewportToContentVerticalOffset;
+                            builder.addVisibleLineBounds(left, top, right, bottom);
+                        }
                     }
                 }
             }
diff --git a/core/java/android/window/TaskSnapshot.java b/core/java/android/window/TaskSnapshot.java
index f0144cb..20d1b3b 100644
--- a/core/java/android/window/TaskSnapshot.java
+++ b/core/java/android/window/TaskSnapshot.java
@@ -72,6 +72,7 @@
     int mAppearance;
     private final boolean mIsTranslucent;
     private final boolean mHasImeSurface;
+    private final int mUiMode;
     // Must be one of the named color spaces, otherwise, always use SRGB color space.
     private final ColorSpace mColorSpace;
     private int mInternalReferences;
@@ -96,7 +97,7 @@
             Rect contentInsets, Rect letterboxInsets, boolean isLowResolution,
             boolean isRealSnapshot, int windowingMode,
             @WindowInsetsController.Appearance int appearance, boolean isTranslucent,
-            boolean hasImeSurface) {
+            boolean hasImeSurface, int uiMode) {
         mId = id;
         mCaptureTime = captureTime;
         mTopActivityComponent = topActivityComponent;
@@ -114,6 +115,7 @@
         mAppearance = appearance;
         mIsTranslucent = isTranslucent;
         mHasImeSurface = hasImeSurface;
+        mUiMode = uiMode;
     }
 
     private TaskSnapshot(Parcel source) {
@@ -136,6 +138,7 @@
         mAppearance = source.readInt();
         mIsTranslucent = source.readBoolean();
         mHasImeSurface = source.readBoolean();
+        mUiMode = source.readInt();
     }
 
     /**
@@ -273,6 +276,13 @@
         return mAppearance;
     }
 
+    /**
+     * @return The uiMode the screenshot was taken in.
+     */
+    public int getUiMode() {
+        return mUiMode;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -295,6 +305,7 @@
         dest.writeInt(mAppearance);
         dest.writeBoolean(mIsTranslucent);
         dest.writeBoolean(mHasImeSurface);
+        dest.writeInt(mUiMode);
     }
 
     @Override
@@ -318,7 +329,8 @@
                 + " mAppearance=" + mAppearance
                 + " mIsTranslucent=" + mIsTranslucent
                 + " mHasImeSurface=" + mHasImeSurface
-                + " mInternalReferences=" + mInternalReferences;
+                + " mInternalReferences=" + mInternalReferences
+                + " mUiMode=" + Integer.toHexString(mUiMode);
     }
 
     /**
@@ -370,6 +382,7 @@
         private boolean mIsTranslucent;
         private boolean mHasImeSurface;
         private int mPixelFormat;
+        private int mUiMode;
 
         public Builder setId(long id) {
             mId = id;
@@ -452,6 +465,14 @@
             return this;
         }
 
+        /**
+         * Sets the original uiMode while capture
+         */
+        public Builder setUiMode(int uiMode) {
+            mUiMode = uiMode;
+            return this;
+        }
+
         public int getPixelFormat() {
             return mPixelFormat;
         }
@@ -481,7 +502,8 @@
                     mWindowingMode,
                     mAppearance,
                     mIsTranslucent,
-                    mHasImeSurface);
+                    mHasImeSurface,
+                    mUiMode);
 
         }
     }
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index e9d77f8..314bf89 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -700,6 +700,18 @@
     }
 
     /**
+     * Restore the back navigation target from visible to invisible for canceling gesture animation.
+     * @hide
+     */
+    @NonNull
+    public WindowContainerTransaction restoreBackNavi() {
+        final HierarchyOp hierarchyOp =
+                new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_RESTORE_BACK_NAVIGATION)
+                        .build();
+        mHierarchyOps.add(hierarchyOp);
+        return this;
+    }
+    /**
      * Adds a given {@code Rect} as an insets source frame on the {@code receiver}.
      *
      * @param receiver The window container that the insets source is added to.
@@ -1436,6 +1448,7 @@
         public static final int HIERARCHY_OP_TYPE_ADD_TASK_FRAGMENT_OPERATION = 17;
         public static final int HIERARCHY_OP_TYPE_MOVE_PIP_ACTIVITY_TO_PINNED_TASK = 18;
         public static final int HIERARCHY_OP_TYPE_SET_IS_TRIMMABLE = 19;
+        public static final int HIERARCHY_OP_TYPE_RESTORE_BACK_NAVIGATION = 20;
 
         // The following key(s) are for use with mLaunchOptions:
         // When launching a task (eg. from recents), this is the taskId to be launched.
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 125a0b2..4f84817 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -218,3 +218,10 @@
     description: "Enables desktop windowing app-to-web education"
     bug: "348205896"
 }
+
+flag {
+    name: "enable_minimize_button"
+    namespace: "lse_desktop_experience"
+    description: "Adds a minimize button the the caption bar"
+    bug: "356843241"
+}
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index d5746e5..9aeccf4 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -65,6 +65,14 @@
 }
 
 flag {
+  name: "keyguard_going_away_timeout"
+  namespace: "windowing_frontend"
+  description: "Allow a maximum of 10 seconds with keyguardGoingAway=true before force-resetting"
+  bug: "343598832"
+  is_fixed_read_only: true
+}
+
+flag {
   name: "close_to_square_config_includes_status_bar"
   namespace: "windowing_frontend"
   description: "On close to square display, when necessary, configuration includes status bar"
@@ -72,6 +80,17 @@
 }
 
 flag {
+  name: "reduce_keyguard_transitions"
+  namespace: "windowing_frontend"
+  description: "Avoid setting keyguard transitions ready unless there are no other changes"
+  bug: "354647472"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
   name: "transit_ready_tracking"
   namespace: "windowing_frontend"
   description: "Enable accurate transition readiness tracking"
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index b8c2a5f..adbc598 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -19,13 +19,6 @@
 
 flag {
     namespace: "windowing_sdk"
-    name: "fullscreen_dim_flag"
-    description: "Whether to allow showing fullscreen dim on ActivityEmbedding split"
-    bug: "293797706"
-}
-
-flag {
-    namespace: "windowing_sdk"
     name: "activity_embedding_interactive_divider_flag"
     description: "Whether the interactive divider feature is enabled"
     bug: "293654166"
@@ -68,16 +61,6 @@
 
 flag {
     namespace: "windowing_sdk"
-    name: "fix_pip_restore_to_overlay"
-    description: "Restore exit-pip activity back to ActivityEmbedding overlay"
-    bug: "297887697"
-    metadata {
-        purpose: PURPOSE_BUGFIX
-    }
-}
-
-flag {
-    namespace: "windowing_sdk"
     name: "activity_embedding_animation_customization_flag"
     description: "Whether the animation customization feature for AE is enabled"
     bug: "293658614"
@@ -135,3 +118,11 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    namespace: "windowing_sdk"
+    name: "ae_back_stack_restore"
+    description: "Allow the ActivityEmbedding back stack to be restored after process restarted"
+    bug: "289875940"
+    is_fixed_read_only: true
+}
diff --git a/core/java/com/android/internal/graphics/ColorUtils.java b/core/java/com/android/internal/graphics/ColorUtils.java
index f72a5ca..f210741 100644
--- a/core/java/com/android/internal/graphics/ColorUtils.java
+++ b/core/java/com/android/internal/graphics/ColorUtils.java
@@ -21,7 +21,7 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.graphics.Color;
-
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
 import com.android.internal.graphics.cam.Cam;
 
 /**
@@ -29,6 +29,7 @@
  *
  * A set of color-related utility methods, building upon those available in {@code Color}.
  */
+@RavenwoodKeepWholeClass
 public final class ColorUtils {
 
     private static final double XYZ_WHITE_REFERENCE_X = 95.047;
@@ -696,4 +697,4 @@
         double calculateContrast(int foreground, int background, int alpha);
     }
 
-}
\ No newline at end of file
+}
diff --git a/core/java/com/android/internal/graphics/cam/Cam.java b/core/java/com/android/internal/graphics/cam/Cam.java
index 1df85c3..49fa37b 100644
--- a/core/java/com/android/internal/graphics/cam/Cam.java
+++ b/core/java/com/android/internal/graphics/cam/Cam.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
 
 import com.android.internal.graphics.ColorUtils;
 
@@ -25,6 +26,7 @@
  * A color appearance model, based on CAM16, extended to use L* as the lightness dimension, and
  * coupled to a gamut mapping algorithm. Creates a color system, enables a digital design system.
  */
+@RavenwoodKeepWholeClass
 public class Cam {
     // The maximum difference between the requested L* and the L* returned.
     private static final float DL_MAX = 0.2f;
diff --git a/core/java/com/android/internal/graphics/cam/CamUtils.java b/core/java/com/android/internal/graphics/cam/CamUtils.java
index f541729..76fabc6 100644
--- a/core/java/com/android/internal/graphics/cam/CamUtils.java
+++ b/core/java/com/android/internal/graphics/cam/CamUtils.java
@@ -19,6 +19,7 @@
 
 import android.annotation.NonNull;
 import android.graphics.Color;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
 
 import com.android.internal.graphics.ColorUtils;
 
@@ -45,6 +46,7 @@
  * consistent, and reasonably good. It worked." - Fairchild, Color Models and Systems: Handbook of
  * Color Psychology, 2015
  */
+@RavenwoodKeepWholeClass
 public final class CamUtils {
     private CamUtils() {
     }
diff --git a/core/java/com/android/internal/graphics/cam/Frame.java b/core/java/com/android/internal/graphics/cam/Frame.java
index 0ac7cbc..c419fab 100644
--- a/core/java/com/android/internal/graphics/cam/Frame.java
+++ b/core/java/com/android/internal/graphics/cam/Frame.java
@@ -17,6 +17,7 @@
 package com.android.internal.graphics.cam;
 
 import android.annotation.NonNull;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
 import android.util.MathUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -33,6 +34,7 @@
  * number of calculations during the color => CAM conversion process that depend only on the viewing
  * conditions. Caching those calculations in a Frame instance saves a significant amount of time.
  */
+@RavenwoodKeepWholeClass
 public final class Frame {
     // Standard viewing conditions assumed in RGB specification - Stokes, Anderson, Chandrasekar,
     // Motta - A Standard Default Color Space for the Internet: sRGB, 1996.
diff --git a/core/java/com/android/internal/graphics/cam/HctSolver.java b/core/java/com/android/internal/graphics/cam/HctSolver.java
index d7a8691..6e558e7 100644
--- a/core/java/com/android/internal/graphics/cam/HctSolver.java
+++ b/core/java/com/android/internal/graphics/cam/HctSolver.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.graphics.cam;
 
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
+
 /**
  * An efficient algorithm for determining the closest sRGB color to a set of HCT coordinates,
  * based on geometrical insights for finding intersections in linear RGB, CAM16, and L*a*b*.
@@ -24,6 +26,7 @@
  * Copied from //java/com/google/ux/material/libmonet/hct on May 22 2022.
  * ColorUtils/MathUtils functions that were required were added to CamUtils.
  */
+@RavenwoodKeepWholeClass
 public class HctSolver {
     private HctSolver() {}
 
diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
index 2daf0fd..b009c99 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
@@ -20,6 +20,8 @@
 import android.annotation.DrawableRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.inputmethodservice.InputMethodService.BackDispositionMode;
+import android.inputmethodservice.InputMethodService.ImeWindowVisibility;
 import android.net.Uri;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -106,14 +108,10 @@
      *
      * @param vis visibility flags
      * @param backDisposition disposition flags
-     * @see android.inputmethodservice.InputMethodService#IME_ACTIVE
-     * @see android.inputmethodservice.InputMethodService#IME_VISIBLE
-     * @see android.inputmethodservice.InputMethodService#IME_INVISIBLE
-     * @see android.inputmethodservice.InputMethodService#BACK_DISPOSITION_DEFAULT
-     * @see android.inputmethodservice.InputMethodService#BACK_DISPOSITION_ADJUST_NOTHING
      */
     @AnyThread
-    public void setImeWindowStatusAsync(int vis, int backDisposition) {
+    public void setImeWindowStatusAsync(@ImeWindowVisibility int vis,
+            @BackDispositionMode int backDisposition) {
         final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
         if (ops == null) {
             return;
diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java
index 69d1cb3..7bfb800 100644
--- a/core/java/com/android/internal/jank/Cuj.java
+++ b/core/java/com/android/internal/jank/Cuj.java
@@ -210,8 +210,16 @@
      */
     public static final int CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE = 116;
 
+    /**
+     * Track interaction of exiting desktop mode on closing the last window.
+     *
+     * <p>Tracking starts when the last window is closed and finishes when the animation to exit
+     * desktop mode ends.
+     */
+    public static final int CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE = 117;
+
     // When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE.
-    @VisibleForTesting static final int LAST_CUJ = CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE;
+    @VisibleForTesting static final int LAST_CUJ = CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE;
 
     /** @hide */
     @IntDef({
@@ -319,7 +327,8 @@
             CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN,
             CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE,
             CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH,
-            CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE
+            CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE,
+            CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CujType {}
@@ -438,6 +447,7 @@
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE;
         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;
     }
 
     private Cuj() {
@@ -666,6 +676,8 @@
                 return "LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH";
             case CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE:
                 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";
         }
         return "UNKNOWN";
     }
diff --git a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
index b316a01..12d3264 100644
--- a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
+++ b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
@@ -727,11 +727,11 @@
         this.usesSdkLibraries = CollectionUtils.add(this.usesSdkLibraries,
                 TextUtils.safeIntern(libraryName));
         this.usesSdkLibrariesVersionsMajor = ArrayUtils.appendLong(
-                this.usesSdkLibrariesVersionsMajor, versionMajor, true);
+                this.usesSdkLibrariesVersionsMajor, versionMajor, /* allowDuplicates= */ true);
         this.usesSdkLibrariesCertDigests = ArrayUtils.appendElement(String[].class,
-                this.usesSdkLibrariesCertDigests, certSha256Digests, true);
-        this.usesSdkLibrariesOptional = ArrayUtils.appendBoolean(this.usesSdkLibrariesOptional,
-                usesSdkLibrariesOptional);
+                this.usesSdkLibrariesCertDigests, certSha256Digests, /* allowDuplicates= */ true);
+        this.usesSdkLibrariesOptional = ArrayUtils.appendBooleanDuplicatesAllowed(
+                this.usesSdkLibrariesOptional, usesSdkLibrariesOptional);
         return this;
     }
 
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index fbec1f1..cb20ceb 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -33,6 +33,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.TracePacketOuterClass.TracePacket.INTERNED_DATA;
 import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.PROTOLOG_MESSAGE;
@@ -332,6 +333,8 @@
     }
 
     private void onTracingFlush() {
+        Log.d(LOG_TAG, "Executing onTracingFlush");
+
         final ExecutorService loggingService;
         try {
             mBackgroundServiceLock.lock();
@@ -352,15 +355,19 @@
             Log.e(LOG_TAG, "Failed to wait for tracing to finish", e);
         }
 
-        dumpTransitionTraceConfig();
+        dumpViewerConfig();
+
+        Log.d(LOG_TAG, "Finished onTracingFlush");
     }
 
-    private void dumpTransitionTraceConfig() {
+    private void dumpViewerConfig() {
         if (mViewerConfigInputStreamProvider == null) {
             // No viewer config available
             return;
         }
 
+        Log.d(LOG_TAG, "Dumping viewer config to trace");
+
         ProtoInputStream pis = mViewerConfigInputStreamProvider.getInputStream();
 
         if (pis == null) {
@@ -390,6 +397,8 @@
                 Log.e(LOG_TAG, "Failed to read ProtoLog viewer config to dump on tracing end", e);
             }
         });
+
+        Log.d(LOG_TAG, "Dumped viewer config to trace");
     }
 
     private static void writeViewerConfigGroup(
@@ -441,6 +450,8 @@
                 case (int) GROUP_ID:
                     os.write(GROUP_ID, pis.readInt(GROUP_ID));
                     break;
+                case (int) LOCATION:
+                    os.write(LOCATION, pis.readInt(LOCATION));
                 default:
                     throw new RuntimeException(
                             "Unexpected field id " + pis.getFieldNumber());
@@ -770,6 +781,8 @@
 
     private synchronized void onTracingInstanceStart(
             int instanceIdx, ProtoLogDataSource.ProtoLogConfig config) {
+        Log.d(LOG_TAG, "Executing onTracingInstanceStart");
+
         final LogLevel defaultLogFrom = config.getDefaultGroupConfig().logFrom;
         for (int i = defaultLogFrom.ordinal(); i < LogLevel.values().length; i++) {
             mDefaultLogLevelCounts[i]++;
@@ -800,10 +813,13 @@
         mCacheUpdater.run();
 
         this.mTracingInstances.incrementAndGet();
+
+        Log.d(LOG_TAG, "Finished onTracingInstanceStart");
     }
 
     private synchronized void onTracingInstanceStop(
             int instanceIdx, ProtoLogDataSource.ProtoLogConfig config) {
+        Log.d(LOG_TAG, "Executing onTracingInstanceStop");
         this.mTracingInstances.decrementAndGet();
 
         final LogLevel defaultLogFrom = config.getDefaultGroupConfig().logFrom;
@@ -835,6 +851,7 @@
         }
 
         mCacheUpdater.run();
+        Log.d(LOG_TAG, "Finished onTracingInstanceStop");
     }
 
     private static void logAndPrintln(@Nullable PrintWriter pw, String msg) {
diff --git a/core/java/com/android/internal/protolog/ProtoLogCommandHandler.java b/core/java/com/android/internal/protolog/ProtoLogCommandHandler.java
new file mode 100644
index 0000000..3dab2e3
--- /dev/null
+++ b/core/java/com/android/internal/protolog/ProtoLogCommandHandler.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.ShellCommand;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+public class ProtoLogCommandHandler extends ShellCommand {
+    @NonNull
+    private final ProtoLogService mProtoLogService;
+    @Nullable
+    private final PrintWriter mPrintWriter;
+
+    public ProtoLogCommandHandler(@NonNull ProtoLogService protoLogService) {
+        this(protoLogService, null);
+    }
+
+    @VisibleForTesting
+    public ProtoLogCommandHandler(
+            @NonNull ProtoLogService protoLogService, @Nullable PrintWriter printWriter) {
+        this.mProtoLogService = protoLogService;
+        this.mPrintWriter = printWriter;
+    }
+
+    @Override
+    public int onCommand(String cmd) {
+        if (cmd == null) {
+            onHelp();
+            return 0;
+        }
+
+        return switch (cmd) {
+            case "groups" -> handleGroupsCommands(getNextArg());
+            case "logcat" -> handleLogcatCommands(getNextArg());
+            default -> handleDefaultCommands(cmd);
+        };
+    }
+
+    @Override
+    public void onHelp() {
+        PrintWriter pw = getOutPrintWriter();
+        pw.println("ProtoLog commands:");
+        pw.println("  help");
+        pw.println("    Print this help text.");
+        pw.println();
+        pw.println("  groups (list | status)");
+        pw.println("    list - lists all ProtoLog groups registered with ProtoLog service");
+        pw.println("    status <group> - print the status of a ProtoLog group");
+        pw.println();
+        pw.println("  logcat (enable | disable) <group>");
+        pw.println("    enable or disable ProtoLog to logcat");
+        pw.println();
+    }
+
+    @NonNull
+    @Override
+    public PrintWriter getOutPrintWriter() {
+        if (mPrintWriter != null) {
+            return mPrintWriter;
+        }
+
+        return super.getOutPrintWriter();
+    }
+
+    private int handleGroupsCommands(@Nullable String cmd) {
+        PrintWriter pw = getOutPrintWriter();
+
+        if (cmd == null) {
+            pw.println("Incomplete command. Use 'cmd protolog help' for guidance.");
+            return 0;
+        }
+
+        switch (cmd) {
+            case "list": {
+                final String[] availableGroups = mProtoLogService.getGroups();
+                if (availableGroups.length == 0) {
+                    pw.println("No ProtoLog groups registered with ProtoLog service.");
+                    return 0;
+                }
+
+                pw.println("ProtoLog groups registered with service:");
+                for (String group : availableGroups) {
+                    pw.println("- " + group);
+                }
+
+                return 0;
+            }
+            case "status": {
+                final String group = getNextArg();
+
+                if (group == null) {
+                    pw.println("Incomplete command. Use 'cmd protolog help' for guidance.");
+                    return 0;
+                }
+
+                pw.println("ProtoLog group " + group + "'s status:");
+
+                if (!Set.of(mProtoLogService.getGroups()).contains(group)) {
+                    pw.println("UNREGISTERED");
+                    return 0;
+                }
+
+                pw.println("LOG_TO_LOGCAT = " + mProtoLogService.isLoggingToLogcat(group));
+                return 0;
+            }
+            default: {
+                pw.println("Unknown command: " + cmd);
+                return -1;
+            }
+        }
+    }
+
+    private int handleLogcatCommands(@Nullable String cmd) {
+        PrintWriter pw = getOutPrintWriter();
+
+        if (cmd == null || peekNextArg() == null) {
+            pw.println("Incomplete command. Use 'cmd protolog help' for guidance.");
+            return 0;
+        }
+
+        switch (cmd) {
+            case "enable" -> {
+                mProtoLogService.enableProtoLogToLogcat(processGroups());
+                return 0;
+            }
+            case "disable" -> {
+                mProtoLogService.disableProtoLogToLogcat(processGroups());
+                return 0;
+            }
+            default -> {
+                pw.println("Unknown command: " + cmd);
+                return -1;
+            }
+        }
+    }
+
+    @NonNull
+    private String[] processGroups() {
+        if (getRemainingArgsCount() == 0) {
+            return mProtoLogService.getGroups();
+        }
+
+        final List<String> groups = new ArrayList<>();
+        while (getRemainingArgsCount() > 0) {
+            groups.add(getNextArg());
+        }
+
+        return groups.toArray(new String[0]);
+    }
+}
diff --git a/core/java/com/android/internal/protolog/ProtoLogService.java b/core/java/com/android/internal/protolog/ProtoLogService.java
new file mode 100644
index 0000000..2333a06
--- /dev/null
+++ b/core/java/com/android/internal/protolog/ProtoLogService.java
@@ -0,0 +1,458 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 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.Group.TAG;
+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.MESSAGE;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE_ID;
+import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.PROTOLOG_VIEWER_CONFIG;
+import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.TIMESTAMP;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.os.SystemClock;
+import android.tracing.perfetto.DataSourceParams;
+import android.tracing.perfetto.InitArguments;
+import android.tracing.perfetto.Producer;
+import android.util.Log;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+/**
+ * The ProtoLog service is responsible for orchestrating centralized actions of the protolog tracing
+ * system. Currently this service has the following roles:
+ * - Handle shell commands to toggle logging ProtoLog messages for specified groups to logcat.
+ * - Handle viewer config dumping (the mapping from message hash to message string) for all protolog
+ *   clients. This is for two reasons: firstly, because client processes might be frozen so might
+ *   not response to the request to dump their viewer config when the trace is stopped; secondly,
+ *   multiple processes might be running the same code with the same viewer config, this centralized
+ *   service ensures we don't dump the same viewer config multiple times across processes.
+ * <p>
+ * {@link com.android.internal.protolog.IProtoLogClient ProtoLog clients} register themselves to
+ * this service on initialization.
+ * <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";
+
+    private final ProtoLogDataSource mDataSource = new ProtoLogDataSource(
+            this::onTracingInstanceStart,
+            this::onTracingInstanceFlush,
+            this::onTracingInstanceStop
+    );
+
+    /**
+     * Keeps track of how many of each viewer config file is currently registered.
+     * Use to keep track of which viewer config files are actively being used in tracing and might
+     * need to be dumped on flush.
+     */
+    private final Map<String, Integer> mConfigFileCounts = new HashMap<>();
+    /**
+     * Keeps track of the viewer config file of each client if available.
+     */
+    private final Map<IProtoLogClient, String> mClientConfigFiles = new HashMap<>();
+
+    /**
+     * Keeps track of all the protolog groups that have been registered by clients and are still
+     * being actively traced.
+     */
+    private final Set<String> mRegisteredGroups = new HashSet<>();
+    /**
+     * Keeps track of all the clients that are actively tracing a given protolog group.
+     */
+    private final Map<String, Set<IProtoLogClient>> mGroupToClients = new HashMap<>();
+
+    /**
+     * Keeps track of whether or not a given group should be logged to logcat.
+     * True when logging to logcat, false otherwise.
+     */
+    private final Map<String, Boolean> mLogGroupToLogcatStatus = new TreeMap<>();
+
+    /**
+     * Keeps track of all the tracing instance ids that are actively running for ProtoLog.
+     */
+    private final Set<Integer> mRunningInstances = new HashSet<>();
+
+    private final ViewerConfigFileTracer mViewerConfigFileTracer;
+
+    public ProtoLogService() {
+        this(ProtoLogService::dumpTransitionTraceConfig);
+    }
+
+    @VisibleForTesting
+    public ProtoLogService(@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.
+        Producer.init(InitArguments.DEFAULTS);
+        final var params = new DataSourceParams.Builder()
+                .setBufferExhaustedPolicy(DataSourceParams.PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP)
+                .build();
+        mDataSource.register(params);
+
+        mViewerConfigFileTracer = tracer;
+    }
+
+    public static class RegisterClientArgs extends IRegisterClientArgs.Stub {
+        /**
+         * The viewer config file to be registered for this client ProtoLog process.
+         */
+        @Nullable
+        private String mViewerConfigFile = null;
+        /**
+         * The list of all groups that this client protolog process supports and might trace.
+         */
+        @NonNull
+        private String[] mGroups = new String[0];
+        /**
+         * The default logcat status of the ProtoLog client. True is logging to logcat, false
+         * otherwise. The indices should match the indices in {@link mGroups}.
+         */
+        @NonNull
+        private boolean[] mLogcatStatus = new boolean[0];
+
+        public record GroupConfig(@NonNull String group, boolean logToLogcat) {}
+
+        /**
+         * Specify groups to register with this client that will be used for protologging in this
+         * process.
+         * @param groups to register with this client.
+         * @return self
+         */
+        public RegisterClientArgs setGroups(GroupConfig... groups) {
+            mGroups = new String[groups.length];
+            mLogcatStatus = new boolean[groups.length];
+
+            for (int i = 0; i < groups.length; i++) {
+                mGroups[i] = groups[i].group;
+                mLogcatStatus[i] = groups[i].logToLogcat;
+            }
+
+            return this;
+        }
+
+        /**
+         * Set the viewer config file that the logs in this process are using.
+         * @param viewerConfigFile The file path of the viewer config.
+         * @return self
+         */
+        public RegisterClientArgs setViewerConfigFile(@NonNull String viewerConfigFile) {
+            mViewerConfigFile = viewerConfigFile;
+
+            return this;
+        }
+
+        @Override
+        @NonNull
+        public String[] getGroups() {
+            return mGroups;
+        }
+
+        @Override
+        @NonNull
+        public boolean[] getGroupsDefaultLogcatStatus() {
+            return mLogcatStatus;
+        }
+
+        @Nullable
+        @Override
+        public String getViewerConfigFile() {
+            return mViewerConfigFile;
+        }
+    }
+
+    @FunctionalInterface
+    public interface ViewerConfigFileTracer {
+        /**
+         * Write the viewer config data to the trace buffer.
+         *
+         * @param dataSource The target datasource to write the viewer config to.
+         * @param viewerConfigFilePath The path of the viewer config file which contains the data we
+         *                             want to write to the trace buffer.
+         * @throws FileNotFoundException if the viewerConfigFilePath is invalid.
+         */
+        void trace(@NonNull ProtoLogDataSource dataSource, @NonNull String viewerConfigFilePath)
+                throws FileNotFoundException;
+    }
+
+    @Override
+    public void registerClient(@NonNull IProtoLogClient client, @NonNull IRegisterClientArgs args)
+            throws RemoteException {
+        client.asBinder().linkToDeath(() -> onClientBinderDeath(client), /* flags */ 0);
+
+        final String viewerConfigFile = args.getViewerConfigFile();
+        if (viewerConfigFile != null) {
+            registerViewerConfigFile(client, viewerConfigFile);
+        }
+
+        registerGroups(client, args.getGroups(), args.getGroupsDefaultLogcatStatus());
+    }
+
+    @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 ProtoLogCommandHandler(this)
+                .exec(this, in, out, err, args, callback, resultReceiver);
+    }
+
+    /**
+     * Get the list of groups clients have registered to the protolog service.
+     * @return The list of ProtoLog groups registered with this service.
+     */
+    @NonNull
+    public String[] getGroups() {
+        return mRegisteredGroups.toArray(new String[0]);
+    }
+
+    /**
+     * Enable logging target groups to logcat.
+     * @param groups we want to enable logging them to logcat for.
+     */
+    public void enableProtoLogToLogcat(String... groups) {
+        toggleProtoLogToLogcat(true, groups);
+    }
+
+    /**
+     * Disable logging target groups to logcat.
+     * @param groups we want to disable from being logged to logcat.
+     */
+    public void disableProtoLogToLogcat(String... groups) {
+        toggleProtoLogToLogcat(false, groups);
+    }
+
+    /**
+     * Check if a group is logging to logcat
+     * @param group The group we want to check for
+     * @return True iff we are logging this group to logcat.
+     */
+    public boolean isLoggingToLogcat(@NonNull String group) {
+        final Boolean isLoggingToLogcat = mLogGroupToLogcatStatus.get(group);
+
+        if (isLoggingToLogcat == null) {
+            throw new RuntimeException(
+                    "Trying to get logcat logging status of non-registered group " + group);
+        }
+
+        return isLoggingToLogcat;
+    }
+
+    private void registerViewerConfigFile(
+            @NonNull IProtoLogClient client, @NonNull String viewerConfigFile) {
+        final var count = mConfigFileCounts.getOrDefault(viewerConfigFile, 0);
+        mConfigFileCounts.put(viewerConfigFile, count + 1);
+        mClientConfigFiles.put(client, viewerConfigFile);
+    }
+
+    private void registerGroups(@NonNull IProtoLogClient client, @NonNull String[] groups,
+            @NonNull boolean[] logcatStatuses) throws RemoteException {
+        if (groups.length != logcatStatuses.length) {
+            throw new RuntimeException(
+                    "Expected groups and logcatStatuses to have the same length, "
+                        + "but groups has length " + groups.length
+                        + " and logcatStatuses has length " + logcatStatuses.length);
+        }
+
+        for (int i = 0; i < groups.length; i++) {
+            String group = groups[i];
+            boolean logcatStatus = logcatStatuses[i];
+
+            mRegisteredGroups.add(group);
+
+            mGroupToClients.putIfAbsent(group, new HashSet<>());
+            mGroupToClients.get(group).add(client);
+
+            if (!mLogGroupToLogcatStatus.containsKey(group)) {
+                mLogGroupToLogcatStatus.put(group, logcatStatus);
+            }
+
+            boolean requestedLogToLogcat = mLogGroupToLogcatStatus.get(group);
+            if (requestedLogToLogcat != logcatStatus) {
+                client.toggleLogcat(requestedLogToLogcat, new String[] { group });
+            }
+        }
+    }
+
+    private void toggleProtoLogToLogcat(boolean enabled, @NonNull String[] groups) {
+        final var clientToGroups = new HashMap<IProtoLogClient, Set<String>>();
+
+        for (String group : groups) {
+            final var clients = mGroupToClients.get(group);
+
+            if (clients == null) {
+                // No clients associated to this group
+                Log.w(LOG_TAG, "Attempting to toggle log to logcat for group " + group
+                        + " with no registered clients.");
+                continue;
+            }
+
+            for (IProtoLogClient client : clients) {
+                clientToGroups.putIfAbsent(client, new HashSet<>());
+                clientToGroups.get(client).add(group);
+            }
+        }
+
+        for (IProtoLogClient client : clientToGroups.keySet()) {
+            try {
+                client.toggleLogcat(enabled, clientToGroups.get(client).toArray(new String[0]));
+            } catch (RemoteException e) {
+                throw new RuntimeException(
+                        "Failed to toggle logcat status for groups on client", e);
+            }
+        }
+
+        for (String group : groups) {
+            mLogGroupToLogcatStatus.put(group, enabled);
+        }
+    }
+
+    private void onTracingInstanceStart(int instanceIdx, ProtoLogDataSource.ProtoLogConfig config) {
+        mRunningInstances.add(instanceIdx);
+    }
+
+    private void onTracingInstanceFlush() {
+        for (String fileName : mConfigFileCounts.keySet()) {
+            try {
+                mViewerConfigFileTracer.trace(mDataSource, fileName);
+            } catch (FileNotFoundException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    private void onTracingInstanceStop(int instanceIdx, ProtoLogDataSource.ProtoLogConfig config) {
+        mRunningInstances.remove(instanceIdx);
+    }
+
+    private static void dumpTransitionTraceConfig(@NonNull ProtoLogDataSource dataSource,
+            @NonNull String viewerConfigFilePath) throws FileNotFoundException {
+        final var pis = new ProtoInputStream(new FileInputStream(viewerConfigFilePath));
+
+        dataSource.trace(ctx -> {
+            try {
+                final ProtoOutputStream os = ctx.newTracePacket();
+
+                os.write(TIMESTAMP, SystemClock.elapsedRealtimeNanos());
+
+                final long outProtologViewerConfigToken = os.start(PROTOLOG_VIEWER_CONFIG);
+                while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+                    switch (pis.getFieldNumber()) {
+                        case (int) MESSAGES -> writeViewerConfigMessage(pis, os);
+                        case (int) GROUPS -> writeViewerConfigGroup(pis, os);
+                    }
+                }
+
+                os.end(outProtologViewerConfigToken);
+            } catch (IOException e) {
+                Log.e(LOG_TAG, "Failed to read ProtoLog viewer config to dump on tracing end", e);
+            }
+        });
+    }
+
+    private void onClientBinderDeath(@NonNull IProtoLogClient client) {
+        // Dump the tracing config now if no other client is going to dump the same config file.
+        String configFile = mClientConfigFiles.get(client);
+        if (configFile != null) {
+            final var newCount = mConfigFileCounts.get(configFile) - 1;
+            mConfigFileCounts.put(configFile, newCount);
+            boolean lastProcessWithViewerConfig = newCount == 0;
+            if (lastProcessWithViewerConfig) {
+                try {
+                    mViewerConfigFileTracer.trace(mDataSource, configFile);
+                } catch (FileNotFoundException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        }
+    }
+
+    private static void writeViewerConfigGroup(
+            @NonNull ProtoInputStream pis, @NonNull ProtoOutputStream os) throws IOException {
+        final long inGroupToken = pis.start(GROUPS);
+        final long outGroupToken = os.start(GROUPS);
+
+        while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pis.getFieldNumber()) {
+                case (int) ID -> {
+                    int id = pis.readInt(ID);
+                    os.write(ID, id);
+                }
+                case (int) NAME -> {
+                    String name = pis.readString(NAME);
+                    os.write(NAME, name);
+                }
+                case (int) TAG -> {
+                    String tag = pis.readString(TAG);
+                    os.write(TAG, tag);
+                }
+                default ->
+                    throw new RuntimeException(
+                            "Unexpected field id " + pis.getFieldNumber());
+            }
+        }
+
+        pis.end(inGroupToken);
+        os.end(outGroupToken);
+    }
+
+    private static void writeViewerConfigMessage(
+            @NonNull ProtoInputStream pis, @NonNull ProtoOutputStream os) throws IOException {
+        final long inMessageToken = pis.start(MESSAGES);
+        final long outMessagesToken = os.start(MESSAGES);
+
+        while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pis.getFieldNumber()) {
+                case (int) MESSAGE_ID -> os.write(MESSAGE_ID,
+                        pis.readLong(MESSAGE_ID));
+                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));
+                default ->
+                    throw new RuntimeException(
+                            "Unexpected field id " + pis.getFieldNumber());
+            }
+        }
+
+        pis.end(inMessageToken);
+        os.end(outMessagesToken);
+    }
+}
diff --git a/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java b/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java
index 7240aff..3adc6b2 100644
--- a/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java
+++ b/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.statusbar;
 
+import android.inputmethodservice.InputMethodService.BackDispositionMode;
+import android.inputmethodservice.InputMethodService.ImeWindowVisibility;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.ArrayMap;
@@ -30,7 +32,9 @@
     public final int mDisabledFlags1;                   // switch[0]
     public final int mAppearance;                       // switch[1]
     public final AppearanceRegion[] mAppearanceRegions; // switch[2]
+    @ImeWindowVisibility
     public final int mImeWindowVis;                     // switch[3]
+    @BackDispositionMode
     public final int mImeBackDisposition;               // switch[4]
     public final boolean mShowImeSwitcher;              // switch[5]
     public final int mDisabledFlags2;                   // switch[6]
@@ -42,10 +46,11 @@
     public final LetterboxDetails[] mLetterboxDetails;
 
     public RegisterStatusBarResult(ArrayMap<String, StatusBarIcon> icons, int disabledFlags1,
-            int appearance, AppearanceRegion[] appearanceRegions, int imeWindowVis,
-            int imeBackDisposition, boolean showImeSwitcher, int disabledFlags2,
-            boolean navbarColorManagedByIme, int behavior, int requestedVisibleTypes,
-            String packageName, int transientBarTypes, LetterboxDetails[] letterboxDetails) {
+            int appearance, AppearanceRegion[] appearanceRegions,
+            @ImeWindowVisibility int imeWindowVis, @BackDispositionMode int imeBackDisposition,
+            boolean showImeSwitcher, int disabledFlags2, boolean navbarColorManagedByIme,
+            int behavior, int requestedVisibleTypes, String packageName, int transientBarTypes,
+            LetterboxDetails[] letterboxDetails) {
         mIcons = new ArrayMap<>(icons);
         mDisabledFlags1 = disabledFlags1;
         mAppearance = appearance;
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index 8f00f79..1e2cad4 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -620,10 +620,10 @@
     }
 
     /**
-     * Adds value to given array if not already present, providing set-like
-     * behavior.
+     * Adds value to given array. The method allows duplicate values.
      */
-    public static boolean[] appendBoolean(@Nullable boolean[] cur, boolean val) {
+    public static boolean[] appendBooleanDuplicatesAllowed(@Nullable boolean[] cur,
+            boolean val) {
         if (cur == null) {
             return new boolean[] { val };
         }
diff --git a/core/java/com/android/internal/vibrator/persistence/SerializedAmplitudeStepWaveform.java b/core/java/com/android/internal/vibrator/persistence/SerializedAmplitudeStepWaveform.java
index 15ecedd..cd7dcfd 100644
--- a/core/java/com/android/internal/vibrator/persistence/SerializedAmplitudeStepWaveform.java
+++ b/core/java/com/android/internal/vibrator/persistence/SerializedAmplitudeStepWaveform.java
@@ -29,7 +29,7 @@
 import android.util.IntArray;
 import android.util.LongArray;
 
-import com.android.internal.vibrator.persistence.SerializedVibrationEffect.SerializedSegment;
+import com.android.internal.vibrator.persistence.SerializedComposedEffect.SerializedSegment;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 
diff --git a/core/java/com/android/internal/vibrator/persistence/SerializedVibrationEffect.java b/core/java/com/android/internal/vibrator/persistence/SerializedComposedEffect.java
similarity index 86%
rename from core/java/com/android/internal/vibrator/persistence/SerializedVibrationEffect.java
rename to core/java/com/android/internal/vibrator/persistence/SerializedComposedEffect.java
index 23df304..6c562c9 100644
--- a/core/java/com/android/internal/vibrator/persistence/SerializedVibrationEffect.java
+++ b/core/java/com/android/internal/vibrator/persistence/SerializedComposedEffect.java
@@ -29,24 +29,24 @@
 import java.util.Arrays;
 
 /**
- * Serialized representation of a {@link VibrationEffect}.
+ * Serialized representation of a {@link VibrationEffect.Composed}.
  *
  * <p>The vibration is represented by a list of serialized segments that can be added to a
  * {@link VibrationEffect.Composition} during the {@link #deserialize()} procedure.
  *
  * @hide
  */
-final class SerializedVibrationEffect implements XmlSerializedVibration<VibrationEffect> {
+final class SerializedComposedEffect implements XmlSerializedVibration<VibrationEffect.Composed> {
 
     @NonNull
     private final SerializedSegment[] mSegments;
 
-    SerializedVibrationEffect(@NonNull SerializedSegment segment) {
+    SerializedComposedEffect(@NonNull SerializedSegment segment) {
         requireNonNull(segment);
         mSegments = new SerializedSegment[]{ segment };
     }
 
-    SerializedVibrationEffect(@NonNull SerializedSegment[] segments) {
+    SerializedComposedEffect(@NonNull SerializedSegment[] segments) {
         requireNonNull(segments);
         checkArgument(segments.length > 0, "Unsupported empty vibration");
         mSegments = segments;
@@ -54,12 +54,12 @@
 
     @NonNull
     @Override
-    public VibrationEffect deserialize() {
+    public VibrationEffect.Composed deserialize() {
         VibrationEffect.Composition composition = VibrationEffect.startComposition();
         for (SerializedSegment segment : mSegments) {
             segment.deserializeIntoComposition(composition);
         }
-        return composition.compose();
+        return (VibrationEffect.Composed) composition.compose();
     }
 
     @Override
@@ -79,7 +79,7 @@
 
     @Override
     public String toString() {
-        return "SerializedVibrationEffect{"
+        return "SerializedComposedEffect{"
                 + "segments=" + Arrays.toString(mSegments)
                 + '}';
     }
diff --git a/core/java/com/android/internal/vibrator/persistence/SerializedCompositionPrimitive.java b/core/java/com/android/internal/vibrator/persistence/SerializedCompositionPrimitive.java
index db5c7ff..862f7cb 100644
--- a/core/java/com/android/internal/vibrator/persistence/SerializedCompositionPrimitive.java
+++ b/core/java/com/android/internal/vibrator/persistence/SerializedCompositionPrimitive.java
@@ -27,7 +27,7 @@
 import android.os.VibrationEffect;
 import android.os.vibrator.PrimitiveSegment;
 
-import com.android.internal.vibrator.persistence.SerializedVibrationEffect.SerializedSegment;
+import com.android.internal.vibrator.persistence.SerializedComposedEffect.SerializedSegment;
 import com.android.internal.vibrator.persistence.XmlConstants.PrimitiveEffectName;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
diff --git a/core/java/com/android/internal/vibrator/persistence/SerializedPredefinedEffect.java b/core/java/com/android/internal/vibrator/persistence/SerializedPredefinedEffect.java
index 8924311..a6f48a4 100644
--- a/core/java/com/android/internal/vibrator/persistence/SerializedPredefinedEffect.java
+++ b/core/java/com/android/internal/vibrator/persistence/SerializedPredefinedEffect.java
@@ -25,7 +25,7 @@
 import android.os.VibrationEffect;
 import android.os.vibrator.PrebakedSegment;
 
-import com.android.internal.vibrator.persistence.SerializedVibrationEffect.SerializedSegment;
+import com.android.internal.vibrator.persistence.SerializedComposedEffect.SerializedSegment;
 import com.android.internal.vibrator.persistence.XmlConstants.PredefinedEffectName;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
diff --git a/core/java/com/android/internal/vibrator/persistence/SerializedVendorEffect.java b/core/java/com/android/internal/vibrator/persistence/SerializedVendorEffect.java
new file mode 100644
index 0000000..aa1b0a23
--- /dev/null
+++ b/core/java/com/android/internal/vibrator/persistence/SerializedVendorEffect.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 com.android.internal.vibrator.persistence;
+
+import static com.android.internal.vibrator.persistence.XmlConstants.TAG_VENDOR_EFFECT;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.os.PersistableBundle;
+import android.os.VibrationEffect;
+import android.text.TextUtils;
+import android.util.Base64;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+/**
+ * Serialized representation of a {@link VibrationEffect.VendorEffect}.
+ *
+ * <p>The vibration is represented by an opaque {@link PersistableBundle} that can be used by
+ * {@link VibrationEffect#createVendorEffect(PersistableBundle)} during the {@link #deserialize()}
+ * procedure.
+ *
+ * @hide
+ */
+final class SerializedVendorEffect implements XmlSerializedVibration<VibrationEffect.VendorEffect> {
+
+    @NonNull
+    private final PersistableBundle mVendorData;
+
+    SerializedVendorEffect(@NonNull PersistableBundle vendorData) {
+        requireNonNull(vendorData);
+        mVendorData = vendorData;
+    }
+
+    @SuppressLint("MissingPermission")
+    @NonNull
+    @Override
+    public VibrationEffect.VendorEffect deserialize() {
+        return (VibrationEffect.VendorEffect) VibrationEffect.createVendorEffect(mVendorData);
+    }
+
+    @Override
+    public void write(@NonNull TypedXmlSerializer serializer)
+            throws IOException {
+        serializer.startTag(XmlConstants.NAMESPACE, XmlConstants.TAG_VIBRATION_EFFECT);
+        writeContent(serializer);
+        serializer.endTag(XmlConstants.NAMESPACE, XmlConstants.TAG_VIBRATION_EFFECT);
+    }
+
+    @Override
+    public void writeContent(@NonNull TypedXmlSerializer serializer) throws IOException {
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        mVendorData.writeToStream(outputStream);
+
+        serializer.startTag(XmlConstants.NAMESPACE, XmlConstants.TAG_VENDOR_EFFECT);
+        serializer.text(Base64.encodeToString(outputStream.toByteArray(), Base64.NO_WRAP));
+        serializer.endTag(XmlConstants.NAMESPACE, XmlConstants.TAG_VENDOR_EFFECT);
+    }
+
+    @Override
+    public String toString() {
+        return "SerializedVendorEffect{"
+                + "vendorData=" + mVendorData
+                + '}';
+    }
+
+    /** Parser implementation for {@link SerializedVendorEffect}. */
+    static final class Parser {
+
+        @NonNull
+        static SerializedVendorEffect parseNext(@NonNull TypedXmlPullParser parser,
+                @XmlConstants.Flags int flags) throws XmlParserException, IOException {
+            XmlValidator.checkStartTag(parser, TAG_VENDOR_EFFECT);
+            XmlValidator.checkTagHasNoUnexpectedAttributes(parser);
+
+            PersistableBundle vendorData;
+            XmlReader.readNextText(parser, TAG_VENDOR_EFFECT);
+
+            try {
+                String text = parser.getText().trim();
+                XmlValidator.checkParserCondition(!text.isEmpty(),
+                        "Expected tag %s to have base64 representation of vendor data, got empty",
+                        TAG_VENDOR_EFFECT);
+
+                vendorData = PersistableBundle.readFromStream(
+                        new ByteArrayInputStream(Base64.decode(text, Base64.DEFAULT)));
+                XmlValidator.checkParserCondition(!vendorData.isEmpty(),
+                        "Expected tag %s to have non-empty vendor data, got empty bundle",
+                        TAG_VENDOR_EFFECT);
+            } catch (IllegalArgumentException | NullPointerException e) {
+                throw new XmlParserException(
+                        TextUtils.formatSimple(
+                                "Expected base64 representation of vendor data in tag %s, got %s",
+                                TAG_VENDOR_EFFECT, parser.getText()),
+                        e);
+            } catch (IOException e) {
+                throw new XmlParserException("Error reading vendor data from decoded bytes", e);
+            }
+
+            // Consume tag
+            XmlReader.readEndTag(parser);
+
+            return new SerializedVendorEffect(vendorData);
+        }
+    }
+}
diff --git a/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlParser.java b/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlParser.java
index 2b8b61d..a9fbcaf 100644
--- a/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlParser.java
+++ b/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlParser.java
@@ -18,13 +18,15 @@
 
 import static com.android.internal.vibrator.persistence.XmlConstants.TAG_PREDEFINED_EFFECT;
 import static com.android.internal.vibrator.persistence.XmlConstants.TAG_PRIMITIVE_EFFECT;
+import static com.android.internal.vibrator.persistence.XmlConstants.TAG_VENDOR_EFFECT;
 import static com.android.internal.vibrator.persistence.XmlConstants.TAG_VIBRATION_EFFECT;
 import static com.android.internal.vibrator.persistence.XmlConstants.TAG_WAVEFORM_EFFECT;
 
 import android.annotation.NonNull;
 import android.os.VibrationEffect;
+import android.os.vibrator.Flags;
 
-import com.android.internal.vibrator.persistence.SerializedVibrationEffect.SerializedSegment;
+import com.android.internal.vibrator.persistence.SerializedComposedEffect.SerializedSegment;
 import com.android.modules.utils.TypedXmlPullParser;
 
 import java.io.IOException;
@@ -80,6 +82,16 @@
  *   }
  * </pre>
  *
+ * * Vendor vibration effects
+ *
+ * <pre>
+ *   {@code
+ *     <vibration-effect>
+ *       <vendor-effect>base64-representation-of-persistable-bundle</vendor-effect>
+ *     </vibration-effect>
+ *   }
+ * </pre>
+ *
  * @hide
  */
 public class VibrationEffectXmlParser {
@@ -87,11 +99,9 @@
     /**
      * Parses the current XML tag with all nested tags into a single {@link XmlSerializedVibration}
      * wrapping a {@link VibrationEffect}.
-     *
-     * @see XmlParser#parseTag(TypedXmlPullParser)
      */
     @NonNull
-    public static XmlSerializedVibration<VibrationEffect> parseTag(
+    public static XmlSerializedVibration<? extends VibrationEffect> parseTag(
             @NonNull TypedXmlPullParser parser, @XmlConstants.Flags int flags)
             throws XmlParserException, IOException {
         XmlValidator.checkStartTag(parser, TAG_VIBRATION_EFFECT);
@@ -107,8 +117,9 @@
      * <p>This can be reused for reading a vibration from an XML root tag or from within a combined
      * vibration, but it should always be called from places that validates the top level tag.
      */
-    static SerializedVibrationEffect parseVibrationContent(TypedXmlPullParser parser,
-            @XmlConstants.Flags int flags) throws XmlParserException, IOException {
+    private static XmlSerializedVibration<? extends VibrationEffect> parseVibrationContent(
+            TypedXmlPullParser parser, @XmlConstants.Flags int flags)
+            throws XmlParserException, IOException {
         String vibrationTagName = parser.getName();
         int vibrationTagDepth = parser.getDepth();
 
@@ -116,11 +127,16 @@
                 XmlReader.readNextTagWithin(parser, vibrationTagDepth),
                 "Unsupported empty vibration tag");
 
-        SerializedVibrationEffect serializedVibration;
+        XmlSerializedVibration<? extends VibrationEffect> serializedVibration;
 
         switch (parser.getName()) {
+            case TAG_VENDOR_EFFECT:
+                if (Flags.vendorVibrationEffects()) {
+                    serializedVibration = SerializedVendorEffect.Parser.parseNext(parser, flags);
+                    break;
+                } // else fall through
             case TAG_PREDEFINED_EFFECT:
-                serializedVibration = new SerializedVibrationEffect(
+                serializedVibration = new SerializedComposedEffect(
                         SerializedPredefinedEffect.Parser.parseNext(parser, flags));
                 break;
             case TAG_PRIMITIVE_EFFECT:
@@ -128,11 +144,11 @@
                 do { // First primitive tag already open
                     primitives.add(SerializedCompositionPrimitive.Parser.parseNext(parser));
                 } while (XmlReader.readNextTagWithin(parser, vibrationTagDepth));
-                serializedVibration = new SerializedVibrationEffect(
+                serializedVibration = new SerializedComposedEffect(
                         primitives.toArray(new SerializedSegment[primitives.size()]));
                 break;
             case TAG_WAVEFORM_EFFECT:
-                serializedVibration = new SerializedVibrationEffect(
+                serializedVibration = new SerializedComposedEffect(
                         SerializedAmplitudeStepWaveform.Parser.parseNext(parser));
                 break;
             default:
diff --git a/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlSerializer.java b/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlSerializer.java
index f561c14..d74a23d 100644
--- a/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlSerializer.java
+++ b/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlSerializer.java
@@ -17,13 +17,15 @@
 package com.android.internal.vibrator.persistence;
 
 import android.annotation.NonNull;
+import android.os.PersistableBundle;
 import android.os.VibrationEffect;
+import android.os.vibrator.Flags;
 import android.os.vibrator.PrebakedSegment;
 import android.os.vibrator.PrimitiveSegment;
 import android.os.vibrator.StepSegment;
 import android.os.vibrator.VibrationEffectSegment;
 
-import com.android.internal.vibrator.persistence.SerializedVibrationEffect.SerializedSegment;
+import com.android.internal.vibrator.persistence.SerializedComposedEffect.SerializedSegment;
 import com.android.internal.vibrator.persistence.XmlConstants.PredefinedEffectName;
 import com.android.internal.vibrator.persistence.XmlConstants.PrimitiveEffectName;
 
@@ -41,6 +43,7 @@
  *     <li>{@link VibrationEffect#createWaveform(long[], int[], int)}
  *     <li>A composition created exclusively via
  *         {@link VibrationEffect.Composition#addPrimitive(int, float, int)}
+ *     <li>{@link VibrationEffect#createVendorEffect(PersistableBundle)}
  * </ul>
  *
  * @hide
@@ -49,13 +52,16 @@
 
     /**
      * Creates a serialized representation of the input {@code vibration}.
-     *
-     * @see XmlSerializer#serialize
      */
     @NonNull
-    public static XmlSerializedVibration<VibrationEffect> serialize(
+    public static XmlSerializedVibration<? extends VibrationEffect> serialize(
             @NonNull VibrationEffect vibration, @XmlConstants.Flags int flags)
             throws XmlSerializerException {
+        if (Flags.vendorVibrationEffects()
+                && (vibration instanceof VibrationEffect.VendorEffect vendorEffect)) {
+            return serializeVendorEffect(vendorEffect);
+        }
+
         XmlValidator.checkSerializerCondition(vibration instanceof VibrationEffect.Composed,
                 "Unsupported VibrationEffect type %s", vibration);
 
@@ -73,7 +79,7 @@
         return serializeWaveformEffect(composed);
     }
 
-    private static SerializedVibrationEffect serializePredefinedEffect(
+    private static SerializedComposedEffect serializePredefinedEffect(
             VibrationEffect.Composed effect, @XmlConstants.Flags int flags)
             throws XmlSerializerException {
         List<VibrationEffectSegment> segments = effect.getSegments();
@@ -81,10 +87,15 @@
                 "Unsupported repeating predefined effect %s", effect);
         XmlValidator.checkSerializerCondition(segments.size() == 1,
                 "Unsupported multiple segments in predefined effect %s", effect);
-        return new SerializedVibrationEffect(serializePrebakedSegment(segments.get(0), flags));
+        return new SerializedComposedEffect(serializePrebakedSegment(segments.get(0), flags));
     }
 
-    private static SerializedVibrationEffect serializePrimitiveEffect(
+    private static SerializedVendorEffect serializeVendorEffect(
+            VibrationEffect.VendorEffect effect) {
+        return new SerializedVendorEffect(effect.getVendorData());
+    }
+
+    private static SerializedComposedEffect serializePrimitiveEffect(
             VibrationEffect.Composed effect) throws XmlSerializerException {
         List<VibrationEffectSegment> segments = effect.getSegments();
         XmlValidator.checkSerializerCondition(effect.getRepeatIndex() == -1,
@@ -95,10 +106,10 @@
             primitives[i] = serializePrimitiveSegment(segments.get(i));
         }
 
-        return new SerializedVibrationEffect(primitives);
+        return new SerializedComposedEffect(primitives);
     }
 
-    private static SerializedVibrationEffect serializeWaveformEffect(
+    private static SerializedComposedEffect serializeWaveformEffect(
             VibrationEffect.Composed effect) throws XmlSerializerException {
         SerializedAmplitudeStepWaveform.Builder serializedWaveformBuilder =
                 new SerializedAmplitudeStepWaveform.Builder();
@@ -120,7 +131,7 @@
                     segment.getDuration(), toAmplitudeInt(segment.getAmplitude()));
         }
 
-        return new SerializedVibrationEffect(serializedWaveformBuilder.build());
+        return new SerializedComposedEffect(serializedWaveformBuilder.build());
     }
 
     private static SerializedPredefinedEffect serializePrebakedSegment(
diff --git a/core/java/com/android/internal/vibrator/persistence/XmlConstants.java b/core/java/com/android/internal/vibrator/persistence/XmlConstants.java
index 8b92153..2a55d99 100644
--- a/core/java/com/android/internal/vibrator/persistence/XmlConstants.java
+++ b/core/java/com/android/internal/vibrator/persistence/XmlConstants.java
@@ -40,6 +40,7 @@
 
     public static final String TAG_PREDEFINED_EFFECT = "predefined-effect";
     public static final String TAG_PRIMITIVE_EFFECT = "primitive-effect";
+    public static final String TAG_VENDOR_EFFECT = "vendor-effect";
     public static final String TAG_WAVEFORM_EFFECT = "waveform-effect";
     public static final String TAG_WAVEFORM_ENTRY = "waveform-entry";
     public static final String TAG_REPEATING = "repeating";
diff --git a/core/java/com/android/internal/vibrator/persistence/XmlParser.java b/core/java/com/android/internal/vibrator/persistence/XmlParser.java
deleted file mode 100644
index 6712f1c..0000000
--- a/core/java/com/android/internal/vibrator/persistence/XmlParser.java
+++ /dev/null
@@ -1,51 +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.internal.vibrator.persistence;
-
-import android.annotation.NonNull;
-
-import com.android.modules.utils.TypedXmlPullParser;
-
-import java.io.IOException;
-
-/**
- * Parse XML tags into valid {@link XmlSerializedVibration} instances.
- *
- * @param <T> The vibration type that will be parsed.
- * @see XmlSerializedVibration
- * @hide
- */
-@FunctionalInterface
-public interface XmlParser<T> {
-
-    /**
-     * Parses the current XML tag with all nested tags into a single {@link XmlSerializedVibration}.
-     *
-     * <p>This method will consume nested XML tags until it finds the
-     * {@link TypedXmlPullParser#END_TAG} for the current tag.
-     *
-     * <p>The vibration reconstructed by the returned {@link XmlSerializedVibration#deserialize()}
-     * is guaranteed to be valid. This method will throw an exception otherwise.
-     *
-     * @param pullParser The {@link TypedXmlPullParser} with the input XML.
-     * @return The parsed vibration wrapped in a {@link XmlSerializedVibration} representation.
-     * @throws IOException        On any I/O error while reading the input XML
-     * @throws XmlParserException If the XML content does not represent a valid vibration.
-     */
-    XmlSerializedVibration<T> parseTag(@NonNull TypedXmlPullParser pullParser)
-            throws XmlParserException, IOException;
-}
diff --git a/core/java/com/android/internal/vibrator/persistence/XmlParserException.java b/core/java/com/android/internal/vibrator/persistence/XmlParserException.java
index 7507864..e2b30e7 100644
--- a/core/java/com/android/internal/vibrator/persistence/XmlParserException.java
+++ b/core/java/com/android/internal/vibrator/persistence/XmlParserException.java
@@ -23,7 +23,6 @@
 /**
  * Represents an error while parsing a vibration XML input.
  *
- * @see XmlParser
  * @hide
  */
 public final class XmlParserException extends Exception {
diff --git a/core/java/com/android/internal/vibrator/persistence/XmlReader.java b/core/java/com/android/internal/vibrator/persistence/XmlReader.java
index a5ace84..0ac6fef 100644
--- a/core/java/com/android/internal/vibrator/persistence/XmlReader.java
+++ b/core/java/com/android/internal/vibrator/persistence/XmlReader.java
@@ -130,6 +130,25 @@
     }
 
     /**
+     * Read the next element, ignoring comments and ignorable whitespace, and returns only if it's a
+     * {@link XmlPullParser#TEXT}. Any other tag will fail this check.
+     *
+     * <p>The parser will be pointing to the first next element after skipping comments,
+     * instructions and ignorable whitespace.
+     */
+    public static void readNextText(TypedXmlPullParser parser, String tagName)
+            throws XmlParserException, IOException {
+        try {
+            int type = parser.next(); // skips comments, instruction tokens and ignorable whitespace
+            XmlValidator.checkParserCondition(type == XmlPullParser.TEXT,
+                    "Unexpected event %s of type %d, expected text event inside tag %s",
+                    parser.getName(), type, tagName);
+        } catch (XmlPullParserException e) {
+            throw XmlParserException.createFromPullParserException("text event", e);
+        }
+    }
+
+    /**
      * Check parser has a {@link XmlPullParser#END_TAG} as the next tag, with no nested tags.
      *
      * <p>The parser will be pointing to the end tag after this method.
diff --git a/core/java/com/android/internal/vibrator/persistence/XmlSerializedVibration.java b/core/java/com/android/internal/vibrator/persistence/XmlSerializedVibration.java
index 3233fa2..c20b7d2 100644
--- a/core/java/com/android/internal/vibrator/persistence/XmlSerializedVibration.java
+++ b/core/java/com/android/internal/vibrator/persistence/XmlSerializedVibration.java
@@ -26,8 +26,7 @@
  * Serialized representation of a generic vibration.
  *
  * <p>This can be used to represent a {@link android.os.CombinedVibration} or a
- * {@link android.os.VibrationEffect}. Instances can be created from vibration objects via
- * {@link XmlSerializer}, or from XML content via {@link XmlParser}.
+ * {@link android.os.VibrationEffect}.
  *
  * <p>The separation of serialization and writing procedures enables configurable rules to define
  * which vibrations can be successfully serialized before any data is written to the output stream.
diff --git a/core/java/com/android/internal/vibrator/persistence/XmlSerializer.java b/core/java/com/android/internal/vibrator/persistence/XmlSerializer.java
deleted file mode 100644
index 102e6c1..0000000
--- a/core/java/com/android/internal/vibrator/persistence/XmlSerializer.java
+++ /dev/null
@@ -1,40 +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.internal.vibrator.persistence;
-
-import android.annotation.NonNull;
-
-/**
- * Creates a {@link XmlSerializedVibration} instance representing a vibration.
- *
- * @param <T> The vibration type that will be serialized.
- * @see XmlSerializedVibration
- * @hide
- */
-@FunctionalInterface
-public interface XmlSerializer<T> {
-
-    /**
-     * Creates a serialized representation of the input {@code vibration}.
-     *
-     * @param vibration The vibration to be serialized
-     * @return The serialized representation of the input vibration
-     * @throws XmlSerializerException If the input vibration cannot be serialized
-     */
-    @NonNull
-    XmlSerializedVibration<T> serialize(@NonNull T vibration) throws XmlSerializerException;
-}
diff --git a/core/java/com/android/internal/vibrator/persistence/XmlSerializerException.java b/core/java/com/android/internal/vibrator/persistence/XmlSerializerException.java
index c57ff5d..2e7ad09 100644
--- a/core/java/com/android/internal/vibrator/persistence/XmlSerializerException.java
+++ b/core/java/com/android/internal/vibrator/persistence/XmlSerializerException.java
@@ -19,7 +19,6 @@
 /**
  * Represents an error while serializing a vibration input.
  *
- * @see XmlSerializer
  * @hide
  */
 public final class XmlSerializerException extends Exception {
diff --git a/core/java/com/android/internal/vibrator/persistence/XmlValidator.java b/core/java/com/android/internal/vibrator/persistence/XmlValidator.java
index 84d4f3f..1b5a356 100644
--- a/core/java/com/android/internal/vibrator/persistence/XmlValidator.java
+++ b/core/java/com/android/internal/vibrator/persistence/XmlValidator.java
@@ -18,7 +18,7 @@
 
 import static java.util.Objects.requireNonNull;
 
-import android.annotation.NonNull;
+import android.os.VibrationEffect;
 import android.text.TextUtils;
 
 import com.android.internal.util.ArrayUtils;
@@ -82,11 +82,11 @@
      * Check given {@link XmlSerializedVibration} represents the expected {@code vibration} object
      * when it's deserialized.
      */
-    @NonNull
-    public static <T> void checkSerializedVibration(
-            XmlSerializedVibration<T> serializedVibration, T expectedVibration)
+    public static void checkSerializedVibration(
+            XmlSerializedVibration<? extends VibrationEffect> serializedVibration,
+            VibrationEffect expectedVibration)
             throws XmlSerializerException {
-        T deserializedVibration = requireNonNull(serializedVibration.deserialize());
+        VibrationEffect deserializedVibration = requireNonNull(serializedVibration.deserialize());
         checkSerializerCondition(Objects.equals(expectedVibration, deserializedVibration),
                 "Unexpected serialized vibration %s: found deserialization %s, expected %s",
                 serializedVibration, deserializedVibration, expectedVibration);
diff --git a/core/java/com/android/internal/widget/MessagingMessage.java b/core/java/com/android/internal/widget/MessagingMessage.java
index ad90a63..a59ee77 100644
--- a/core/java/com/android/internal/widget/MessagingMessage.java
+++ b/core/java/com/android/internal/widget/MessagingMessage.java
@@ -105,7 +105,10 @@
     }
 
     default void removeMessage(ArrayList<MessagingLinearLayout.MessagingChild> toRecycle) {
-        getGroup().removeMessage(this, toRecycle);
+        final MessagingGroup group = getGroup();
+        if (group != null) {
+            group.removeMessage(this, toRecycle);
+        }
     }
 
     default void setMessagingGroup(MessagingGroup group) {
@@ -132,7 +135,12 @@
     @Override
     default void hideAnimated() {
         setIsHidingAnimated(true);
-        getGroup().performRemoveAnimation(getView(), () -> setIsHidingAnimated(false));
+        final MessagingGroup group = getGroup();
+        if (group != null) {
+            group.performRemoveAnimation(getView(), () -> setIsHidingAnimated(false));
+        } else {
+            setIsHidingAnimated(false);
+        }
     }
 
     default boolean hasOverlappingRendering() {
diff --git a/core/jni/OWNERS b/core/jni/OWNERS
index b2bc19c..c0fe098 100644
--- a/core/jni/OWNERS
+++ b/core/jni/OWNERS
@@ -51,6 +51,10 @@
 # Sensor
 per-file android_hardware_SensorManager* = arthuri@google.com, bduddie@google.com, stange@google.com
 
+# Security
+per-file android_os_SELinux.cpp = file:/core/java/android/security/OWNERS
+per-file android_security_* = file:/core/java/android/security/OWNERS
+
 per-file *Zygote* = file:/ZYGOTE_OWNERS
 per-file core_jni_helpers.* = file:/ZYGOTE_OWNERS
 per-file fd_utils.* = file:/ZYGOTE_OWNERS
@@ -67,7 +71,6 @@
 per-file android_os_storage_* = file:/core/java/android/os/storage/OWNERS
 per-file android_os_Trace* = file:/TRACE_OWNERS
 per-file android_se_* = file:/omapi/java/android/se/OWNERS
-per-file android_security_* = file:/core/java/android/security/OWNERS
 per-file android_view_* = file:/core/java/android/view/OWNERS
 per-file com_android_internal_net_* = file:/services/core/java/com/android/server/net/OWNERS
 
diff --git a/core/proto/android/server/vibrator/vibratormanagerservice.proto b/core/proto/android/server/vibrator/vibratormanagerservice.proto
index 12804d4..e7f0560 100644
--- a/core/proto/android/server/vibrator/vibratormanagerservice.proto
+++ b/core/proto/android/server/vibrator/vibratormanagerservice.proto
@@ -163,7 +163,7 @@
     optional bool vibrator_under_external_control = 5;
     optional bool low_power_mode = 6;
     optional bool vibrate_on = 24;
-    optional bool keyboard_vibration_on = 25;
+    reserved 25; // prev keyboard_vibration_on
     optional int32 default_vibration_amplitude = 26;
     optional int32 alarm_intensity = 18;
     optional int32 alarm_default_intensity = 19;
diff --git a/core/proto/android/widget/remoteviews.proto b/core/proto/android/widget/remoteviews.proto
index 37d1c5b..5892396 100644
--- a/core/proto/android/widget/remoteviews.proto
+++ b/core/proto/android/widget/remoteviews.proto
@@ -89,6 +89,205 @@
             bytes adaptive_bitmap = 8;
         };
     }
+
+    /**
+     * Represents a CharSequence with Spans.
+     */
+    message CharSequence {
+        optional string text = 1;
+        repeated Span spans = 2;
+
+        message Span {
+            optional int32 start = 1;
+            optional int32 end = 2;
+            optional int32 flags = 3;
+            // We use `repeated` for the following fields so that ProtoOutputStream does not omit
+            // empty messages (e.g. EasyEdit, Superscript). In practice, only one of the following
+            // fields will be written per Span message. We cannot use `oneof` here because
+            // ProtoOutputStream will omit empty messages.
+            repeated AbsoluteSize absolute_size = 4;
+            repeated AccessibilityClickable accessibility_clickable = 5;
+            repeated AccessibilityReplacement accessibility_replacement = 6;
+            repeated AccessibilityUrl accessibility_url = 7;
+            repeated Alignment alignment = 8;
+            repeated Annotation annotation = 9;
+            repeated BackgroundColor background_color = 10;
+            repeated Bullet bullet = 11;
+            repeated EasyEdit easy_edit = 12;
+            repeated ForegroundColor foreground_color = 13;
+            repeated LeadingMargin leading_margin = 14;
+            repeated LineBackground line_background = 15;
+            repeated LineBreak line_break = 16;
+            repeated LineHeight line_height = 17;
+            repeated Locale locale = 18;
+            repeated Quote quote = 19;
+            repeated RelativeSize relative_size = 20;
+            repeated ScaleX scale_x = 21;
+            repeated SpellCheck spell_check = 22;
+            repeated Strikethrough strikethrough = 23;
+            repeated Style style = 24;
+            repeated Subscript subscript = 25;
+            repeated Suggestion suggestion = 26;
+            repeated SuggestionRange suggestion_range = 27;
+            repeated Superscript superscript = 28;
+            repeated TextAppearance text_appearance = 29;
+            repeated Tts tts = 30;
+            repeated Typeface typeface = 31;
+            repeated Underline underline = 32;
+            repeated Url url = 33;
+
+            message AbsoluteSize {
+                optional int32 size = 1;
+                optional bool dip = 2;
+            }
+
+            message AccessibilityClickable {
+                optional int32 original_clickable_span_id = 1;
+            }
+
+            message AccessibilityReplacement {
+                optional CharSequence content_description = 1;
+            }
+
+            message AccessibilityUrl {
+                optional string url = 1;
+            }
+
+            message Alignment {
+                optional string alignment = 1;
+            }
+
+            message Annotation {
+                optional string key = 1;
+                optional string value = 2;
+            }
+
+            message BackgroundColor {
+                optional int32 color = 1;
+            }
+
+            message Bullet {
+                optional int32 gap_width = 1;
+                optional int32 color = 2;
+                optional int32 bullet_radius = 3;
+                optional bool want_color = 4;
+            }
+
+            message EasyEdit {}
+
+            message ForegroundColor {
+                optional int32 color = 1;
+            }
+
+            message LeadingMargin {
+                optional int32 first = 1;
+                optional int32 rest = 2;
+            }
+
+            message LineBackground {
+                optional int32 color = 1;
+            }
+
+            message LineBreak {
+                optional int32 line_break_style = 1;
+                optional int32 line_break_word_style = 2;
+                optional int32 hyphenation = 3;
+            }
+
+            message LineHeight {
+                optional int32 height = 1;
+            }
+
+            message Locale {
+                optional string language_tags = 1;
+            }
+
+            message Quote {
+                optional int32 color = 1;
+                optional int32 stripe_width = 2;
+                optional int32 gap_width = 3;
+            }
+
+            message RelativeSize {
+                optional float proportion = 1;
+            }
+
+            message ScaleX {
+                optional float proportion = 1;
+            }
+
+            message SpellCheck {
+                optional bool in_progress = 1;
+            }
+
+            message Strikethrough {}
+
+            message Style {
+                optional int32 style = 1;
+                optional int32 font_weight_adjustment = 2;
+            }
+
+            message Subscript {}
+
+            message Suggestion {
+                repeated string suggestions = 1;
+                optional int32 flags = 2;
+                optional string locale_string_for_compatibility = 3;
+                optional string language_tag = 4;
+                optional int32 hash_code = 5;
+                optional int32 easy_correct_underline_color = 6;
+                optional float easy_correct_underline_thickness = 7;
+                optional int32 misspelled_underline_color = 8;
+                optional float misspelled_underline_thickness = 9;
+                optional int32 auto_correction_underline_color = 10;
+                optional float auto_correction_underline_thickness = 11;
+                optional int32 grammar_error_underline_color = 12;
+                optional float grammar_error_underline_thickness = 13;
+            }
+
+            message SuggestionRange {
+                optional int32 background_color = 1;
+            }
+
+            message Superscript {}
+
+            // Typeface is omitted
+            message TextAppearance {
+                optional string family_name = 1;
+                optional int32 style = 2;
+                optional int32 text_size = 3;
+                optional android.content.res.ColorStateListProto text_color = 4;
+                optional android.content.res.ColorStateListProto text_color_link = 5;
+                optional int32 text_font_weight = 7;
+                optional string text_locale = 8;
+                optional float shadow_radius = 9;
+                optional float shadow_dx = 10;
+                optional float shadow_dy = 11;
+                optional int32 shadow_color = 12;
+                optional bool has_elegant_text_height_field = 13;
+                optional bool elegant_text_height = 14;
+                optional bool has_letter_spacing_field = 15;
+                optional float letter_spacing = 16;
+                optional string font_feature_settings = 17;
+                optional string font_variation_settings = 18;
+            }
+
+            message Tts {
+                optional string type = 1;
+                optional string args = 2;
+            }
+
+            message Typeface {
+                optional string family = 1;
+            }
+
+            message Underline {}
+
+            message Url {
+                optional string url = 1;
+            }
+        }
+    }
 }
 
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 50727a2..7aeabee 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8132,6 +8132,12 @@
     <permission android:name="android.permission.MONITOR_STICKY_MODIFIER_STATE"
                 android:protectionLevel="signature" />
 
+    <!-- Allows low-level access to monitor keyboard system shortcuts
+         <p>Not for use by third-party applications.
+         @hide -->
+    <permission android:name="android.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS"
+                android:protectionLevel="signature" />
+
     <uses-permission android:name="android.permission.HANDLE_QUERY_PACKAGE_RESTART" />
 
     <!-- Allows financed device kiosk apps to perform actions on the Device Lock service
diff --git a/core/res/res/layout/input_method_switch_dialog_new.xml b/core/res/res/layout/input_method_switch_dialog_new.xml
index 5a4d6b1..610a212 100644
--- a/core/res/res/layout/input_method_switch_dialog_new.xml
+++ b/core/res/res/layout/input_method_switch_dialog_new.xml
@@ -17,25 +17,35 @@
 
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/container"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:orientation="vertical">
 
-    <com.android.internal.widget.MaxHeightFrameLayout
-        android:layout_width="320dp"
+    <LinearLayout
+        android:layout_width="wrap_content"
         android:layout_height="0dp"
         android:layout_weight="1"
-        android:maxHeight="373dp">
+        android:orientation="horizontal">
 
-        <com.android.internal.widget.RecyclerView
-            android:id="@+id/list"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:paddingVertical="8dp"
-            android:clipToPadding="false"
-            android:layoutManager="com.android.internal.widget.LinearLayoutManager"/>
+        <!-- TODO(b/357644229): Enable shrinking width without three levels of nesting. -->
+        <com.android.internal.widget.MaxHeightFrameLayout
+            android:layout_width="320dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:maxHeight="373dp">
 
-    </com.android.internal.widget.MaxHeightFrameLayout>
+            <com.android.internal.widget.RecyclerView
+                android:id="@+id/list"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:paddingVertical="8dp"
+                android:clipToPadding="false"
+                android:layoutManager="com.android.internal.widget.LinearLayoutManager"/>
+
+        </com.android.internal.widget.MaxHeightFrameLayout>
+
+    </LinearLayout>
 
     <LinearLayout
         style="?android:attr/buttonBarStyle"
@@ -51,7 +61,8 @@
         <Space
             android:layout_width="0dp"
             android:layout_height="wrap_content"
-            android:layout_weight="1"/>
+            android:layout_weight="1"
+            android:importantForAccessibility="no"/>
 
         <Button
             style="?attr/buttonBarButtonStyle"
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 16a97c4..09ed650 100644
--- a/core/res/res/layout/input_method_switch_item_new.xml
+++ b/core/res/res/layout/input_method_switch_item_new.xml
@@ -31,7 +31,8 @@
         android:layout_marginTop="8dp"
         android:layout_marginEnd="24dp"
         android:layout_marginBottom="12dp"
-        android:visibility="gone"/>
+        android:visibility="gone"
+        android:importantForAccessibility="no"/>
 
     <TextView
         android:id="@+id/header_text"
@@ -81,7 +82,8 @@
             android:layout_marginStart="12dp"
             android:src="@drawable/ic_check_24dp"
             android:tint="?attr/materialColorOnSurface"
-            android:visibility="gone"/>
+            android:visibility="gone"
+            android:importantForAccessibility="no"/>
 
     </LinearLayout>
 
diff --git a/core/res/res/layout/list_menu_item_icon.xml b/core/res/res/layout/list_menu_item_icon.xml
index a30be6a..5854e81 100644
--- a/core/res/res/layout/list_menu_item_icon.xml
+++ b/core/res/res/layout/list_menu_item_icon.xml
@@ -18,6 +18,8 @@
     android:id="@+id/icon"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
+    android:maxWidth="@dimen/list_menu_item_icon_max_width"
+    android:adjustViewBounds="true"
     android:layout_gravity="center_vertical"
     android:layout_marginStart="8dip"
     android:layout_marginEnd="-8dip"
diff --git a/core/res/res/layout/time_picker_text_input_material.xml b/core/res/res/layout/time_picker_text_input_material.xml
index 4988842..86070b1 100644
--- a/core/res/res/layout/time_picker_text_input_material.xml
+++ b/core/res/res/layout/time_picker_text_input_material.xml
@@ -34,19 +34,29 @@
         android:layoutDirection="ltr">
         <EditText
             android:id="@+id/input_hour"
-            android:layout_width="50dp"
+            android:layout_width="50sp"
             android:layout_height="wrap_content"
+            android:layout_alignEnd="@id/hour_label_holder"
             android:inputType="number"
             android:textAppearance="@style/TextAppearance.Material.TimePicker.InputField"
             android:imeOptions="actionNext"/>
-        <TextView
-            android:id="@+id/label_hour"
+        <!-- Ensure the label_hour takes up at least 50sp of space -->
+        <FrameLayout
+            android:id="@+id/hour_label_holder"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_below="@id/input_hour"
-            android:layout_alignStart="@id/input_hour"
-            android:labelFor="@+id/input_hour"
-            android:text="@string/time_picker_hour_label"/>
+            android:layout_below="@id/input_hour">
+            <TextView
+                android:id="@+id/label_hour"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:labelFor="@+id/input_hour"
+                android:text="@string/time_picker_hour_label"/>
+            <Space
+                android:layout_width="50sp"
+                android:layout_height="0dp"/>
+        </FrameLayout>
 
         <TextView
             android:id="@+id/input_separator"
@@ -58,21 +68,30 @@
 
         <EditText
             android:id="@+id/input_minute"
-            android:layout_width="50dp"
+            android:layout_width="50sp"
             android:layout_height="wrap_content"
             android:layout_alignBaseline="@id/input_hour"
             android:layout_toEndOf="@id/input_separator"
             android:inputType="number"
             android:textAppearance="@style/TextAppearance.Material.TimePicker.InputField" />
-        <TextView
-            android:id="@+id/label_minute"
+        <!-- Ensure the label_minute takes up at least 50sp of space -->
+        <FrameLayout
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_below="@id/input_minute"
             android:layout_alignStart="@id/input_minute"
-            android:labelFor="@+id/input_minute"
-            android:text="@string/time_picker_minute_label"/>
-
+            >
+            <TextView
+                android:id="@+id/label_minute"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:labelFor="@+id/input_minute"
+                android:text="@string/time_picker_minute_label"/>
+            <Space
+                android:layout_width="50sp"
+                android:layout_height="0dp"/>
+        </FrameLayout>
         <TextView
             android:visibility="invisible"
             android:id="@+id/label_error"
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 77b5587..f397ef2 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -1065,4 +1065,7 @@
     <!-- The non-linear progress interval when the screen is wider than the
         navigation_edge_action_progress_threshold. -->
     <item name="back_progress_non_linear_factor" format="float" type="dimen">0.2</item>
+
+    <!-- The maximum width for a context menu icon -->
+    <dimen name="list_menu_item_icon_max_width">24dp</dimen>
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 9104379..cb58339 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3277,6 +3277,9 @@
     <string name="input_method_nav_back_button_desc">Back</string>
     <!-- Content description of the switch input method button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="input_method_ime_switch_button_desc">Switch input method</string>
+    <!-- Accessibility text for the long click action on the switch input method button. This will
+         be used following "Double-tap and hold to..." [CHAR LIMIT=NONE] -->
+    <string name="input_method_ime_switch_long_click_action_desc">Open input method picker</string>
 
     <!-- If the device is getting low on internal storage, a notification is shown to the user.  This is the title of that notification. -->
     <string name="low_internal_storage_view_title">Storage space running out</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 8f4018f..0d16e9c 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2245,6 +2245,7 @@
   <java-symbol type="string" name="heavy_weight_notification_detail" />
   <java-symbol type="string" name="image_wallpaper_component" />
   <java-symbol type="string" name="input_method_binding_label" />
+  <java-symbol type="string" name="input_method_ime_switch_long_click_action_desc" />
   <java-symbol type="string" name="launch_warning_original" />
   <java-symbol type="string" name="launch_warning_replace" />
   <java-symbol type="string" name="launch_warning_title" />
@@ -5461,7 +5462,9 @@
   <java-symbol type="bool" name="config_enable_a11y_fullscreen_magnification_overscroll_handler" />
   <java-symbol type="dimen" name="accessibility_fullscreen_magnification_gesture_edge_slop" />
 
+  <!-- For HapticFeedbackConstants configurability defined at HapticFeedbackCustomization -->
   <java-symbol type="string" name="config_hapticFeedbackCustomizationFile" />
+  <java-symbol type="xml" name="haptic_feedback_customization" />
 
   <!-- For ActivityManager PSS profiling configurability -->
   <java-symbol type="bool" name="config_am_disablePssProfiling" />
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/res/xml/network_security_config.xml b/core/res/res/xml/haptic_feedback_customization.xml
similarity index 71%
rename from libs/WindowManager/Shell/tests/flicker/splitscreen/res/xml/network_security_config.xml
rename to core/res/res/xml/haptic_feedback_customization.xml
index 4bd9ca0..7ac0787 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/res/xml/network_security_config.xml
+++ b/core/res/res/xml/haptic_feedback_customization.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ 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.
@@ -15,8 +15,4 @@
   ~ limitations under the License.
   -->
 
-<network-security-config>
-    <domain-config cleartextTrafficPermitted="true">
-        <domain includeSubdomains="true">localhost</domain>
-    </domain-config>
-</network-security-config>
+<haptic-feedback-constants/>
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 5793bbe..2bbaf9c 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -249,6 +249,7 @@
     ],
     srcs: [
         "src/android/app/ActivityManagerTest.java",
+        "src/android/colormodel/CamTest.java",
         "src/android/content/ContextTest.java",
         "src/android/content/pm/PackageManagerTest.java",
         "src/android/content/pm/UserInfoTest.java",
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index c05ea3d..fc3c2f3 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -265,6 +265,17 @@
             </intent-filter>
         </activity>
 
+        <activity android:name="android.widget.ChronometerActivity"
+                android:label="ChronometerActivity"
+                android:screenOrientation="portrait"
+                android:exported="true"
+                android:theme="@android:style/Theme.Material.Light">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+            </intent-filter>
+        </activity>
+
         <activity android:name="android.widget.DatePickerActivity"
                 android:label="DatePickerActivity"
                 android:screenOrientation="portrait"
diff --git a/core/tests/coretests/res/layout/chronometer_layout.xml b/core/tests/coretests/res/layout/chronometer_layout.xml
new file mode 100644
index 0000000..f209c41
--- /dev/null
+++ b/core/tests/coretests/res/layout/chronometer_layout.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<FrameLayout
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+    <Chronometer
+            android:id="@+id/chronometer"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"/>
+</FrameLayout>
diff --git a/core/tests/coretests/res/values/styles.xml b/core/tests/coretests/res/values/styles.xml
index 78cd1e1..e7009d14 100644
--- a/core/tests/coretests/res/values/styles.xml
+++ b/core/tests/coretests/res/values/styles.xml
@@ -61,4 +61,28 @@
     <style name="IsFrameRatePowerSavingsBalancedEnabled">
         <item name="android:windowIsFrameRatePowerSavingsBalanced">true</item>
     </style>
+    <style name="customFont">
+        <item name="android:fontFamily">@font/samplefont</item>
+    </style>
+    <style name="customFontWithStyle">
+        <item name="android:fontFamily">@font/samplefont</item>
+        <item name="android:textStyle">bold|italic</item>
+    </style>
+    <style name="textAppearanceWithAllAttributes">
+        <item name="android:fontFamily">@font/samplefont</item>
+        <item name="android:textStyle">bold|italic</item>
+        <item name="android:textSize">160dp</item>
+        <item name="android:textColor">#FF00FF</item>
+        <item name="android:textColorLink">#00FFFF</item>
+        <item name="android:textLocale">ja-JP,zh-CN</item>
+        <item name="android:shadowColor">#00FFFF</item>
+        <item name="android:shadowDx">1.0</item>
+        <item name="android:shadowDy">2.0</item>
+        <item name="android:shadowRadius">3.0</item>
+        <item name="android:elegantTextHeight">true</item>
+        <item name="android:letterSpacing">1.0</item>
+        <item name="android:fontFeatureSettings">\"smcp\"</item>
+        <item name="android:fontVariationSettings">\'wdth\' 150</item>
+    </style>
+
 </resources>
diff --git a/core/tests/coretests/src/android/colormodel/CamTest.java b/core/tests/coretests/src/android/colormodel/CamTest.java
index 05fc0e0..cf398db 100644
--- a/core/tests/coretests/src/android/colormodel/CamTest.java
+++ b/core/tests/coretests/src/android/colormodel/CamTest.java
@@ -18,9 +18,12 @@
 
 import static org.junit.Assert.assertEquals;
 
+import android.platform.test.ravenwood.RavenwoodRule;
+
 import androidx.test.filters.LargeTest;
 
 import org.junit.Assert;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -35,6 +38,9 @@
     static final int GREEN = 0xff00ff00;
     static final int BLUE = 0xff0000ff;
 
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
     @Test
     public void camFromIntToInt() {
         Cam cam = Cam.fromInt(RED);
diff --git a/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java b/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java
index 2f336ab..e2c1902 100644
--- a/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java
+++ b/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java
@@ -20,6 +20,10 @@
 
 import android.content.Context;
 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.text.SpannableString;
 import android.text.SpannableStringBuilder;
 import android.text.Spanned;
@@ -30,7 +34,10 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.text.flags.Flags;
+
 import org.junit.BeforeClass;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -41,6 +48,9 @@
     private static View sView;
     private static final String TEXT = "abc def";
 
+    @Rule
+    public CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     @BeforeClass
     public static void setupClass() {
         final Context context = InstrumentationRegistry.getTargetContext();
@@ -76,11 +86,13 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
     public void transformedText_charAt_editing() {
         transformedText_charAt_editing(false, "\n\n");
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
     public void transformedText_charAt_singleLine_editing() {
         transformedText_charAt_editing(true, "\uFFFD");
     }
@@ -132,6 +144,64 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
+    public void transformedText_charAt_editing_stickyHighlightRange() {
+        transformedText_charAt_editing_stickyHighlightRange(false, "\n\n");
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
+    public void transformedText_charAt_singleLine_editing_stickyHighlightRange() {
+        transformedText_charAt_editing_stickyHighlightRange(true, "\uFFFD");
+    }
+
+    private void transformedText_charAt_editing_stickyHighlightRange(boolean singleLine,
+            String placeholder) {
+        final SpannableStringBuilder text = new SpannableStringBuilder(TEXT);
+        final InsertModeTransformationMethod transformationMethod =
+                new InsertModeTransformationMethod(3, singleLine, null);
+        final CharSequence transformedText = transformationMethod.getTransformation(text, sView);
+        // TransformationMethod is set on the original text as a TextWatcher in the TextView.
+        text.setSpan(transformationMethod, 0, text.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+
+        assertCharSequence(transformedText,  "abc" + placeholder + " def");
+
+        // original text is "abcxx def" after insertion.
+        text.insert(3, "xx");
+        assertCharSequence(transformedText, "abcxx" + placeholder + " def");
+
+        // original text is "abcxx vvdef" after insertion.
+        text.insert(6, "vv");
+        assertCharSequence(transformedText, "abcxx" + placeholder + " vvdef");
+
+        // original text is "abc vvdef" after deletion.
+        text.delete(3, 5);
+        assertCharSequence(transformedText, "abc" + placeholder + " vvdef");
+
+        // original text is "abc def" after deletion.
+        text.delete(4, 6);
+        assertCharSequence(transformedText, "abc" + placeholder + " def");
+
+        // original text is "abdef" after deletion.
+        // deletion range covers the placeholder's insertion point. It'll try to stay the same,
+        // which is still at index 3.
+        text.delete(2, 4);
+        assertCharSequence(transformedText, "abd" + placeholder + "ef");
+
+        // original text is "axxdef" after replace.
+        // this time the replaced range is ahead of the placeholder's insertion point. It updates to
+        // index 4.
+        text.replace(1, 2, "xx");
+        assertCharSequence(transformedText, "axxd" + placeholder + "ef");
+
+        // original text is "ax" after replace.
+        // the deleted range covers the placeholder's insertion point. It tries to stay at index 4.
+        // However, 4 out of bounds now. So placeholder is inserted at the end of the string.
+        text.delete(2, 6);
+        assertCharSequence(transformedText, "ax" + placeholder);
+    }
+
+    @Test
     public void transformedText_subSequence() {
         for (int offset = 0; offset < TEXT.length(); ++offset) {
             final InsertModeTransformationMethod transformationMethod =
@@ -697,7 +767,7 @@
     }
 
     @Test
-    public void transformedText_getHighlightStartAndEnd_insertion_singleLine() {
+    public void transformedText_getHighlightStartAndEnd_singleLine_insertion() {
         transformedText_getHighlightStartAndEnd_insertion(true, "\uFDDD");
     }
 
@@ -751,16 +821,18 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
     public void transformedText_getHighlightStartAndEnd_deletion() {
         transformedText_getHighlightStartAndEnd_deletion(false, "\n\n");
     }
 
     @Test
-    public void transformedText_getHighlightStartAndEnd_insertion_deletion() {
+    @RequiresFlagsDisabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
+    public void transformedText_getHighlightStartAndEnd_singleLine_deletion() {
         transformedText_getHighlightStartAndEnd_deletion(true, "\uFDDD");
     }
 
-    public void transformedText_getHighlightStartAndEnd_deletion(boolean singleLine,
+    private void transformedText_getHighlightStartAndEnd_deletion(boolean singleLine,
             String placeholder) {
         final SpannableStringBuilder text = new SpannableStringBuilder(TEXT);
         final InsertModeTransformationMethod transformationMethod =
@@ -816,14 +888,93 @@
         assertThat(transformedText.getHighlightEnd()).isEqualTo(1 + placeholder.length());
     }
 
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
+    public void transformedText_getHighlightStartAndEnd_deletion_stickyHighlightRange() {
+        transformedText_getHighlightStartAndEnd_deletion_stickyHighlightRange(false, "\n\n");
+    }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
+    public void transformedText_getHighlightStartAndEnd_singleLine_deletion_stickyHighlightRange() {
+        transformedText_getHighlightStartAndEnd_deletion_stickyHighlightRange(true, "\uFDDD");
+    }
+
+    private void transformedText_getHighlightStartAndEnd_deletion_stickyHighlightRange(
+            boolean singleLine, String placeholder) {
+        final SpannableStringBuilder text = new SpannableStringBuilder(TEXT);
+        final InsertModeTransformationMethod transformationMethod =
+                new InsertModeTransformationMethod(3, singleLine, null);
+        final InsertModeTransformationMethod.TransformedText transformedText =
+                (InsertModeTransformationMethod.TransformedText) transformationMethod
+                        .getTransformation(text, sView);
+        // TransformationMethod is set on the original text as a TextWatcher in the TextView.
+        text.setSpan(transformationMethod, 0, text.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+
+        // note: the placeholder text is also highlighted.
+        assertThat(transformedText.getHighlightStart()).isEqualTo(3);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(3 + placeholder.length());
+
+        // original text is "abcxxxxxx def" after insertion.
+        // the placeholder is now inserted at index 9.
+        // the highlight start is still 3.
+        // the highlight end now is 9 + placeholder.length().
+        text.insert(3, "xxxxxx");
+        assertThat(transformedText.getHighlightStart()).isEqualTo(3);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(9 + placeholder.length());
+
+        // original text is "abxxxxxx def" after deletion.
+        // the placeholder is now inserted at index 6.
+        // the highlight start is 2, since the deletion happens before the highlight range.
+        // the highlight end now is 8 + placeholder.length().
+        text.delete(2, 3);
+        assertThat(transformedText.getHighlightStart()).isEqualTo(2);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(8 + placeholder.length());
+
+        // original text is "abxxx def" after deletion.
+        // the placeholder is now inserted at index 5.
+        // the highlight start is still 2, since the deletion happens in the highlight range.
+        // the highlight end now is 5 + placeholder.length().
+        text.delete(2, 5);
+        assertThat(transformedText.getHighlightStart()).isEqualTo(2);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(5 + placeholder.length());
+
+        // original text is "abxxx d" after deletion.
+        // the placeholder is now inserted at index 5.
+        // the highlight start is still 2, since the deletion happens after the highlight range.
+        // the highlight end now is still 5 + placeholder.length().
+        text.delete(7, 9);
+        assertThat(transformedText.getHighlightStart()).isEqualTo(2);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(5 + placeholder.length());
+
+        // original text is "axx d" after deletion.
+        // the placeholder is now inserted at index 3.
+        // the highlight start is at 2, since the deletion range covers the start.
+        // the highlight end is 3 + placeholder.length().
+        text.delete(1, 3);
+        assertThat(transformedText.getHighlightStart()).isEqualTo(2);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(3 + placeholder.length());
+
+        // original text is "ax" after deletion.
+        // the placeholder is now inserted at index 2.
+        // the highlight start is at 2.
+        // the highlight end is 2 + placeholder.length(). It wants to stay at 3, but it'll be out
+        // of bounds, so it'll be 2 instead.
+        text.delete(2, 5);
+        assertThat(transformedText.getHighlightStart()).isEqualTo(2);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(2 + placeholder.length());
+    }
+
+
+    @Test
+    @RequiresFlagsDisabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
     public void transformedText_getHighlightStartAndEnd_replace() {
         transformedText_getHighlightStartAndEnd_replace(false, "\n\n");
     }
 
     @Test
-    public void transformedText_getHighlightStartAndEnd_insertion__replace() {
+    @RequiresFlagsDisabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
+    public void transformedText_getHighlightStartAndEnd_singleLine_replace() {
         transformedText_getHighlightStartAndEnd_replace(true, "\uFDDD");
     }
 
@@ -908,6 +1059,99 @@
         assertThat(transformedText.getHighlightEnd()).isEqualTo(3 + placeholder.length());
     }
 
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
+    public void transformedText_getHighlightStartAndEnd_replace_stickyHighlightRange() {
+        transformedText_getHighlightStartAndEnd_replace_stickyHighlightRange(false, "\n\n");
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
+    public void transformedText_getHighlightStartAndEnd_singleLine_replace_stickyHighlightRange() {
+        transformedText_getHighlightStartAndEnd_replace_stickyHighlightRange(true, "\uFDDD");
+    }
+
+    private void transformedText_getHighlightStartAndEnd_replace_stickyHighlightRange(
+            boolean singleLine, String placeholder) {
+        final SpannableStringBuilder text = new SpannableStringBuilder(TEXT);
+        final InsertModeTransformationMethod transformationMethod =
+                new InsertModeTransformationMethod(3, singleLine, null);
+        final InsertModeTransformationMethod.TransformedText transformedText =
+                (InsertModeTransformationMethod.TransformedText) transformationMethod
+                        .getTransformation(text, sView);
+        // TransformationMethod is set on the original text as a TextWatcher in the TextView.
+        text.setSpan(transformationMethod, 0, text.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+
+        // note: the placeholder text is also highlighted.
+        assertThat(transformedText.getHighlightStart()).isEqualTo(3);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(3 + placeholder.length());
+
+        // original text is "abcxxxxxx def" after insertion.
+        // the placeholder is now inserted at index 9.
+        // the highlight start is still 3.
+        // the highlight end now is 9 + placeholder.length().
+        text.insert(3, "xxxxxx");
+        assertThat(transformedText.getHighlightStart()).isEqualTo(3);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(9 + placeholder.length());
+
+        // original text is "abvvxxxxxx def" after replace.
+        // the replacement happens before the highlight range; highlight range is offset by 1
+        // the placeholder is now inserted at index 10,
+        // the highlight start is 4.
+        // the highlight end is 10 + placeholder.length().
+        text.replace(2, 3, "vv");
+        assertThat(transformedText.getHighlightStart()).isEqualTo(4);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(10 + placeholder.length());
+
+        // original text is "abvvxxx def" after replace.
+        // the replacement happens in the highlight range; highlight end is offset by -3
+        // the placeholder is now inserted at index 7,
+        // the highlight start is still 4.
+        // the highlight end is 7 + placeholder.length().
+        text.replace(5, 9, "x");
+        assertThat(transformedText.getHighlightStart()).isEqualTo(4);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(7 + placeholder.length());
+
+        // original text is "abvvxxxvvv" after replace.
+        // the replacement happens after the highlight range; highlight is not changed
+        // the placeholder is now inserted at index 7,
+        // the highlight start is still 4.
+        // the highlight end is 7 + placeholder.length().
+        text.replace(7, 11, "vvv");
+        assertThat(transformedText.getHighlightStart()).isEqualTo(4);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(7 + placeholder.length());
+
+        // original text is "abxxxxvvv" after replace.
+        // the replacement covers the highlight start; highlight start stays the same;
+        // highlight end is offset by -1
+        // the placeholder is now inserted at index 6,
+        // the highlight start is 4.
+        // the highlight end is 6 + placeholder.length().
+        text.replace(2, 5, "xx");
+        assertThat(transformedText.getHighlightStart()).isEqualTo(4);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(6 + placeholder.length());
+
+        // original text is "abxxxxxvv" after replace.
+        // the replacement covers the highlight end; highlight end stays the same;
+        // highlight start stays the same
+        // the placeholder is now inserted at index 6,
+        // the highlight start is 2.
+        // the highlight end is 6 + placeholder.length().
+        text.replace(5, 7, "xx");
+        assertThat(transformedText.getHighlightStart()).isEqualTo(4);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(6 + placeholder.length());
+
+        // original text is "axxv" after replace.
+        // the replacement covers the highlight range; highlight start stays the same.
+        // highlight end shrink to the text length.
+        // the placeholder is now inserted at index 3,
+        // the highlight start is 2.
+        // the highlight end is 4 + placeholder.length().
+        text.replace(1, 8, "xx");
+        assertThat(transformedText.getHighlightStart()).isEqualTo(4);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(4 + placeholder.length());
+    }
+
     private static  <T> void assertNextSpanTransition(Spanned spanned, int[] transitions,
             Class<T> type) {
         int currentTransition = 0;
diff --git a/core/tests/coretests/src/android/view/HapticScrollFeedbackProviderTest.java b/core/tests/coretests/src/android/view/HapticScrollFeedbackProviderTest.java
index 3dfeb7f..ac6c19e 100644
--- a/core/tests/coretests/src/android/view/HapticScrollFeedbackProviderTest.java
+++ b/core/tests/coretests/src/android/view/HapticScrollFeedbackProviderTest.java
@@ -16,16 +16,20 @@
 
 package android.view;
 
+import static android.os.vibrator.Flags.FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED;
 import static android.view.HapticFeedbackConstants.SCROLL_ITEM_FOCUS;
 import static android.view.HapticFeedbackConstants.SCROLL_LIMIT;
 import static android.view.HapticFeedbackConstants.SCROLL_TICK;
+
 import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.view.flags.FeatureFlags;
 
 import androidx.test.InstrumentationRegistry;
@@ -33,17 +37,24 @@
 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 java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 
+// TODO(b/353625893): update old tests to use new infra like those with "inputDeviceCustomized".
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 @Presubmit
 public final class HapticScrollFeedbackProviderTest {
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
     private static final int INPUT_DEVICE_1 = 1;
     private static final int INPUT_DEVICE_2 = 2;
 
@@ -64,6 +75,7 @@
         mView = new TestView(InstrumentationRegistry.getContext());
         mProvider = new HapticScrollFeedbackProvider(mView, mMockViewConfig,
                 /* disabledIfViewPlaysScrollHaptics= */ true);
+        mSetFlagsRule.disableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
     }
 
     @Test
@@ -85,6 +97,26 @@
     }
 
     @Test
+    public void testRotaryEncoder_inputDeviceCustomized_noFeedbackWhenViewBasedFeedbackIsEnabled() {
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+
+        when(mMockViewConfig.isViewBasedRotaryEncoderHapticScrollFeedbackEnabled())
+                .thenReturn(true);
+        setHapticScrollTickInterval(5);
+
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ 10);
+        mProvider.onSnapToItem(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ true);
+
+        assertThat(mView.mHapticFeedbackRequests).hasSize(0);
+    }
+
+    @Test
     public void testRotaryEncoder_feedbackWhenDisregardingViewBasedScrollHaptics() {
         mProvider = new HapticScrollFeedbackProvider(mView, mMockViewConfig,
                 /* disabledIfViewPlaysScrollHaptics= */ false);
@@ -107,6 +139,35 @@
     }
 
     @Test
+    public void testRotaryEncoder_inputDeviceCustomized_feedbackWhenDisregardingViewBasedScrollHaptics() {
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+        List<HapticFeedbackRequest> requests = new ArrayList<>();
+
+        mProvider = new HapticScrollFeedbackProvider(mView, mMockViewConfig,
+                /* disabledIfViewPlaysScrollHaptics= */ false);
+        when(mMockViewConfig.isViewBasedRotaryEncoderHapticScrollFeedbackEnabled())
+                .thenReturn(true);
+        setHapticScrollTickInterval(5);
+
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ 10);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_TICK, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        mProvider.onSnapToItem(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ true);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+
+        assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+    }
+
+    @Test
     public void testNoFeedbackWhenFeedbackIsDisabled() {
         setHapticScrollFeedbackEnabled(false);
         // Call different types scroll feedback methods; non of them should produce feedback because
@@ -130,6 +191,31 @@
     }
 
     @Test
+    public void testNoFeedbackWhenFeedbackIsDisabled_inputDeviceCustomized() {
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+
+        setHapticScrollFeedbackEnabled(false);
+        // Call different types scroll feedback methods; non of them should produce feedback because
+        // feedback has been disabled.
+        mProvider.onSnapToItem(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ true);
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ false);
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ 300);
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ -300);
+
+        assertThat(mView.mHapticFeedbackRequests).hasSize(0);
+    }
+
+    @Test
     public void testSnapToItem() {
         mProvider.onSnapToItem(
                 INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
@@ -138,6 +224,25 @@
     }
 
     @Test
+    public void testSnapToItem_inputDeviceCustomized() {
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+        List<HapticFeedbackRequest> requests = new ArrayList<>();
+
+        mProvider.onSnapToItem(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+        requests.add(new HapticFeedbackRequest(
+                    SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+
+        mProvider.onSnapToItem(
+                INPUT_DEVICE_2, InputDevice.SOURCE_TOUCHSCREEN, MotionEvent.AXIS_SCROLL);
+        requests.add(
+                new HapticFeedbackRequest(
+                        SCROLL_ITEM_FOCUS, INPUT_DEVICE_2, InputDevice.SOURCE_TOUCHSCREEN));
+
+        assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+    }
+
+    @Test
     public void testScrollLimit_start() {
         mProvider.onSnapToItem(
                 INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
@@ -150,6 +255,24 @@
     }
 
     @Test
+    public void testScrollLimit_inputDeviceCustomized_start() {
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+        List<HapticFeedbackRequest> requests = new ArrayList<>();
+
+        mProvider.onSnapToItem(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ true);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+
+        assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+    }
+
+    @Test
     public void testScrollLimit_stop() {
         mProvider.onSnapToItem(
                 INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
@@ -162,6 +285,24 @@
     }
 
     @Test
+    public void testScrollLimit_inputDeviceCustomized_stop() {
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+        List<HapticFeedbackRequest> requests = new ArrayList<>();
+
+        mProvider.onSnapToItem(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ false);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+
+        assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+    }
+
+    @Test
     public void testScrollProgress_zeroTickInterval() {
         setHapticScrollTickInterval(0);
 
@@ -176,6 +317,22 @@
     }
 
     @Test
+    public void testScrollProgress_inputDeviceCustomized_zeroTickInterval() {
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+
+        setHapticScrollTickInterval(0);
+
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ 30);
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ 20);
+
+        assertThat(mView.mHapticFeedbackRequests).hasSize(0);
+    }
+
+    @Test
     public void testScrollProgress_progressEqualsOrExceedsPositiveThreshold() {
         setHapticScrollTickInterval(100);
         mProvider.onScrollProgress(
@@ -198,6 +355,32 @@
     }
 
     @Test
+    public void testScrollProgress_inputDeviceCustomized_progressEqualsOrExceedsPositiveThreshold() {
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+        List<HapticFeedbackRequest> requests = new ArrayList<>();
+        setHapticScrollTickInterval(100);
+
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ 20);
+
+        assertThat(mView.mHapticFeedbackRequests).hasSize(0);
+
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ 80);
+        requests.add(new HapticFeedbackRequest(
+                    SCROLL_TICK, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ 120);
+        requests.add(new HapticFeedbackRequest(
+                    SCROLL_TICK, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+
+        assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+    }
+
+    @Test
     public void testScrollProgress_progressEqualsOrExceedsNegativeThreshold() {
         setHapticScrollTickInterval(100);
 
@@ -224,6 +407,35 @@
     }
 
     @Test
+    public void testScrollProgress_inputDeviceCustomized_progressEqualsOrExceedsNegativeThreshold() {
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+        List<HapticFeedbackRequest> requests = new ArrayList<>();
+        setHapticScrollTickInterval(100);
+
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ -20);
+
+        assertThat(mView.mHapticFeedbackRequests).hasSize(0);
+
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ -80);
+        requests.add(new HapticFeedbackRequest(
+                    SCROLL_TICK, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ -70);
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ -40);
+        requests.add(new HapticFeedbackRequest(
+                    SCROLL_TICK, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+
+        assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+    }
+
+    @Test
     public void testScrollProgress_positiveAndNegativeProgresses() {
         setHapticScrollTickInterval(100);
 
@@ -262,6 +474,54 @@
     }
 
     @Test
+    public void testScrollProgress_inputDeviceCustomized_positiveAndNegativeProgresses() {
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+        List<HapticFeedbackRequest> requests = new ArrayList<>();
+        setHapticScrollTickInterval(100);
+
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ 20);
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ -90);
+
+        // total pixel abs = 70
+        assertThat(mView.mHapticFeedbackRequests).hasSize(0);
+
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ 10);
+
+        // total pixel abs = 60
+        assertThat(mView.mHapticFeedbackRequests).hasSize(0);
+
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ -50);
+        // total pixel abs = 110. Passed threshold. total pixel reduced to -10.
+        requests.add(new HapticFeedbackRequest(
+                    SCROLL_TICK, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+
+        assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ 40);
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ 50);
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ 60);
+        // total pixel abs = 140. Passed threshold. total pixel reduced to 40.
+        requests.add(new HapticFeedbackRequest(
+                    SCROLL_TICK, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+
+        assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+    }
+
+    @Test
     public void testScrollProgress_singleProgressExceedsThreshold() {
         setHapticScrollTickInterval(100);
 
@@ -273,6 +533,21 @@
     }
 
     @Test
+    public void testScrollProgress_inputDeviceCustomized_singleProgressExceedsThreshold() {
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+        List<HapticFeedbackRequest> requests = new ArrayList<>();
+        setHapticScrollTickInterval(100);
+
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ 1000);
+        requests.add(new HapticFeedbackRequest(
+                    SCROLL_TICK, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+
+        assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+    }
+
+    @Test
     public void testScrollLimit_startAndEndLimit_playsOnlyOneFeedback() {
         mProvider.onSnapToItem(
                 INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
@@ -288,6 +563,29 @@
     }
 
     @Test
+    public void testScrollLimit_startAndEndLimit_inputDeviceCustomized_playsOnlyOneFeedback() {
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+        List<HapticFeedbackRequest> requests = new ArrayList<>();
+
+        mProvider.onSnapToItem(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        // end played.
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ false);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        // start after end NOT played.
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ true);
+
+        assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+    }
+
+    @Test
     public void testScrollLimit_doubleStartLimit_playsOnlyOneFeedback() {
         mProvider.onSnapToItem(
                 INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
@@ -303,6 +601,29 @@
     }
 
     @Test
+    public void testScrollLimit_doubleStartLimit_inputDeviceCustomized_playsOnlyOneFeedback() {
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+        List<HapticFeedbackRequest> requests = new ArrayList<>();
+
+        mProvider.onSnapToItem(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        // 1st start played.
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ true);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        // 2nd start NOT played.
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ true);
+
+        assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+    }
+
+    @Test
     public void testScrollLimit_doubleEndLimit_playsOnlyOneFeedback() {
         mProvider.onSnapToItem(
                 INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
@@ -318,6 +639,29 @@
     }
 
     @Test
+    public void testScrollLimit_doubleEndLimit_inputDeviceCustomized_playsOnlyOneFeedback() {
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+        List<HapticFeedbackRequest> requests = new ArrayList<>();
+
+        mProvider.onSnapToItem(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        // 1st end played.
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ false);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        // 2nd end NOT played.
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ false);
+
+        assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+    }
+
+    @Test
     public void testScrollLimit_notEnabledWithZeroProgress() {
         mProvider.onSnapToItem(
                 INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
@@ -339,6 +683,36 @@
     }
 
     @Test
+    public void testScrollLimit_inputDeviceCustomized_notEnabledWithZeroProgress() {
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+        List<HapticFeedbackRequest> requests = new ArrayList<>();
+
+        mProvider.onSnapToItem(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        // end played.
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ false);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+
+        // progress 0. scroll not started.
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ 0);
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ true);
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ false);
+
+        assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+    }
+
+    @Test
     public void testScrollLimit_enabledWithProgress() {
         mProvider.onSnapToItem(
                 INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
@@ -357,6 +731,36 @@
     }
 
     @Test
+    public void testScrollLimit_inputDeviceCustomized_enabledWithProgress() {
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+        List<HapticFeedbackRequest> requests = new ArrayList<>();
+
+        mProvider.onSnapToItem(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        // end played.
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ false);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        // No tick since tick-interval is default 0, which means no tick.
+        // But still re-enable next limit feedback.
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ 80);
+        // scroll pixel not 0, so end played.
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ false);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+
+        assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+    }
+
+    @Test
     public void testScrollLimit_enabledWithSnap() {
         mProvider.onSnapToItem(
                 INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
@@ -374,6 +778,35 @@
     }
 
     @Test
+    public void testScrollLimit_inputDeviceCustomized_enabledWithSnap() {
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+        List<HapticFeedbackRequest> requests = new ArrayList<>();
+
+        // 1st enabled limit by snap
+        mProvider.onSnapToItem(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ false);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        // 2nd enabled limit by snap
+        mProvider.onSnapToItem(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ false);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+
+        assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+    }
+
+    @Test
     public void testScrollLimit_notEnabledWithDissimilarSnap() {
         mProvider.onSnapToItem(
                 INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
@@ -391,6 +824,33 @@
     }
 
     @Test
+    public void testScrollLimit_inputDeviceCustomized_notEnabledWithDissimilarSnap() {
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+        List<HapticFeedbackRequest> requests = new ArrayList<>();
+
+        mProvider.onSnapToItem(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ false);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+
+        mProvider.onSnapToItem(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_X);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        // Last snap is on AXIS_X, so end on AXIS_SCROLL is NOT played.
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ false);
+
+        assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+    }
+
+    @Test
     public void testScrollLimit_enabledWithDissimilarProgress() {
         mProvider.onSnapToItem(
                 INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
@@ -408,6 +868,33 @@
         assertFeedbackCount(mView, HapticFeedbackConstants.SCROLL_LIMIT, 2);
     }
 
+    @Test
+    public void testScrollLimit_inputDeviceCustomized_enabledWithDissimilarProgress() {
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+        List<HapticFeedbackRequest> requests = new ArrayList<>();
+
+        mProvider.onSnapToItem(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ false);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        // No tick since tick-interval is default 0, which means no tick.
+        // But still re-enable next limit feedback.
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ 80);
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ false);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+
+        assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+    }
 
     @Test
     public void testScrollLimit_doesNotEnabledWithMotionFromDifferentDeviceId() {
@@ -428,9 +915,94 @@
         assertFeedbackCount(mView, HapticFeedbackConstants.SCROLL_LIMIT, 1);
     }
 
+    @Test
+    public void testNonRotaryInputFeedbackNotBlockedByRotaryUnavailability() {
+        when(mMockViewConfig.isViewBasedRotaryEncoderHapticScrollFeedbackEnabled())
+                .thenReturn(true);
+        setHapticScrollFeedbackEnabled(true);
+        setHapticScrollTickInterval(5);
+        mProvider = new HapticScrollFeedbackProvider(mView, mMockViewConfig,
+                /* disabledIfViewPlaysScrollHaptics= */ true);
+
+        // Expect one feedback here. Touch input should provide feedback since scroll feedback has
+        // been enabled via `setHapticScrollFeedbackEnabled(true)`.
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_TOUCHSCREEN, MotionEvent.AXIS_Y,
+                /* deltaInPixels= */ 10);
+        // Because `isViewBasedRotaryEncoderHapticScrollFeedbackEnabled()` is false and
+        // `disabledIfViewPlaysScrollHaptics` is true, the scroll progress from rotary encoders will
+        // produce no feedback.
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_2, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ 20);
+        // This event from the touch screen should produce feedback. The rotary encoder event's
+        // inability to not play scroll feedback should not impact this touch input.
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_TOUCHSCREEN, MotionEvent.AXIS_Y,
+                /* deltaInPixels= */ 30);
+
+        assertFeedbackCount(mView, HapticFeedbackConstants.SCROLL_TICK, 2);
+    }
+
+    @Test
+    public void testScrollLimit_inputDeviceCustomized_doesNotEnabledWithMotionFromDifferentDeviceId() {
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+        List<HapticFeedbackRequest> requests = new ArrayList<>();
+
+        mProvider.onSnapToItem(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ false);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+
+        mProvider.onSnapToItem(
+                INPUT_DEVICE_2, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_ITEM_FOCUS, INPUT_DEVICE_2, InputDevice.SOURCE_ROTARY_ENCODER));
+        // last snap was for input device #2, so limit for input device #1 not re-enabled.
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1,
+                InputDevice.SOURCE_ROTARY_ENCODER,
+                MotionEvent.AXIS_SCROLL,
+                /* isStart= */ false);
+
+        assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+    }
+
+    @Test
+    public void testScrollLimit_inputDeviceCustomized_doesNotEnabledWithMotionFromDifferentSource() {
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+        List<HapticFeedbackRequest> requests = new ArrayList<>();
+
+        mProvider.onSnapToItem(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ false);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        mProvider.onSnapToItem(
+                INPUT_DEVICE_1, InputDevice.SOURCE_TOUCHSCREEN, MotionEvent.AXIS_SCROLL);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_TOUCHSCREEN));
+        // last snap was for input source touch screen, so rotary's limit is NOT re-enabled.
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1,
+                InputDevice.SOURCE_ROTARY_ENCODER,
+                MotionEvent.AXIS_SCROLL,
+                /* isStart= */ false);
+
+        assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+    }
 
     private void assertNoFeedback(TestView view) {
-        for (int feedback : new int[] {SCROLL_ITEM_FOCUS, SCROLL_LIMIT, SCROLL_TICK}) {
+        for (int feedback : new int[]{SCROLL_ITEM_FOCUS, SCROLL_LIMIT, SCROLL_TICK}) {
             assertFeedbackCount(view, feedback, 0);
         }
     }
@@ -440,7 +1012,7 @@
     }
 
     private void assertOnlyFeedback(TestView view, int expectedFeedback, int expectedCount) {
-        for (int feedback : new int[] {SCROLL_ITEM_FOCUS, SCROLL_LIMIT, SCROLL_TICK}) {
+        for (int feedback : new int[]{SCROLL_ITEM_FOCUS, SCROLL_LIMIT, SCROLL_TICK}) {
             assertFeedbackCount(view, feedback, (feedback == expectedFeedback) ? expectedCount : 0);
         }
     }
@@ -460,8 +1032,9 @@
                 .thenReturn(enabled);
     }
 
-    private static class TestView extends View  {
+    private static class TestView extends View {
         final Map<Integer, Integer> mFeedbackCount = new HashMap<>();
+        final List<HapticFeedbackRequest> mHapticFeedbackRequests = new ArrayList<>();
 
         TestView(Context context) {
             super(context);
@@ -475,5 +1048,47 @@
             mFeedbackCount.put(feedback, mFeedbackCount.get(feedback) + 1);
             return true;
         }
+
+        @Override
+        public void performHapticFeedbackForInputDevice(int feedback, int inputDeviceId,
+                int inputSource, int flags) {
+            mHapticFeedbackRequests.add(
+                    new HapticFeedbackRequest(feedback, inputDeviceId, inputSource));
+        }
+    }
+
+    private static class HapticFeedbackRequest {
+        // <feedback, inputDeviceId, inputSource>
+        private final int[] mArgs = new int[3];
+
+        private HapticFeedbackRequest(int feedback, int inputDeviceId, int inputSource) {
+            mArgs[0] = feedback;
+            mArgs[1] = inputDeviceId;
+            mArgs[2] = inputSource;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj == null || getClass() != obj.getClass()) {
+                return false;
+            }
+            HapticFeedbackRequest other = (HapticFeedbackRequest) obj;
+            return Arrays.equals(this.mArgs, other.mArgs);
+        }
+
+        @Override
+        public int hashCode() {
+            // Shouldn't depend on hash. Should explicitly match mArgs.
+            return Objects.hash(mArgs[0], mArgs[1], mArgs[2]);
+        }
+
+        @Override
+        public String toString() {
+            return String.format("<feedback=%d; inputDeviceId=%d; inputSource=%d>",
+                    mArgs[0], mArgs[1], mArgs[2]);
+        }
     }
 }
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index c5b75ff..e240a08 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -18,6 +18,7 @@
 
 import static android.util.SequenceUtils.getInitSeq;
 import static android.view.HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING;
+import static android.view.InputDevice.SOURCE_ROTARY_ENCODER;
 import static android.view.Surface.FRAME_RATE_CATEGORY_DEFAULT;
 import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
 import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT;
@@ -501,6 +502,20 @@
         assertThat(result).isFalse();
     }
 
+    @UiThreadTest
+    @Test
+    public void performHapticFeedbackForInputDevice_touchFeedbackDisabled_doNothing() {
+        DisplayInfo displayInfo = new DisplayInfo();
+        displayInfo.flags = Display.FLAG_TOUCH_FEEDBACK_DISABLED;
+        Display display = new Display(DisplayManagerGlobal.getInstance(), /* displayId= */
+                0, displayInfo, new DisplayAdjustments());
+        ViewRootImpl viewRootImpl = new ViewRootImpl(sContext, display);
+
+        viewRootImpl.performHapticFeedbackForInputDevice(HapticFeedbackConstants.CONTEXT_CLICK,
+                1 /* inputDeviceId */,  SOURCE_ROTARY_ENCODER /* inputSource */,
+                FLAG_IGNORE_GLOBAL_SETTING, 0 /* privFlags */);
+    }
+
     /**
      * Test the default values are properly set
      */
@@ -1419,8 +1434,47 @@
     }
 
     @Test
+    @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
+            FLAG_TOOLKIT_FRAME_RATE_FUNCTION_ENABLING_READ_ONLY,
+            FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
+    public void votePreferredFrameRate_resetWhenDestroyingSurface()
+            throws Throwable {
+        if (!ViewProperties.vrr_enabled().orElse(true)) {
+            return;
+        }
+        mView = new View(sContext);
+        WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
+        wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
+
+        sInstrumentation.runOnMainSync(() -> {
+            WindowManager wm = sContext.getSystemService(WindowManager.class);
+            wm.addView(mView, wmlp);
+        });
+        sInstrumentation.waitForIdleSync();
+
+        mViewRootImpl = mView.getViewRootImpl();
+
+        waitForFrameRateCategoryToSettle(mView);
+
+        sInstrumentation.runOnMainSync(() -> {
+            mViewRootImpl.getView().setVisibility(View.INVISIBLE);
+            mViewRootImpl.mSurface.release();
+            mView.invalidate();
+        });
+        sInstrumentation.waitForIdleSync();
+
+        assertEquals(false, mViewRootImpl.mSurface.isValid());
+        assertEquals(FRAME_RATE_CATEGORY_DEFAULT,
+                mViewRootImpl.getLastPreferredFrameRateCategory());
+        assertEquals(FRAME_RATE_CATEGORY_DEFAULT,
+                mViewRootImpl.getPreferredFrameRateCategory());
+        assertEquals(0, mViewRootImpl.getLastPreferredFrameRate(), 0.1);
+        assertEquals(0, mViewRootImpl.getPreferredFrameRate(), 0.1);
+    }
+
+    @Test
     @RequiresFlagsEnabled(FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY)
-    public void votePreferredFrameRate_velocityVotedAfterOnDraw() throws Throwable {
+    public void votePreferredFrameRate_reset() throws Throwable {
         if (!ViewProperties.vrr_enabled().orElse(true)) {
             return;
         }
diff --git a/core/tests/coretests/src/android/widget/ChronometerActivity.java b/core/tests/coretests/src/android/widget/ChronometerActivity.java
new file mode 100644
index 0000000..aaed430
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/ChronometerActivity.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.widget;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import com.android.frameworks.coretests.R;
+
+/**
+ * A minimal application for DatePickerFocusTest.
+ */
+public class ChronometerActivity extends Activity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.chronometer_layout);
+    }
+}
diff --git a/core/tests/coretests/src/android/widget/ChronometerTest.java b/core/tests/coretests/src/android/widget/ChronometerTest.java
new file mode 100644
index 0000000..3c73837
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/ChronometerTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.widget;
+
+import android.app.Activity;
+import android.test.ActivityInstrumentationTestCase2;
+
+import androidx.test.filters.LargeTest;
+
+import com.android.frameworks.coretests.R;
+
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test {@link DatePicker} focus changes.
+ */
+@SuppressWarnings("deprecation")
+@LargeTest
+public class ChronometerTest extends ActivityInstrumentationTestCase2<ChronometerActivity> {
+
+    private Activity mActivity;
+    private Chronometer mChronometer;
+
+    public ChronometerTest() {
+        super(ChronometerActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mActivity = getActivity();
+        mChronometer = mActivity.findViewById(R.id.chronometer);
+    }
+
+    public void testChronometerTicksSequentially() throws Throwable {
+        final CountDownLatch latch = new CountDownLatch(5);
+        ArrayList<String> ticks = new ArrayList<>();
+        runOnUiThread(() -> {
+            mChronometer.setOnChronometerTickListener((chronometer) -> {
+                ticks.add(chronometer.getText().toString());
+                latch.countDown();
+                try {
+                    Thread.sleep(500);
+                } catch (InterruptedException e) {
+                }
+            });
+            mChronometer.start();
+        });
+        assertTrue(latch.await(6, TimeUnit.SECONDS));
+        assertTrue(ticks.size() >= 5);
+        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));
+    }
+
+    private void runOnUiThread(Runnable runnable) throws InterruptedException {
+        final CountDownLatch latch = new CountDownLatch(1);
+        mActivity.runOnUiThread(() -> {
+            runnable.run();
+            latch.countDown();
+        });
+        latch.await();
+    }
+}
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsSerializersTest.kt b/core/tests/coretests/src/android/widget/RemoteViewsSerializersTest.kt
index 44d10d3..b999df4 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsSerializersTest.kt
+++ b/core/tests/coretests/src/android/widget/RemoteViewsSerializersTest.kt
@@ -21,17 +21,121 @@
 import android.graphics.Bitmap
 import android.graphics.BlendMode
 import android.graphics.Color
+import android.graphics.Typeface
 import android.graphics.drawable.BitmapDrawable
 import android.graphics.drawable.Icon
+import android.graphics.text.LineBreakConfig
+import android.os.LocaleList
+import android.text.Layout
+import android.text.ParcelableSpan
+import android.text.SpannableStringBuilder
+import android.text.Spanned
+import android.text.TextPaint
+import android.text.style.AbsoluteSizeSpan
+import android.text.style.AccessibilityClickableSpan
+import android.text.style.AccessibilityReplacementSpan
+import android.text.style.AccessibilityURLSpan
+import android.text.style.AlignmentSpan
+import android.text.style.BackgroundColorSpan
+import android.text.style.BulletSpan
+import android.text.style.EasyEditSpan
+import android.text.style.ForegroundColorSpan
+import android.text.style.LeadingMarginSpan
+import android.text.style.LineBackgroundSpan
+import android.text.style.LineBreakConfigSpan
+import android.text.style.LineHeightSpan
+import android.text.style.LocaleSpan
+import android.text.style.QuoteSpan
+import android.text.style.RelativeSizeSpan
+import android.text.style.ScaleXSpan
+import android.text.style.SpellCheckSpan
+import android.text.style.StrikethroughSpan
+import android.text.style.StyleSpan
+import android.text.style.SubscriptSpan
+import android.text.style.SuggestionRangeSpan
+import android.text.style.SuggestionSpan
+import android.text.style.SuperscriptSpan
+import android.text.style.TextAppearanceSpan
+import android.text.style.TtsSpan
+import android.text.style.TypefaceSpan
+import android.text.style.URLSpan
+import android.text.style.UnderlineSpan
 import android.util.proto.ProtoInputStream
 import android.util.proto.ProtoOutputStream
+import android.widget.RemoteViewsSerializers.createAbsoluteSizeSpanFromProto
+import android.widget.RemoteViewsSerializers.createAccessibilityClickableSpanFromProto
+import android.widget.RemoteViewsSerializers.createAccessibilityReplacementSpanFromProto
+import android.widget.RemoteViewsSerializers.createAccessibilityURLSpanFromProto
+import android.widget.RemoteViewsSerializers.createAnnotationFromProto
+import android.widget.RemoteViewsSerializers.createBackgroundColorSpanFromProto
+import android.widget.RemoteViewsSerializers.createBulletSpanFromProto
+import android.widget.RemoteViewsSerializers.createCharSequenceFromProto
+import android.widget.RemoteViewsSerializers.createEasyEditSpanFromProto
+import android.widget.RemoteViewsSerializers.createForegroundColorSpanFromProto
 import android.widget.RemoteViewsSerializers.createIconFromProto
+import android.widget.RemoteViewsSerializers.createLeadingMarginSpanStandardFromProto
+import android.widget.RemoteViewsSerializers.createLineBackgroundSpanStandardFromProto
+import android.widget.RemoteViewsSerializers.createLineBreakConfigSpanFromProto
+import android.widget.RemoteViewsSerializers.createLineHeightSpanStandardFromProto
+import android.widget.RemoteViewsSerializers.createLocaleSpanFromProto
+import android.widget.RemoteViewsSerializers.createQuoteSpanFromProto
+import android.widget.RemoteViewsSerializers.createRelativeSizeSpanFromProto
+import android.widget.RemoteViewsSerializers.createScaleXSpanFromProto
+import android.widget.RemoteViewsSerializers.createStrikethroughSpanFromProto
+import android.widget.RemoteViewsSerializers.createStyleSpanFromProto
+import android.widget.RemoteViewsSerializers.createSubscriptSpanFromProto
+import android.widget.RemoteViewsSerializers.createSuggestionRangeSpanFromProto
+import android.widget.RemoteViewsSerializers.createSuggestionSpanFromProto
+import android.widget.RemoteViewsSerializers.createSuperscriptSpanFromProto
+import android.widget.RemoteViewsSerializers.createTextAppearanceSpanFromProto
+import android.widget.RemoteViewsSerializers.createTtsSpanFromProto
+import android.widget.RemoteViewsSerializers.createTypefaceSpanFromProto
+import android.widget.RemoteViewsSerializers.createURLSpanFromProto
+import android.widget.RemoteViewsSerializers.createUnderlineSpanFromProto
+import android.widget.RemoteViewsSerializers.writeAbsoluteSizeSpanToProto
+import android.widget.RemoteViewsSerializers.writeAccessibilityClickableSpanToProto
+import android.widget.RemoteViewsSerializers.writeAccessibilityReplacementSpanToProto
+import android.widget.RemoteViewsSerializers.writeAccessibilityURLSpanToProto
+import android.widget.RemoteViewsSerializers.writeAlignmentSpanStandardToProto
+import android.widget.RemoteViewsSerializers.writeAnnotationToProto
+import android.widget.RemoteViewsSerializers.writeBackgroundColorSpanToProto
+import android.widget.RemoteViewsSerializers.writeBulletSpanToProto
+import android.widget.RemoteViewsSerializers.writeCharSequenceToProto
+import android.widget.RemoteViewsSerializers.writeEasyEditSpanToProto
+import android.widget.RemoteViewsSerializers.writeForegroundColorSpanToProto
 import android.widget.RemoteViewsSerializers.writeIconToProto
+import android.widget.RemoteViewsSerializers.writeLeadingMarginSpanStandardToProto
+import android.widget.RemoteViewsSerializers.writeLineBackgroundSpanStandardToProto
+import android.widget.RemoteViewsSerializers.writeLineBreakConfigSpanToProto
+import android.widget.RemoteViewsSerializers.writeLineHeightSpanStandardToProto
+import android.widget.RemoteViewsSerializers.writeLocaleSpanToProto
+import android.widget.RemoteViewsSerializers.writeQuoteSpanToProto
+import android.widget.RemoteViewsSerializers.writeRelativeSizeSpanToProto
+import android.widget.RemoteViewsSerializers.writeScaleXSpanToProto
+import android.widget.RemoteViewsSerializers.writeStrikethroughSpanToProto
+import android.widget.RemoteViewsSerializers.writeStyleSpanToProto
+import android.widget.RemoteViewsSerializers.writeSubscriptSpanToProto
+import android.widget.RemoteViewsSerializers.writeSuggestionRangeSpanToProto
+import android.widget.RemoteViewsSerializers.writeSuggestionSpanToProto
+import android.widget.RemoteViewsSerializers.writeSuperscriptSpanToProto
+import android.widget.RemoteViewsSerializers.writeTextAppearanceSpanToProto
+import android.widget.RemoteViewsSerializers.writeTtsSpanToProto
+import android.widget.RemoteViewsSerializers.writeTypefaceSpanToProto
+import android.widget.RemoteViewsSerializers.writeURLSpanToProto
+import android.widget.RemoteViewsSerializers.writeUnderlineSpanToProto
+import androidx.core.os.persistableBundleOf
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.frameworks.coretests.R
 import com.google.common.truth.Truth.assertThat
 import java.io.ByteArrayOutputStream
+import java.util.Locale
+import kotlin.random.Random
+import kotlin.test.assertIs
+import org.junit.Assert.assertArrayEquals
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -84,6 +188,511 @@
             }
         }
     }
+
+    @Test
+    fun testWriteToProto() {
+        // This test checks that all of the supported spans are written with their start, end and
+        // flags. Span-specific data is tested in other tests.
+        val string = "0123456789"
+        data class SpanSpec(
+            val span: ParcelableSpan,
+            val start: Int = Random.nextInt(0, string.length),
+            val end: Int = Random.nextInt(start, string.length),
+            val flags: Int = Random.nextInt(0, 256).shl(Spanned.SPAN_USER_SHIFT),
+        )
+
+        val specs = listOf(
+            AbsoluteSizeSpan(0),
+            AccessibilityClickableSpan(0),
+            AccessibilityReplacementSpan(null as String?),
+            AccessibilityURLSpan(URLSpan(null)),
+            AlignmentSpan.Standard(Layout.Alignment.ALIGN_LEFT),
+            android.text.Annotation(null, null),
+            BackgroundColorSpan(0),
+            BulletSpan(0),
+            EasyEditSpan(),
+            ForegroundColorSpan(0),
+            LeadingMarginSpan.Standard(0),
+            LineBackgroundSpan.Standard(0),
+            LineBreakConfigSpan(LineBreakConfig.NONE),
+            LineHeightSpan.Standard(1),
+            LocaleSpan(LocaleList.getDefault()),
+            QuoteSpan(),
+            RelativeSizeSpan(0f),
+            ScaleXSpan(0f),
+            SpellCheckSpan(),
+            StrikethroughSpan(),
+            StyleSpan(0),
+            SubscriptSpan(),
+            SuggestionRangeSpan(),
+            SuggestionSpan(context, arrayOf(), 0),
+            SuperscriptSpan(),
+            TextAppearanceSpan(context, android.R.style.TextAppearance),
+            TtsSpan(null, persistableBundleOf()),
+            TypefaceSpan(null),
+            UnderlineSpan(),
+            URLSpan(null),
+        ).map { SpanSpec(it) }
+
+        val original = SpannableStringBuilder(string)
+        for (spec in specs) {
+            original.setSpan(spec.span, spec.start, spec.end, spec.flags)
+        }
+
+        val out = ProtoOutputStream()
+        writeCharSequenceToProto(out, original)
+        val input = ProtoInputStream(out.bytes)
+        val copy = createCharSequenceFromProto(input)
+
+        assertIs<Spanned>(copy)
+        for (spec in specs) {
+            val spans = copy.getSpans(spec.start, spec.end, Object::class.java)
+            android.util.Log.e("TestRunner", "Can I find $spec")
+            val span = spans.single { spec.span::class.java.name == it::class.java.name }
+            assertEquals(spec.flags, copy.getSpanFlags(span))
+        }
+    }
+
+    @Test
+    fun writeToProto_notSpanned() {
+        val string = "Hello World"
+        val out = ProtoOutputStream()
+        writeCharSequenceToProto(out, string)
+        val input = ProtoInputStream(out.bytes)
+        val copy = createCharSequenceFromProto(input)
+        assertIs<String>(copy)
+        assertEquals(copy, string)
+    }
+
+    @Test
+    fun testAbsoluteSizeSpan() {
+        for (span in arrayOf(AbsoluteSizeSpan(0, false), AbsoluteSizeSpan(2, true))) {
+            val out = ProtoOutputStream()
+            writeAbsoluteSizeSpanToProto(out, span)
+            val input = ProtoInputStream(out.bytes)
+            val copy = createAbsoluteSizeSpanFromProto(input)
+            assertEquals(span.size, copy.size)
+            assertEquals(span.dip, copy.dip)
+        }
+    }
+
+    @Test
+    fun testAccessibilityClickableSpan() {
+        for (id in 0..1) {
+            val span = AccessibilityClickableSpan(id)
+            val out = ProtoOutputStream()
+            writeAccessibilityClickableSpanToProto(out, span)
+            val input = ProtoInputStream(out.bytes)
+            val copy = createAccessibilityClickableSpanFromProto(input)
+            assertEquals(span.originalClickableSpanId, copy.originalClickableSpanId)
+        }
+    }
+
+    @Test
+    fun testAccessibilityReplacementSpan() {
+        for (contentDescription in arrayOf(null, "123")) {
+            val span = AccessibilityReplacementSpan(contentDescription)
+            val out = ProtoOutputStream()
+            writeAccessibilityReplacementSpanToProto(out, span)
+            val input = ProtoInputStream(out.bytes)
+            val copy = createAccessibilityReplacementSpanFromProto(input)
+            assertEquals(span.contentDescription, copy.contentDescription)
+        }
+    }
+
+    @Test
+    fun testAccessibilityURLSpan() {
+        for (url in arrayOf(null, "123")) {
+            val span = AccessibilityURLSpan(URLSpan(url))
+            val out = ProtoOutputStream()
+            writeAccessibilityURLSpanToProto(out, span)
+            val input = ProtoInputStream(out.bytes)
+            val copy = createAccessibilityURLSpanFromProto(input)
+            assertEquals(span.url, copy.url)
+        }
+    }
+
+    @Test
+    fun testAlignmentSpanStandard() {
+        for (alignment in arrayOf(
+            Layout.Alignment.ALIGN_CENTER,
+            Layout.Alignment.ALIGN_LEFT,
+            Layout.Alignment.ALIGN_NORMAL,
+            Layout.Alignment.ALIGN_OPPOSITE)) {
+            val span = AlignmentSpan.Standard(alignment)
+            val out = ProtoOutputStream()
+            writeAlignmentSpanStandardToProto(out, span)
+            val input = ProtoInputStream(out.bytes)
+            val copy = RemoteViewsSerializers.createAlignmentSpanStandardFromProto(input)
+            assertEquals(span.alignment, copy.alignment)
+        }
+    }
+
+    @Test
+    fun testAnnotation() {
+        for ((key, value) in arrayOf(null to null, "key" to "value")) {
+            val span = android.text.Annotation(key, value)
+            val out = ProtoOutputStream()
+            writeAnnotationToProto(out, span)
+            val input = ProtoInputStream(out.bytes)
+            val copy = createAnnotationFromProto(input)
+            assertEquals(span.key, copy.key)
+            assertEquals(span.value, copy.value)
+        }
+    }
+
+    @Test
+    fun testBackgroundColorSpan() {
+        for (color in intArrayOf(Color.RED, Color.MAGENTA)) {
+            val span = BackgroundColorSpan(color)
+            val out = ProtoOutputStream()
+            writeBackgroundColorSpanToProto(out, span)
+            val input = ProtoInputStream(out.bytes)
+            val copy = createBackgroundColorSpanFromProto(input)
+            assertEquals(span.backgroundColor, copy.backgroundColor)
+        }
+    }
+
+    @Test
+    fun testBulletSpan() {
+        for (span in arrayOf(BulletSpan(), BulletSpan(2, Color.RED, 5))) {
+            val out = ProtoOutputStream()
+            writeBulletSpanToProto(out, span)
+            val input = ProtoInputStream(out.bytes)
+            val copy = createBulletSpanFromProto(input)
+            assertEquals(span.getLeadingMargin(true), copy.getLeadingMargin(true))
+            assertEquals(span.color, copy.color)
+            assertEquals(span.color, copy.color)
+            assertEquals(span.gapWidth, copy.gapWidth)
+        }
+    }
+
+    @Test
+    fun testEasyEditSpan() {
+        val span = EasyEditSpan()
+        val out = ProtoOutputStream()
+        writeEasyEditSpanToProto(out, span)
+        val input = ProtoInputStream(out.bytes)
+        createEasyEditSpanFromProto(input)
+    }
+
+    @Test
+    fun testForegroundColorSpan() {
+        for (color in intArrayOf(0, Color.RED, Color.MAGENTA)) {
+            val span = ForegroundColorSpan(color)
+            val out = ProtoOutputStream()
+            writeForegroundColorSpanToProto(out, span)
+            val input = ProtoInputStream(out.bytes)
+            val copy = createForegroundColorSpanFromProto(input)
+            assertEquals(span.foregroundColor.toLong(), copy.foregroundColor.toLong())
+        }
+    }
+
+    @Test
+    fun testLeadingMarginSpanStandard() {
+        for (span in arrayOf(LeadingMarginSpan.Standard(10, 20), LeadingMarginSpan.Standard(0))) {
+            val out = ProtoOutputStream()
+            writeLeadingMarginSpanStandardToProto(out, span)
+            val input = ProtoInputStream(out.bytes)
+            val copy = createLeadingMarginSpanStandardFromProto(input)
+            assertEquals(span.getLeadingMargin(true), copy.getLeadingMargin(true))
+            assertEquals(span.getLeadingMargin(false), copy.getLeadingMargin(false))
+        }
+    }
+
+    @Test
+    fun testLineBackgroundSpan() {
+        for (color in intArrayOf(0, Color.RED, Color.MAGENTA)) {
+            val span = LineBackgroundSpan.Standard(color)
+            val out = ProtoOutputStream()
+            writeLineBackgroundSpanStandardToProto(out, span)
+            val input = ProtoInputStream(out.bytes)
+            val copy = createLineBackgroundSpanStandardFromProto(input)
+            assertEquals(span.color, copy.color)
+        }
+    }
+
+    @Test
+    fun testLineBreakConfigSpan() {
+        val config = LineBreakConfig.Builder()
+            .setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_STRICT)
+            .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_AUTO)
+            .setHyphenation(LineBreakConfig.HYPHENATION_ENABLED)
+            .build()
+        val span = LineBreakConfigSpan(config)
+        val out = ProtoOutputStream()
+        writeLineBreakConfigSpanToProto(out, span)
+        val input = ProtoInputStream(out.bytes)
+        val copy = createLineBreakConfigSpanFromProto(input).lineBreakConfig
+        assertEquals(copy.lineBreakStyle, config.lineBreakStyle)
+        assertEquals(copy.lineBreakWordStyle, config.lineBreakWordStyle)
+        assertEquals(copy.hyphenation, config.hyphenation)
+    }
+
+    @Test
+    fun testLineHeightSpanStandard() {
+        for (height in 1..2) {
+            val span = LineHeightSpan.Standard(height)
+            val out = ProtoOutputStream()
+            writeLineHeightSpanStandardToProto(out, span)
+            val input = ProtoInputStream(out.bytes)
+            val copy = createLineHeightSpanStandardFromProto(input)
+            assertEquals(span.height, copy.height)
+        }
+    }
+
+    @Test
+    fun testLocaleSpan() {
+        for (list in arrayOf(
+            LocaleList.getEmptyLocaleList(),
+            LocaleList.forLanguageTags("en"),
+            LocaleList.forLanguageTags("en-GB,en"),
+        )) {
+            val span = LocaleSpan(list)
+            val out = ProtoOutputStream()
+            writeLocaleSpanToProto(out, span)
+            val input = ProtoInputStream(out.bytes)
+            val copy = createLocaleSpanFromProto(input)
+            assertEquals(span.locales[0], copy.locale)
+            assertEquals(span.locales, copy.locales)
+        }
+    }
+
+    @Test
+    fun testQuoteSpan() {
+        for (color in intArrayOf(0, Color.RED, Color.MAGENTA)) {
+            val span = QuoteSpan(color)
+            val out = ProtoOutputStream()
+            writeQuoteSpanToProto(out, span)
+            val input = ProtoInputStream(out.bytes)
+            val copy = createQuoteSpanFromProto(input)
+            assertEquals(span.color, copy.color)
+            assertTrue(span.gapWidth > 0)
+            assertTrue(span.stripeWidth > 0)
+        }
+    }
+
+    @Test
+    fun testRelativeSizeSpan() {
+        for (size in arrayOf(0f, 1.0f)) {
+            val span = RelativeSizeSpan(size)
+            val out = ProtoOutputStream()
+            writeRelativeSizeSpanToProto(out, span)
+            val input = ProtoInputStream(out.bytes)
+            val copy = createRelativeSizeSpanFromProto(input)
+            assertEquals(span.sizeChange, copy.sizeChange)
+        }
+    }
+
+    @Test
+    fun testScaleXSpan() {
+        for (scale in arrayOf(0f, 1.0f)) {
+            val span = ScaleXSpan(scale)
+            val out = ProtoOutputStream()
+            writeScaleXSpanToProto(out, span)
+            val input = ProtoInputStream(out.bytes)
+            val copy = createScaleXSpanFromProto(input)
+            assertEquals(span.scaleX, copy.scaleX, 0.0f)
+        }
+    }
+
+    @Test
+    fun testStrikethroughSpan() {
+        val span = StrikethroughSpan()
+        val out = ProtoOutputStream()
+        writeStrikethroughSpanToProto(out, span)
+        val input = ProtoInputStream(out.bytes)
+        createStrikethroughSpanFromProto(input)
+    }
+
+    @Test
+    fun testStyleSpan() {
+        for (style in arrayOf(Typeface.BOLD, Typeface.NORMAL)) {
+            val span = StyleSpan(style)
+            val out = ProtoOutputStream()
+            writeStyleSpanToProto(out, span)
+            val input = ProtoInputStream(out.bytes)
+            val copy = createStyleSpanFromProto(input)
+            assertEquals(span.style, copy.style)
+        }
+    }
+
+    @Test
+    fun testSubscriptSpan() {
+        val span = SubscriptSpan()
+        val out = ProtoOutputStream()
+        writeSubscriptSpanToProto(out, span)
+        val input = ProtoInputStream(out.bytes)
+        createSubscriptSpanFromProto(input)
+    }
+
+    @Test
+    fun testSuggestionSpan() {
+        val suggestions = arrayOf("suggestion1", "suggestion2")
+        val span = SuggestionSpan(
+            Locale.forLanguageTag("en"), suggestions,
+            SuggestionSpan.FLAG_AUTO_CORRECTION)
+
+        val out = ProtoOutputStream()
+        writeSuggestionSpanToProto(out, span)
+        val input = ProtoInputStream(out.bytes)
+        val copy = createSuggestionSpanFromProto(input)
+        assertArrayEquals("Should (de)serialize suggestions",
+            suggestions, copy.suggestions)
+    }
+
+    @Test
+    fun testSuggestionRangeSpan() {
+        for (backgroundColor in 0..1) {
+            val span = SuggestionRangeSpan()
+            span.backgroundColor = backgroundColor
+            val out = ProtoOutputStream()
+            writeSuggestionRangeSpanToProto(out, span)
+            val input = ProtoInputStream(out.bytes)
+            val copy = createSuggestionRangeSpanFromProto(input)
+            assertEquals(span.backgroundColor, copy.backgroundColor)
+        }
+    }
+
+    @Test
+    fun testSuperscriptSpan() {
+        val span = SuperscriptSpan()
+        val out = ProtoOutputStream()
+        writeSuperscriptSpanToProto(out, span)
+        val input = ProtoInputStream(out.bytes)
+        createSuperscriptSpanFromProto(input)
+    }
+
+
+    @Test
+    fun testTextAppearanceSpan_FontResource() {
+        val span = TextAppearanceSpan(context, R.style.customFont)
+        val out = ProtoOutputStream()
+        writeTextAppearanceSpanToProto(out, span)
+        val input = ProtoInputStream(out.bytes)
+        val copy = createTextAppearanceSpanFromProto(input)
+        val tp = TextPaint()
+        span.updateDrawState(tp)
+        val originalSpanTextWidth = tp.measureText("a")
+        copy.updateDrawState(tp)
+        assertEquals(originalSpanTextWidth, tp.measureText("a"), 0.0f)
+    }
+
+    @Test
+    fun testTextAppearanceSpan_FontResource_WithStyle() {
+        val span = TextAppearanceSpan(context, R.style.customFontWithStyle)
+        val out = ProtoOutputStream()
+        writeTextAppearanceSpanToProto(out, span)
+        val input = ProtoInputStream(out.bytes)
+        val copy = createTextAppearanceSpanFromProto(input)
+        val tp = TextPaint()
+        span.updateDrawState(tp)
+        val originalSpanTextWidth = tp.measureText("a")
+        copy.updateDrawState(tp)
+        assertEquals(originalSpanTextWidth, tp.measureText("a"), 0.0f)
+    }
+
+    @Test
+    fun testTextAppearanceSpan_WithAllAttributes() {
+        val span = TextAppearanceSpan(context, R.style.textAppearanceWithAllAttributes)
+        val out = ProtoOutputStream()
+        writeTextAppearanceSpanToProto(out, span)
+        val input = ProtoInputStream(out.bytes)
+        val copy = createTextAppearanceSpanFromProto(input)
+        val originalTextColor = span.textColor
+        val copyTextColor = copy.textColor
+        val originalLinkTextColor = span.linkTextColor
+        val copyLinkTextColor = copy.linkTextColor
+        assertEquals(span.family, copy.family)
+        // ColorStateList doesn't implement equals(), so we borrow this code
+        // from ColorStateListTest.java to test correctness of parceling.
+        assertEquals(originalTextColor.isStateful, copyTextColor.isStateful)
+        assertEquals(originalTextColor.defaultColor, copyTextColor.defaultColor)
+        assertEquals(originalLinkTextColor.isStateful,
+            copyLinkTextColor.isStateful)
+        assertEquals(originalLinkTextColor.defaultColor,
+            copyLinkTextColor.defaultColor)
+        assertEquals(span.textSize.toLong(), copy.textSize.toLong())
+        assertEquals(span.textStyle.toLong(), copy.textStyle.toLong())
+        assertEquals(span.textFontWeight.toLong(), copy.textFontWeight.toLong())
+        assertEquals(span.textLocales, copy.textLocales)
+        assertEquals(span.shadowColor.toLong(), copy.shadowColor.toLong())
+        assertEquals(span.shadowDx, copy.shadowDx, 0.0f)
+        assertEquals(span.shadowDy, copy.shadowDy, 0.0f)
+        assertEquals(span.shadowRadius, copy.shadowRadius, 0.0f)
+        assertEquals(span.fontFeatureSettings, copy.fontFeatureSettings)
+        assertEquals(span.fontVariationSettings, copy.fontVariationSettings)
+        assertEquals(span.isElegantTextHeight, copy.isElegantTextHeight)
+        assertEquals(span.letterSpacing, copy.letterSpacing, 0f)
+        // typeface is omitted from TextAppearanceSpan proto
+    }
+
+    @Test
+    fun testTtsSpan() {
+        val bundle = persistableBundleOf(
+            "argument.one" to "value.one",
+            "argument.two" to "value.two",
+            "argument.three" to 3L,
+            "argument.four" to 4L,
+        )
+        val span = TtsSpan("test.type.five", bundle)
+        val out = ProtoOutputStream()
+        writeTtsSpanToProto(out, span)
+        val input = ProtoInputStream(out.bytes)
+        val copy = createTtsSpanFromProto(input)
+        assertEquals("test.type.five", copy.type)
+        val args = copy.args
+        assertEquals(4, args.size())
+        assertEquals("value.one", args.getString("argument.one"))
+        assertEquals("value.two", args.getString("argument.two"))
+        assertEquals(3, args.getLong("argument.three"))
+        assertEquals(4, args.getLong("argument.four"))
+    }
+
+
+    @Test
+    fun testTtsSpan_null() {
+        val span = TtsSpan(null, null)
+        val out = ProtoOutputStream()
+        writeTtsSpanToProto(out, span)
+        val input = ProtoInputStream(out.bytes)
+        val copy = createTtsSpanFromProto(input)
+        assertNull(copy.type)
+        assertNull(copy.args)
+    }
+
+    @Test
+    fun testTypefaceSpan() {
+        for (family in arrayOf(null, "monospace")) {
+            val span = TypefaceSpan(family)
+            val out = ProtoOutputStream()
+            writeTypefaceSpanToProto(out, span)
+            val input = ProtoInputStream(out.bytes)
+            val copy = createTypefaceSpanFromProto(input)
+            assertEquals(span.family, copy.family)
+        }
+    }
+
+    @Test
+    fun testUnderlineSpan() {
+        val span = UnderlineSpan()
+        val out = ProtoOutputStream()
+        writeUnderlineSpanToProto(out, span)
+        val input = ProtoInputStream(out.bytes)
+        createUnderlineSpanFromProto(input)
+    }
+
+    @Test
+    fun testURLSpan() {
+        for (url in arrayOf(null, "content://url")) {
+            val span = URLSpan(url)
+            val out = ProtoOutputStream()
+            writeURLSpanToProto(out, span)
+            val input = ProtoInputStream(out.bytes)
+            val copy = createURLSpanFromProto(input)
+            assertEquals(span.url, copy.url)
+        }
+    }
 }
 
 fun equalColorStateLists(a: ColorStateList?, b: ColorStateList?): Boolean {
diff --git a/core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java b/core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java
index 6c8dcd3..fdc00ba 100644
--- a/core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java
+++ b/core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java
@@ -93,7 +93,8 @@
                 ColorSpace.get(ColorSpace.Named.SRGB), ORIENTATION_PORTRAIT,
                 Surface.ROTATION_0, taskSize, contentInsets, new Rect() /* letterboxInsets */,
                 false, true /* isRealSnapshot */, WINDOWING_MODE_FULLSCREEN,
-                0 /* systemUiVisibility */, false /* isTranslucent */, false /* hasImeSurface */);
+                0 /* systemUiVisibility */, false /* isTranslucent */, false /* hasImeSurface */,
+                0 /* uiMode */);
     }
 
     private static TaskDescription createTaskDescription(int background,
diff --git a/core/tests/coretests/src/com/android/internal/jank/CujTest.java b/core/tests/coretests/src/com/android/internal/jank/CujTest.java
index bf35ed0..2362a4c 100644
--- a/core/tests/coretests/src/com/android/internal/jank/CujTest.java
+++ b/core/tests/coretests/src/com/android/internal/jank/CujTest.java
@@ -35,7 +35,6 @@
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
 import java.util.Arrays;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -47,26 +46,30 @@
 public class CujTest {
     private static final String ENUM_NAME_PREFIX =
             "UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__";
-    private static final Set<String> DEPRECATED_VALUES = new HashSet<>() {
-        {
-            add(ENUM_NAME_PREFIX + "IME_INSETS_ANIMATION");
-        }
-    };
-    private static final Map<Integer, String> ENUM_NAME_EXCEPTION_MAP = new HashMap<>() {
-        {
-            put(Cuj.CUJ_NOTIFICATION_ADD, getEnumName("SHADE_NOTIFICATION_ADD"));
-            put(Cuj.CUJ_NOTIFICATION_HEADS_UP_APPEAR, getEnumName("SHADE_HEADS_UP_APPEAR"));
-            put(Cuj.CUJ_NOTIFICATION_APP_START, getEnumName("SHADE_APP_LAUNCH"));
-            put(Cuj.CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR, getEnumName("SHADE_HEADS_UP_DISAPPEAR"));
-            put(Cuj.CUJ_NOTIFICATION_REMOVE, getEnumName("SHADE_NOTIFICATION_REMOVE"));
-            put(Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, getEnumName("NOTIFICATION_SHADE_SWIPE"));
-            put(Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE, getEnumName("SHADE_QS_EXPAND_COLLAPSE"));
-            put(Cuj.CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE, getEnumName("SHADE_QS_SCROLL_SWIPE"));
-            put(Cuj.CUJ_NOTIFICATION_SHADE_ROW_EXPAND, getEnumName("SHADE_ROW_EXPAND"));
-            put(Cuj.CUJ_NOTIFICATION_SHADE_ROW_SWIPE, getEnumName("SHADE_ROW_SWIPE"));
-            put(Cuj.CUJ_NOTIFICATION_SHADE_SCROLL_FLING, getEnumName("SHADE_SCROLL_FLING"));
-        }
-    };
+    private static final Set<String> DEPRECATED_VALUES = Set.of(
+            ENUM_NAME_PREFIX + "IME_INSETS_ANIMATION"
+    );
+    private static final Map<Integer, String> ENUM_NAME_EXCEPTION_MAP = Map.ofEntries(
+            Map.entry(Cuj.CUJ_NOTIFICATION_ADD, getEnumName("SHADE_NOTIFICATION_ADD")),
+            Map.entry(Cuj.CUJ_NOTIFICATION_HEADS_UP_APPEAR, getEnumName("SHADE_HEADS_UP_APPEAR")),
+            Map.entry(Cuj.CUJ_NOTIFICATION_APP_START, getEnumName("SHADE_APP_LAUNCH")),
+            Map.entry(
+                    Cuj.CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR,
+                    getEnumName("SHADE_HEADS_UP_DISAPPEAR")),
+            Map.entry(Cuj.CUJ_NOTIFICATION_REMOVE, getEnumName("SHADE_NOTIFICATION_REMOVE")),
+            Map.entry(
+                    Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE,
+                    getEnumName("NOTIFICATION_SHADE_SWIPE")),
+            Map.entry(
+                        Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE,
+                        getEnumName("SHADE_QS_EXPAND_COLLAPSE")),
+            Map.entry(
+                    Cuj.CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE,
+                    getEnumName("SHADE_QS_SCROLL_SWIPE")),
+            Map.entry(Cuj.CUJ_NOTIFICATION_SHADE_ROW_EXPAND, getEnumName("SHADE_ROW_EXPAND")),
+            Map.entry(Cuj.CUJ_NOTIFICATION_SHADE_ROW_SWIPE, getEnumName("SHADE_ROW_SWIPE")),
+            Map.entry(Cuj.CUJ_NOTIFICATION_SHADE_SCROLL_FLING, getEnumName("SHADE_SCROLL_FLING"))
+    );
 
     @Rule
     public final Expect mExpect = Expect.create();
diff --git a/core/tests/resourceflaggingtests/Android.bp b/core/tests/resourceflaggingtests/Android.bp
index dd86094..efb8437 100644
--- a/core/tests/resourceflaggingtests/Android.bp
+++ b/core/tests/resourceflaggingtests/Android.bp
@@ -22,54 +22,6 @@
     default_team: "trendy_team_android_resources",
 }
 
-genrule {
-    name: "resource-flagging-test-app-resources-compile",
-    tools: ["aapt2"],
-    srcs: [
-        "flagged_resources_res/values/bools.xml",
-    ],
-    out: ["values_bools.arsc.flat"],
-    cmd: "$(location aapt2) compile $(in) -o $(genDir) " +
-        "--feature-flags test.package.falseFlag:ro=false,test.package.trueFlag:ro=true",
-}
-
-genrule {
-    name: "resource-flagging-test-app-resources-compile2",
-    tools: ["aapt2"],
-    srcs: [
-        "flagged_resources_res/values/bools2.xml",
-    ],
-    out: ["values_bools2.arsc.flat"],
-    cmd: "$(location aapt2) compile $(in) -o $(genDir) " +
-        "--feature-flags test.package.falseFlag:ro=false,test.package.trueFlag:ro=true",
-}
-
-genrule {
-    name: "resource-flagging-test-app-apk",
-    tools: ["aapt2"],
-    // The first input file in the list must be the manifest
-    srcs: [
-        "TestAppAndroidManifest.xml",
-        ":resource-flagging-test-app-resources-compile",
-        ":resource-flagging-test-app-resources-compile2",
-    ],
-    out: ["resapp.apk"],
-    cmd: "$(location aapt2) link -o $(out) --manifest $(in)",
-}
-
-java_genrule {
-    name: "resource-flagging-apk-as-resource",
-    srcs: [
-        ":resource-flagging-test-app-apk",
-    ],
-    out: ["apks_as_resources.res.zip"],
-    tools: ["soong_zip"],
-
-    cmd: "mkdir -p $(genDir)/res/raw && " +
-        "cp $(in) $(genDir)/res/raw/$$(basename $(in)) && " +
-        "$(location soong_zip) -o $(out) -C $(genDir)/res -D $(genDir)/res",
-}
-
 android_test {
     name: "ResourceFlaggingTests",
     srcs: [
@@ -82,6 +34,6 @@
         "testng",
         "compatibility-device-util-axt",
     ],
-    resource_zips: [":resource-flagging-apk-as-resource"],
+    resource_zips: [":resource-flagging-test-app-apk-as-resource"],
     test_suites: ["device-tests"],
 }
diff --git a/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java b/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java
index ad8542e..c1e3578 100644
--- a/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java
+++ b/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java
@@ -69,11 +69,23 @@
     }
 
     private boolean getBoolean(String name) {
-        int resId = mResources.getIdentifier(name, "bool", "com.android.intenal.flaggedresources");
+        int resId = mResources.getIdentifier(
+                name,
+                "bool",
+                "com.android.intenal.flaggedresources");
         assertThat(resId).isNotEqualTo(0);
         return mResources.getBoolean(resId);
     }
 
+    private String getString(String name) {
+        int resId = mResources.getIdentifier(
+                name,
+                "string",
+                "com.android.intenal.flaggedresources");
+        assertThat(resId).isNotEqualTo(0);
+        return mResources.getString(resId);
+    }
+
     private String extractApkAndGetPath(int id) throws Exception {
         final Resources resources = mContext.getResources();
         try (InputStream is = resources.openRawResource(id)) {
diff --git a/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java
index fc233fb..3b9f35b 100644
--- a/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java
@@ -161,15 +161,15 @@
     }
 
     @Test
-    public void testAppendBoolean() throws Exception {
+    public void testAppendBooleanDuplicatesAllowed() throws Exception {
         assertArrayEquals(new boolean[] { true },
-                ArrayUtils.appendBoolean(null, true));
+                ArrayUtils.appendBooleanDuplicatesAllowed(null, true));
         assertArrayEquals(new boolean[] { true },
-                ArrayUtils.appendBoolean(new boolean[] { }, true));
+                ArrayUtils.appendBooleanDuplicatesAllowed(new boolean[] { }, true));
         assertArrayEquals(new boolean[] { true, false },
-                ArrayUtils.appendBoolean(new boolean[] { true }, false));
+                ArrayUtils.appendBooleanDuplicatesAllowed(new boolean[] { true }, false));
         assertArrayEquals(new boolean[] { true, true },
-                ArrayUtils.appendBoolean(new boolean[] { true }, true));
+                ArrayUtils.appendBooleanDuplicatesAllowed(new boolean[] { true }, true));
     }
 
     @Test
diff --git a/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java
index e9a08ae..97f1d5e 100644
--- a/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java
@@ -27,7 +27,11 @@
 import android.os.Parcel;
 import android.os.VibrationEffect;
 import android.os.VibratorInfo;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -36,6 +40,9 @@
 public class PrimitiveSegmentTest {
     private static final float TOLERANCE = 1e-2f;
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Test
     public void testCreation() {
         PrimitiveSegment primitive = new PrimitiveSegment(
@@ -87,7 +94,8 @@
     }
 
     @Test
-    public void testScale_fullPrimitiveScaleValue() {
+    @DisableFlags(Flags.FLAG_HAPTICS_SCALE_V2_ENABLED)
+    public void testScale_withLegacyScaling_fullPrimitiveScaleValue() {
         PrimitiveSegment initial = new PrimitiveSegment(
                 VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0);
 
@@ -102,7 +110,24 @@
     }
 
     @Test
-    public void testScale_halfPrimitiveScaleValue() {
+    @EnableFlags(Flags.FLAG_HAPTICS_SCALE_V2_ENABLED)
+    public void testScale_withScalingV2_fullPrimitiveScaleValue() {
+        PrimitiveSegment initial = new PrimitiveSegment(
+                VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0);
+
+        assertEquals(1f, initial.scale(1).getScale(), TOLERANCE);
+        assertEquals(0.5f, initial.scale(0.5f).getScale(), TOLERANCE);
+        // The original value was not scaled up, so this only scales it down.
+        assertEquals(1f, initial.scale(1.5f).getScale(), TOLERANCE);
+        assertEquals(2 / 3f, initial.scale(1.5f).scale(2 / 3f).getScale(), TOLERANCE);
+        // Does not restore to the exact original value because scale up is a bit offset.
+        assertEquals(0.8f, initial.scale(0.8f).getScale(), TOLERANCE);
+        assertEquals(0.86f, initial.scale(0.8f).scale(1.25f).getScale(), TOLERANCE);
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_HAPTICS_SCALE_V2_ENABLED)
+    public void testScale_withLegacyScaling_halfPrimitiveScaleValue() {
         PrimitiveSegment initial = new PrimitiveSegment(
                 VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 0);
 
@@ -117,6 +142,22 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_HAPTICS_SCALE_V2_ENABLED)
+    public void testScale_withScalingV2_halfPrimitiveScaleValue() {
+        PrimitiveSegment initial = new PrimitiveSegment(
+                VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 0);
+
+        assertEquals(0.5f, initial.scale(1).getScale(), TOLERANCE);
+        assertEquals(0.25f, initial.scale(0.5f).getScale(), TOLERANCE);
+        // The original value was not scaled up, so this only scales it down.
+        assertEquals(0.66f, initial.scale(1.5f).getScale(), TOLERANCE);
+        assertEquals(0.44f, initial.scale(1.5f).scale(2 / 3f).getScale(), TOLERANCE);
+        // Does not restore to the exact original value because scale up is a bit offset.
+        assertEquals(0.4f, initial.scale(0.8f).getScale(), TOLERANCE);
+        assertEquals(0.48f, initial.scale(0.8f).scale(1.25f).getScale(), TOLERANCE);
+    }
+
+    @Test
     public void testScale_zeroPrimitiveScaleValue() {
         PrimitiveSegment initial = new PrimitiveSegment(
                 VibrationEffect.Composition.PRIMITIVE_CLICK, 0, 0);
diff --git a/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java
index 01013ab..bea8293 100644
--- a/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java
@@ -29,7 +29,11 @@
 import android.os.Parcel;
 import android.os.VibrationEffect;
 import android.os.VibratorInfo;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -38,6 +42,9 @@
 public class RampSegmentTest {
     private static final float TOLERANCE = 1e-2f;
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Test
     public void testCreation() {
         RampSegment ramp = new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 0,
@@ -97,14 +104,18 @@
     }
 
     @Test
-    public void testScale() {
-        RampSegment initial = new RampSegment(0, 1, 0, 0, 0);
+    @DisableFlags(Flags.FLAG_HAPTICS_SCALE_V2_ENABLED)
+    public void testScale_withLegacyScaling_halfAndFullAmplitudes() {
+        RampSegment initial = new RampSegment(0.5f, 1, 0, 0, 0);
 
-        assertEquals(0f, initial.scale(1).getStartAmplitude(), TOLERANCE);
-        assertEquals(0f, initial.scale(0.5f).getStartAmplitude(), TOLERANCE);
-        assertEquals(0f, initial.scale(1.5f).getStartAmplitude(), TOLERANCE);
-        assertEquals(0f, initial.scale(1.5f).scale(2 / 3f).getStartAmplitude(), TOLERANCE);
-        assertEquals(0f, initial.scale(0.8f).scale(1.25f).getStartAmplitude(), TOLERANCE);
+        assertEquals(0.5f, initial.scale(1).getStartAmplitude(), TOLERANCE);
+        assertEquals(0.17f, initial.scale(0.5f).getStartAmplitude(), TOLERANCE);
+        // The original value was not scaled up, so this only scales it down.
+        assertEquals(0.86f, initial.scale(1.5f).getStartAmplitude(), TOLERANCE);
+        assertEquals(0.47f, initial.scale(1.5f).scale(2 / 3f).getStartAmplitude(), TOLERANCE);
+        // Does not restore to the exact original value because scale up is a bit offset.
+        assertEquals(0.35f, initial.scale(0.8f).getStartAmplitude(), TOLERANCE);
+        assertEquals(0.5f, initial.scale(0.8f).scale(1.25f).getStartAmplitude(), TOLERANCE);
 
         assertEquals(1f, initial.scale(1).getEndAmplitude(), TOLERANCE);
         assertEquals(0.34f, initial.scale(0.5f).getEndAmplitude(), TOLERANCE);
@@ -117,17 +128,38 @@
     }
 
     @Test
-    public void testScale_halfPrimitiveScaleValue() {
+    @EnableFlags(Flags.FLAG_HAPTICS_SCALE_V2_ENABLED)
+    public void testScale_withScalingV2_halfAndFullAmplitudes() {
         RampSegment initial = new RampSegment(0.5f, 1, 0, 0, 0);
 
         assertEquals(0.5f, initial.scale(1).getStartAmplitude(), TOLERANCE);
-        assertEquals(0.17f, initial.scale(0.5f).getStartAmplitude(), TOLERANCE);
+        assertEquals(0.25f, initial.scale(0.5f).getStartAmplitude(), TOLERANCE);
+        // The original value was not scaled up, so this only scales it down.
+        assertEquals(0.66f, initial.scale(1.5f).getStartAmplitude(), TOLERANCE);
+        assertEquals(0.44f, initial.scale(1.5f).scale(2 / 3f).getStartAmplitude(), TOLERANCE);
         // Does not restore to the exact original value because scale up is a bit offset.
-        assertEquals(0.86f, initial.scale(1.5f).getStartAmplitude(), TOLERANCE);
-        assertEquals(0.47f, initial.scale(1.5f).scale(2 / 3f).getStartAmplitude(), TOLERANCE);
+        assertEquals(0.4f, initial.scale(0.8f).getStartAmplitude(), TOLERANCE);
+        assertEquals(0.48f, initial.scale(0.8f).scale(1.25f).getStartAmplitude(), TOLERANCE);
+
+        assertEquals(1f, initial.scale(1).getEndAmplitude(), TOLERANCE);
+        assertEquals(0.5f, initial.scale(0.5f).getEndAmplitude(), TOLERANCE);
+        // The original value was not scaled up, so this only scales it down.
+        assertEquals(1f, initial.scale(1.5f).getEndAmplitude(), TOLERANCE);
+        assertEquals(2 / 3f, initial.scale(1.5f).scale(2 / 3f).getEndAmplitude(), TOLERANCE);
         // Does not restore to the exact original value because scale up is a bit offset.
-        assertEquals(0.35f, initial.scale(0.8f).getStartAmplitude(), TOLERANCE);
-        assertEquals(0.5f, initial.scale(0.8f).scale(1.25f).getStartAmplitude(), TOLERANCE);
+        assertEquals(0.81f, initial.scale(0.8f).getEndAmplitude(), TOLERANCE);
+        assertEquals(0.86f, initial.scale(0.8f).scale(1.25f).getEndAmplitude(), TOLERANCE);
+    }
+
+    @Test
+    public void testScale_zeroAmplitude() {
+        RampSegment initial = new RampSegment(0, 1, 0, 0, 0);
+
+        assertEquals(0f, initial.scale(1).getStartAmplitude(), TOLERANCE);
+        assertEquals(0f, initial.scale(0.5f).getStartAmplitude(), TOLERANCE);
+        assertEquals(0f, initial.scale(1.5f).getStartAmplitude(), TOLERANCE);
+        assertEquals(0f, initial.scale(1.5f).scale(2 / 3f).getStartAmplitude(), TOLERANCE);
+        assertEquals(0f, initial.scale(0.8f).scale(1.25f).getStartAmplitude(), TOLERANCE);
     }
 
     @Test
diff --git a/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java
index 40776ab..411074a 100644
--- a/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java
@@ -27,7 +27,11 @@
 import android.os.Parcel;
 import android.os.VibrationEffect;
 import android.os.VibratorInfo;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -35,6 +39,10 @@
 @RunWith(JUnit4.class)
 public class StepSegmentTest {
     private static final float TOLERANCE = 1e-2f;
+
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Test
     public void testCreation() {
         StepSegment step = new StepSegment(/* amplitude= */ 1f, /* frequencyHz= */ 1f,
@@ -93,7 +101,8 @@
     }
 
     @Test
-    public void testScale_fullAmplitude() {
+    @DisableFlags(Flags.FLAG_HAPTICS_SCALE_V2_ENABLED)
+    public void testScale_withLegacyScaling_fullAmplitude() {
         StepSegment initial = new StepSegment(1f, 0, 0);
 
         assertEquals(1f, initial.scale(1).getAmplitude(), TOLERANCE);
@@ -107,7 +116,23 @@
     }
 
     @Test
-    public void testScale_halfAmplitude() {
+    @EnableFlags(Flags.FLAG_HAPTICS_SCALE_V2_ENABLED)
+    public void testScale_withScalingV2_fullAmplitude() {
+        StepSegment initial = new StepSegment(1f, 0, 0);
+
+        assertEquals(1f, initial.scale(1).getAmplitude(), TOLERANCE);
+        assertEquals(0.5f, initial.scale(0.5f).getAmplitude(), TOLERANCE);
+        // The original value was not scaled up, so this only scales it down.
+        assertEquals(1f, initial.scale(1.5f).getAmplitude(), TOLERANCE);
+        assertEquals(2 / 3f, initial.scale(1.5f).scale(2 / 3f).getAmplitude(), TOLERANCE);
+        // Does not restore to the exact original value because scale up is a bit offset.
+        assertEquals(0.8f, initial.scale(0.8f).getAmplitude(), TOLERANCE);
+        assertEquals(0.86f, initial.scale(0.8f).scale(1.25f).getAmplitude(), TOLERANCE);
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_HAPTICS_SCALE_V2_ENABLED)
+    public void testScale_withLegacyScaling_halfAmplitude() {
         StepSegment initial = new StepSegment(0.5f, 0, 0);
 
         assertEquals(0.5f, initial.scale(1).getAmplitude(), TOLERANCE);
@@ -121,6 +146,21 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_HAPTICS_SCALE_V2_ENABLED)
+    public void testScale_withScalingV2_halfAmplitude() {
+        StepSegment initial = new StepSegment(0.5f, 0, 0);
+
+        assertEquals(0.5f, initial.scale(1).getAmplitude(), TOLERANCE);
+        assertEquals(0.25f, initial.scale(0.5f).getAmplitude(), TOLERANCE);
+        // The original value was not scaled up, so this only scales it down.
+        assertEquals(0.66f, initial.scale(1.5f).getAmplitude(), TOLERANCE);
+        assertEquals(0.44f, initial.scale(1.5f).scale(2 / 3f).getAmplitude(), TOLERANCE);
+        // Does not restore to the exact original value because scale up is a bit offset.
+        assertEquals(0.4f, initial.scale(0.8f).getAmplitude(), TOLERANCE);
+        assertEquals(0.48f, initial.scale(0.8f).scale(1.25f).getAmplitude(), TOLERANCE);
+    }
+
+    @Test
     public void testScale_zeroAmplitude() {
         StepSegment initial = new StepSegment(0, 0, 0);
 
diff --git a/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java b/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java
index bf9a820..1cc38de 100644
--- a/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java
@@ -24,22 +24,32 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertThrows;
 
+import android.os.PersistableBundle;
 import android.os.VibrationEffect;
+import android.os.vibrator.Flags;
 import android.os.vibrator.PrebakedSegment;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.Xml;
 
 import com.android.modules.utils.TypedXmlPullParser;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 import org.xmlpull.v1.XmlPullParser;
 
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.io.StringReader;
 import java.io.StringWriter;
 import java.util.Arrays;
+import java.util.Base64;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
@@ -53,6 +63,9 @@
 @RunWith(JUnit4.class)
 public class VibrationEffectXmlSerializationTest {
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Test
     public void isSupportedMimeType_onlySupportsVibrationXmlMimeType() {
         // Single MIME type supported
@@ -422,6 +435,97 @@
         }
     }
 
+    @Test
+    @EnableFlags(Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    public void testVendorEffect_featureFlagEnabled_allSucceed() throws Exception {
+        PersistableBundle vendorData = new PersistableBundle();
+        vendorData.putInt("id", 1);
+        vendorData.putDouble("scale", 0.5);
+        vendorData.putBoolean("loop", false);
+        vendorData.putLongArray("amplitudes", new long[] { 0, 255, 128 });
+        vendorData.putString("label", "vibration");
+
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        vendorData.writeToStream(outputStream);
+        String vendorDataStr = Base64.getEncoder().encodeToString(outputStream.toByteArray());
+
+        VibrationEffect effect = VibrationEffect.createVendorEffect(vendorData);
+        String xml = "<vibration-effect><vendor-effect>  " // test trailing whitespace is ignored
+                + vendorDataStr
+                + " \n </vendor-effect></vibration-effect>";
+
+        assertPublicApisParserSucceeds(xml, effect);
+        assertPublicApisSerializerSucceeds(effect, vendorDataStr);
+        assertPublicApisRoundTrip(effect);
+
+        assertHiddenApisParserSucceeds(xml, effect);
+        assertHiddenApisSerializerSucceeds(effect, vendorDataStr);
+        assertHiddenApisRoundTrip(effect);
+
+        // Check PersistableBundle from round-trip
+        PersistableBundle parsedVendorData =
+                ((VibrationEffect.VendorEffect) parseVibrationEffect(serialize(effect),
+                        /* flags= */ 0)).getVendorData();
+        assertThat(parsedVendorData.size()).isEqualTo(vendorData.size());
+        assertThat(parsedVendorData.getInt("id")).isEqualTo(1);
+        assertThat(parsedVendorData.getDouble("scale")).isEqualTo(0.5);
+        assertThat(parsedVendorData.getBoolean("loop")).isFalse();
+        assertArrayEquals(parsedVendorData.getLongArray("amplitudes"), new long[] { 0, 255, 128 });
+        assertThat(parsedVendorData.getString("label")).isEqualTo("vibration");
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    public void testInvalidVendorEffect_featureFlagEnabled_allFail() throws IOException {
+        String emptyTag = "<vibration-effect><vendor-effect/></vibration-effect>";
+        assertPublicApisParserFails(emptyTag);
+        assertHiddenApisParserFails(emptyTag);
+
+        String emptyStringTag =
+                "<vibration-effect><vendor-effect> \n </vendor-effect></vibration-effect>";
+        assertPublicApisParserFails(emptyStringTag);
+        assertHiddenApisParserFails(emptyStringTag);
+
+        String invalidString =
+                "<vibration-effect><vendor-effect>invalid</vendor-effect></vibration-effect>";
+        assertPublicApisParserFails(invalidString);
+        assertHiddenApisParserFails(invalidString);
+
+        String validBase64String =
+                "<vibration-effect><vendor-effect>c29tZXNh</vendor-effect></vibration-effect>";
+        assertPublicApisParserFails(validBase64String);
+        assertHiddenApisParserFails(validBase64String);
+
+        PersistableBundle emptyData = new PersistableBundle();
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        emptyData.writeToStream(outputStream);
+        String emptyBundleString = "<vibration-effect><vendor-effect>"
+                + Base64.getEncoder().encodeToString(outputStream.toByteArray())
+                + "</vendor-effect></vibration-effect>";
+        assertPublicApisParserFails(emptyBundleString);
+        assertHiddenApisParserFails(emptyBundleString);
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    public void testVendorEffect_featureFlagDisabled_allFail() throws Exception {
+        PersistableBundle vendorData = new PersistableBundle();
+        vendorData.putInt("id", 1);
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        vendorData.writeToStream(outputStream);
+        String vendorDataStr = Base64.getEncoder().encodeToString(outputStream.toByteArray());
+        String xml = "<vibration-effect><vendor-effect>"
+                + vendorDataStr
+                + "</vendor-effect></vibration-effect>";
+        VibrationEffect vendorEffect = VibrationEffect.createVendorEffect(vendorData);
+
+        assertPublicApisParserFails(xml);
+        assertPublicApisSerializerFails(vendorEffect);
+
+        assertHiddenApisParserFails(xml);
+        assertHiddenApisSerializerFails(vendorEffect);
+    }
+
     private void assertPublicApisParserFails(String xml) {
         assertThrows("Expected parseVibrationEffect to fail for " + xml,
                 VibrationXmlParser.ParseFailedException.class,
@@ -493,6 +597,12 @@
                 () -> serialize(effect));
     }
 
+    private void assertHiddenApisSerializerFails(VibrationEffect effect) {
+        assertThrows("Expected serialization to fail for " + effect,
+                VibrationXmlSerializer.SerializationFailedException.class,
+                () -> serialize(effect, VibrationXmlSerializer.FLAG_ALLOW_HIDDEN_APIS));
+    }
+
     private void assertPublicApisSerializerSucceeds(VibrationEffect effect,
             String... expectedSegments) throws Exception {
         assertSerializationContainsSegments(serialize(effect), expectedSegments);
diff --git a/core/xsd/vibrator/vibration/schema/current.txt b/core/xsd/vibrator/vibration/schema/current.txt
index f0e13c4..280b405 100644
--- a/core/xsd/vibrator/vibration/schema/current.txt
+++ b/core/xsd/vibrator/vibration/schema/current.txt
@@ -41,9 +41,11 @@
     ctor public VibrationEffect();
     method public com.android.internal.vibrator.persistence.PredefinedEffect getPredefinedEffect_optional();
     method public com.android.internal.vibrator.persistence.PrimitiveEffect getPrimitiveEffect_optional();
+    method public byte[] getVendorEffect_optional();
     method public com.android.internal.vibrator.persistence.WaveformEffect getWaveformEffect_optional();
     method public void setPredefinedEffect_optional(com.android.internal.vibrator.persistence.PredefinedEffect);
     method public void setPrimitiveEffect_optional(com.android.internal.vibrator.persistence.PrimitiveEffect);
+    method public void setVendorEffect_optional(byte[]);
     method public void setWaveformEffect_optional(com.android.internal.vibrator.persistence.WaveformEffect);
   }
 
diff --git a/core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd b/core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd
index fcd250b..21a6fac 100644
--- a/core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd
+++ b/core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd
@@ -46,6 +46,9 @@
             <!-- Predefined vibration effect -->
             <xs:element name="predefined-effect" type="PredefinedEffect"/>
 
+            <!-- Vendor vibration effect -->
+            <xs:element name="vendor-effect" type="VendorEffect"/>
+
             <!-- Primitive composition effect -->
             <xs:sequence>
                 <xs:element name="primitive-effect" type="PrimitiveEffect"/>
@@ -136,6 +139,10 @@
         </xs:restriction>
     </xs:simpleType>
 
+    <xs:simpleType name="VendorEffect">
+        <xs:restriction base="xs:base64Binary"/>
+    </xs:simpleType>
+
     <xs:complexType name="PrimitiveEffect">
         <xs:attribute name="name" type="PrimitiveEffectName" use="required"/>
         <xs:attribute name="scale" type="PrimitiveScale"/>
diff --git a/core/xsd/vibrator/vibration/vibration.xsd b/core/xsd/vibrator/vibration/vibration.xsd
index b9de691..d35d777 100644
--- a/core/xsd/vibrator/vibration/vibration.xsd
+++ b/core/xsd/vibrator/vibration/vibration.xsd
@@ -44,6 +44,9 @@
             <!-- Predefined vibration effect -->
             <xs:element name="predefined-effect" type="PredefinedEffect"/>
 
+            <!-- Vendor vibration effect -->
+            <xs:element name="vendor-effect" type="VendorEffect"/>
+
             <!-- Primitive composition effect -->
             <xs:sequence>
                 <xs:element name="primitive-effect" type="PrimitiveEffect"/>
@@ -113,6 +116,10 @@
         </xs:restriction>
     </xs:simpleType>
 
+    <xs:simpleType name="VendorEffect">
+        <xs:restriction base="xs:base64Binary"/>
+    </xs:simpleType>
+
     <xs:complexType name="PrimitiveEffect">
         <xs:attribute name="name" type="PrimitiveEffectName" use="required"/>
         <xs:attribute name="scale" type="PrimitiveScale"/>
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 7be14724..26d180c 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -695,12 +695,8 @@
                         break;
                     case TYPE_ACTIVITY_REPARENTED_TO_TASK:
                         final IBinder candidateAssociatedActToken, lastOverlayToken;
-                        if (Flags.fixPipRestoreToOverlay()) {
-                            candidateAssociatedActToken = change.getOtherActivityToken();
-                            lastOverlayToken = change.getTaskFragmentToken();
-                        } else {
-                            candidateAssociatedActToken = lastOverlayToken = null;
-                        }
+                        candidateAssociatedActToken = change.getOtherActivityToken();
+                        lastOverlayToken = change.getTaskFragmentToken();
                         onActivityReparentedToTask(
                                 wct,
                                 taskId,
@@ -1023,10 +1019,6 @@
     @Nullable
     OverlayContainerRestoreParams getOverlayContainerRestoreParams(
             @Nullable IBinder associatedActivityToken, @Nullable IBinder overlayToken) {
-        if (!Flags.fixPipRestoreToOverlay()) {
-            return null;
-        }
-
         if (associatedActivityToken == null || overlayToken == null) {
             return null;
         }
@@ -2725,15 +2717,19 @@
     TaskFragmentContainer createOrUpdateOverlayTaskFragmentIfNeeded(
             @NonNull WindowContainerTransaction wct, @NonNull Bundle options,
             @NonNull Intent intent, @NonNull Activity launchActivity) {
+        final String overlayTag = Objects.requireNonNull(options.getString(KEY_OVERLAY_TAG));
         if (isActivityFromSplit(launchActivity)) {
             // We restrict to launch the overlay from split. Fallback to treat it as normal
             // launch.
+            Log.w(TAG, "It's not allowed to launch overlay container with tag=" + overlayTag
+                    + " from activity in Activity Embedding split."
+                    + " Launching activity=" + launchActivity
+                    + " Fallback to launch the activity as normal launch.");
             return null;
         }
 
         final List<TaskFragmentContainer> overlayContainers =
                 getAllNonFinishingOverlayContainers();
-        final String overlayTag = Objects.requireNonNull(options.getString(KEY_OVERLAY_TAG));
         final boolean associateLaunchingActivity = options
                 .getBoolean(KEY_OVERLAY_ASSOCIATE_WITH_LAUNCHING_ACTIVITY, 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 f1e7ef5..99716e7 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -405,8 +405,7 @@
 
         // Sets the dim area when the two TaskFragments are adjacent.
         final boolean dimOnTask = !isStacked
-                && splitAttributes.getWindowAttributes().getDimAreaBehavior() == DIM_AREA_ON_TASK
-                && Flags.fullscreenDimFlag();
+                && splitAttributes.getWindowAttributes().getDimAreaBehavior() == DIM_AREA_ON_TASK;
         setTaskFragmentDimOnTask(wct, primaryContainer.getTaskFragmentToken(), dimOnTask);
         setTaskFragmentDimOnTask(wct, secondaryContainer.getTaskFragmentToken(), dimOnTask);
 
@@ -646,7 +645,6 @@
                 container);
         final boolean isFillParent = relativeBounds.isEmpty();
         final boolean dimOnTask = !isFillParent
-                && Flags.fullscreenDimFlag()
                 && attributes.getWindowAttributes().getDimAreaBehavior() == DIM_AREA_ON_TASK;
         final IBinder fragmentToken = container.getTaskFragmentToken();
 
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 d0e2c99..ee3e6f3 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -36,7 +36,6 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.window.flags.Flags;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -257,7 +256,7 @@
         mPendingAppearedIntent = pendingAppearedIntent;
 
         // Save the information necessary for restoring the overlay when needed.
-        if (Flags.fixPipRestoreToOverlay() && overlayTag != null && pendingAppearedIntent != null
+        if (overlayTag != null && pendingAppearedIntent != null
                 && associatedActivity != null && !associatedActivity.isFinishing()) {
             final IBinder associatedActivityToken = associatedActivity.getActivityToken();
             final OverlayContainerRestoreParams params = new OverlayContainerRestoreParams(mToken,
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 475475b..90eeb58 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
@@ -874,8 +874,6 @@
 
     @Test
     public void testOnActivityReparentedToTask_overlayRestoration() {
-        mSetFlagRule.enableFlags(Flags.FLAG_FIX_PIP_RESTORE_TO_OVERLAY);
-
         // Prepares and mock the data necessary for the test.
         final IBinder activityToken = mActivity.getActivityToken();
         final Intent intent = new Intent();
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 5135e9e..1c3d9c3 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -166,6 +166,16 @@
     },
 }
 
+java_library {
+    name: "WindowManager-Shell-lite-proto",
+
+    srcs: ["src/com/android/wm/shell/desktopmode/education/data/proto/**/*.proto"],
+
+    proto: {
+        type: "lite",
+    },
+}
+
 filegroup {
     name: "wm_shell-shared-aidls",
 
@@ -215,6 +225,7 @@
         "androidx.core_core-animation",
         "androidx.core_core-ktx",
         "androidx.arch.core_core-runtime",
+        "androidx.datastore_datastore",
         "androidx.compose.material3_material3",
         "androidx-constraintlayout_constraintlayout",
         "androidx.dynamicanimation_dynamicanimation",
@@ -225,6 +236,7 @@
         "//frameworks/libs/systemui:iconloader_base",
         "com_android_wm_shell_flags_lib",
         "WindowManager-Shell-proto",
+        "WindowManager-Shell-lite-proto",
         "WindowManager-Shell-shared",
         "perfetto_trace_java_protos",
         "dagger2",
diff --git a/libs/WindowManager/Shell/OWNERS b/libs/WindowManager/Shell/OWNERS
index cb422ea..2e19d52 100644
--- a/libs/WindowManager/Shell/OWNERS
+++ b/libs/WindowManager/Shell/OWNERS
@@ -1,5 +1,5 @@
 xutan@google.com
 
 # Give submodule owners in shell resource approval
-per-file res*/*/*.xml = atsjenk@google.com, hwwang@google.com, jorgegil@google.com, lbill@google.com, madym@google.com, vaniadesmonda@google.com, pbdr@google.com, tkachenkoi@google.com, mpodolian@google.com, liranb@google.com
+per-file res*/*/*.xml = atsjenk@google.com, hwwang@google.com, jorgegil@google.com, lbill@google.com, madym@google.com, vaniadesmonda@google.com, pbdr@google.com, tkachenkoi@google.com, mpodolian@google.com, liranb@google.com, pragyabajoria@google.com, uysalorhan@google.com, gsennton@google.com
 per-file res*/*/tv_*.xml = bronger@google.com
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 5e67333..84f7bb2 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,6 +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 java.util.concurrent.Semaphore
 import java.util.concurrent.TimeUnit
 import java.util.function.Consumer
@@ -458,5 +459,7 @@
         override fun isShowingAsBubbleBar(): Boolean = false
 
         override fun hideCurrentInputMethod() {}
+
+        override fun updateBubbleBarLocation(location: BubbleBarLocation) {}
     }
 }
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_menu_item.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_item.xml
index ddcd5c6..e3217811 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_bar_menu_item.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_item.xml
@@ -16,6 +16,7 @@
   -->
 <com.android.wm.shell.bubbles.bar.BubbleBarMenuItemView
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:orientation="horizontal"
     android:layout_width="match_parent"
     android:layout_height="@dimen/bubble_bar_manage_menu_item_height"
@@ -35,7 +36,7 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_marginStart="16dp"
-        android:textColor="?android:attr/textColorPrimary"
+        android:textColor="?androidprv:attr/materialColorOnSurface"
         android:textAppearance="@*android:style/TextAppearance.DeviceDefault" />
 
 </com.android.wm.shell.bubbles.bar.BubbleBarMenuItemView>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml
index 1cbd0e6..f1ecde4 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml
@@ -17,6 +17,7 @@
 <com.android.wm.shell.bubbles.bar.BubbleBarMenuView
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:layout_gravity="center_horizontal"
@@ -51,7 +52,7 @@
             android:layout_height="wrap_content"
             android:layout_marginStart="8dp"
             android:layout_weight="1"
-            android:textColor="?android:attr/textColorPrimary"
+            android:textColor="?androidprv:attr/materialColorOnSurface"
             android:textAppearance="@*android:style/TextAppearance.DeviceDefault" />
 
         <ImageView
@@ -61,7 +62,7 @@
             android:layout_marginStart="8dp"
             android:contentDescription="@null"
             android:src="@drawable/ic_expand_less"
-            app:tint="?android:attr/textColorPrimary" />
+            app:tint="?androidprv:attr/materialColorOnSurface" />
 
     </LinearLayout>
 
diff --git a/libs/WindowManager/Shell/res/values/ids.xml b/libs/WindowManager/Shell/res/values/ids.xml
index bc59a23..debcba0 100644
--- a/libs/WindowManager/Shell/res/values/ids.xml
+++ b/libs/WindowManager/Shell/res/values/ids.xml
@@ -42,6 +42,8 @@
     <item type="id" name="action_move_top_right"/>
     <item type="id" name="action_move_bottom_left"/>
     <item type="id" name="action_move_bottom_right"/>
+    <item type="id" name="action_move_bubble_bar_left"/>
+    <item type="id" name="action_move_bubble_bar_right"/>
 
     <item type="id" name="dismiss_view"/>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 0a8166f..6a62d7a3 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -139,6 +139,14 @@
     <string name="bubble_accessibility_action_move_bottom_left">Move bottom left</string>
     <!-- Action in accessibility menu to move the stack of bubbles to the bottom right of the screen. [CHAR LIMIT=30]-->
     <string name="bubble_accessibility_action_move_bottom_right">Move bottom right</string>
+    <!-- Click action label for bubbles to expand menu. [CHAR LIMIT=30]-->
+    <string name="bubble_accessibility_action_expand_menu">expand menu</string>
+    <!-- Click action label for bubbles to collapse menu. [CHAR LIMIT=30]-->
+    <string name="bubble_accessibility_action_collapse_menu">collapse menu</string>
+    <!-- Action in accessibility menu to move the bubble bar to the left side of the screen. [CHAR_LIMIT=30] -->
+    <string name="bubble_accessibility_action_move_bar_left">Move left</string>
+    <!-- Action in accessibility menu to move the bubble bar to the right side of the screen. [CHAR_LIMIT=30] -->
+    <string name="bubble_accessibility_action_move_bar_right">Move right</string>
     <!-- Accessibility announcement when the stack of bubbles expands. [CHAR LIMIT=NONE]-->
     <string name="bubble_accessibility_announce_expand">expand <xliff:g id="bubble_title" example="Messages">%1$s</xliff:g></string>
     <!-- Accessibility announcement when the stack of bubbles collapses. [CHAR LIMIT=NONE]-->
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
index dc022b4..9027bf3 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
@@ -25,6 +25,7 @@
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
+import static android.view.WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -59,7 +60,8 @@
     public static boolean isOpeningType(@WindowManager.TransitionType int type) {
         return type == TRANSIT_OPEN
                 || type == TRANSIT_TO_FRONT
-                || type == TRANSIT_KEYGUARD_GOING_AWAY;
+                || type == TRANSIT_KEYGUARD_GOING_AWAY
+                || type == TRANSIT_PREPARE_BACK_NAVIGATION;
     }
 
     /** @return true if the transition was triggered by closing something vs opening something */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
index 8d30db6..5355138 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
@@ -18,6 +18,7 @@
 
 import static android.graphics.Matrix.MTRANS_X;
 import static android.graphics.Matrix.MTRANS_Y;
+import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
 
 import android.annotation.CallSuper;
 import android.graphics.Point;
@@ -146,6 +147,14 @@
     /** To be overridden by subclasses to adjust the animation surface change. */
     void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
         // Update the surface position and alpha.
+        if (com.android.graphics.libgui.flags.Flags.edgeExtensionShader()
+                && mAnimation.getExtensionEdges() != 0x0
+                && !(mChange.hasFlags(FLAG_TRANSLUCENT)
+                        && mChange.getActivityComponent() != null)) {
+            // Extend non-translucent activities
+            t.setEdgeExtensionEffect(mLeash, mAnimation.getExtensionEdges());
+        }
+
         mTransformation.getMatrix().postTranslate(mContentRelOffset.x, mContentRelOffset.y);
         t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
         t.setAlpha(mLeash, mTransformation.getAlpha());
@@ -165,7 +174,7 @@
         if (!cropRect.intersect(mWholeAnimationBounds)) {
             // Hide the surface when it is outside of the animation area.
             t.setAlpha(mLeash, 0);
-        } else if (mAnimation.hasExtension()) {
+        } else if (mAnimation.getExtensionEdges() != 0) {
             // Allow the surface to be shown in its original bounds in case we want to use edge
             // extensions.
             cropRect.union(mContentBounds);
@@ -180,6 +189,10 @@
     @CallSuper
     void onAnimationEnd(@NonNull SurfaceControl.Transaction t) {
         onAnimationUpdate(t, mAnimation.getDuration());
+        if (com.android.graphics.libgui.flags.Flags.edgeExtensionShader()
+                && mAnimation.getExtensionEdges() != 0x0) {
+            t.setEdgeExtensionEffect(mLeash, /* edge */ 0);
+        }
     }
 
     final long getDurationHint() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index 5696a54..d2cef4b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -144,8 +144,10 @@
             // ending states.
             prepareForJumpCut(info, startTransaction);
         } else {
-            addEdgeExtensionIfNeeded(startTransaction, finishTransaction,
-                    postStartTransactionCallbacks, adapters);
+            if (!com.android.graphics.libgui.flags.Flags.edgeExtensionShader()) {
+                addEdgeExtensionIfNeeded(startTransaction, finishTransaction,
+                        postStartTransactionCallbacks, adapters);
+            }
             addBackgroundColorIfNeeded(info, startTransaction, finishTransaction, adapters);
             for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
                 duration = Math.max(duration, adapter.getDurationHint());
@@ -341,7 +343,7 @@
             @NonNull List<ActivityEmbeddingAnimationAdapter> adapters) {
         for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
             final Animation animation = adapter.mAnimation;
-            if (!animation.hasExtension()) {
+            if (animation.getExtensionEdges() == 0) {
                 continue;
             }
             if (adapter.mChange.hasFlags(FLAG_TRANSLUCENT)
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 f14f419..1242287 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
@@ -16,9 +16,14 @@
 
 package com.android.wm.shell.back;
 
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.view.RemoteAnimationTarget.MODE_CLOSING;
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
+import static android.view.WindowManager.TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION;
 import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
+import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
+import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
+import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
 
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_HOME;
 import static com.android.window.flags.Flags.migratePredictiveBackTransition;
@@ -31,6 +36,8 @@
 import android.annotation.SuppressLint;
 import android.app.ActivityTaskManager;
 import android.app.IActivityTaskManager;
+import android.app.TaskInfo;
+import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.Configuration;
@@ -88,6 +95,7 @@
 
 import java.io.PrintWriter;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Predicate;
 
 /**
  * Controls the window animation run when a user initiates a back gesture.
@@ -837,8 +845,9 @@
         mShellExecutor.executeDelayed(mAnimationTimeoutRunnable, MAX_ANIMATION_DURATION);
 
         // The next callback should be {@link #onBackAnimationFinished}.
+        final boolean migrateBackToTransition = migratePredictiveBackTransition();
         if (mCurrentTracker.getTriggerBack()) {
-            if (migratePredictiveBackTransition()) {
+            if (migrateBackToTransition) {
                 // notify core gesture is commit
                 if (shouldTriggerCloseTransition()) {
                     mBackTransitionHandler.mCloseTransitionRequested = true;
@@ -856,6 +865,10 @@
             // start post animation
             dispatchOnBackInvoked(mActiveCallback);
         } else {
+            if (migrateBackToTransition
+                    && mBackTransitionHandler.mPrepareOpenTransition != null) {
+                mBackTransitionHandler.createClosePrepareTransition();
+            }
             tryDispatchOnBackCancelled(mActiveCallback);
         }
     }
@@ -960,6 +973,7 @@
         mShellBackAnimationRegistry.resetDefaultCrossActivity();
         cancelLatencyTracking();
         mReceivedNullNavigationInfo = false;
+        mBackTransitionHandler.mLastTrigger = triggerBack;
         if (mBackNavigationInfo != null) {
             mPreviousNavigationType = mBackNavigationInfo.getType();
             mBackNavigationInfo.onBackNavigationFinished(triggerBack);
@@ -1128,12 +1142,18 @@
 
         Runnable mOnAnimationFinishCallback;
         boolean mCloseTransitionRequested;
-        boolean mOpeningRunning;
         SurfaceControl.Transaction mFinishOpenTransaction;
         Transitions.TransitionFinishCallback mFinishOpenTransitionCallback;
         QueuedTransition mQueuedTransition = null;
+        boolean mLastTrigger;
+        // The Transition to make behindActivity become visible
+        IBinder mPrepareOpenTransition;
+        // The Transition to make behindActivity become invisible, if prepare open exist and
+        // animation is canceled, start a close prepare transition to finish the whole transition.
+        IBinder mClosePrepareTransition;
+        TransitionInfo mOpenTransitionInfo;
         void onAnimationFinished() {
-            if (!mCloseTransitionRequested) {
+            if (!mCloseTransitionRequested && mClosePrepareTransition == null) {
                 applyFinishOpenTransition();
             }
             if (mOnAnimationFinishCallback != null) {
@@ -1158,7 +1178,8 @@
                 mFinishOpenTransitionCallback.onTransitionFinished(null);
                 mFinishOpenTransitionCallback = null;
             }
-            mOpeningRunning = false;
+            mOpenTransitionInfo = null;
+            mPrepareOpenTransition = null;
         }
 
         private void applyAndFinish(@NonNull SurfaceControl.Transaction st,
@@ -1178,21 +1199,42 @@
                 @NonNull Transitions.TransitionFinishCallback finishCallback) {
             // Both mShellExecutor and Transitions#mMainExecutor are ShellMainThread, so we don't
             // need to post to ShellExecutor when called.
-            if (info.getType() != WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION
-                    && !isGestureBackTransition(info)) {
+            if (info.getType() == WindowManager.TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION) {
+                // only consume it if this transition hasn't being processed.
+                if (mClosePrepareTransition != null) {
+                    mClosePrepareTransition = null;
+                    applyAndFinish(st, ft, finishCallback);
+                    return true;
+                }
                 return false;
             }
+
+            if (info.getType() != WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION
+                    && isNotGestureBackTransition(info)) {
+                return false;
+            }
+
+            if (shouldCancelAnimation(info)) {
+                return false;
+            }
+
             if (mApps == null || mApps.length == 0) {
                 if (mBackNavigationInfo != null && mShellBackAnimationRegistry
                         .isWaitingAnimation(mBackNavigationInfo.getType())) {
                     // Waiting for animation? Queue update to wait for animation start.
                     consumeQueuedTransitionIfNeeded();
                     mQueuedTransition = new QueuedTransition(info, st, ft, finishCallback);
-                } else {
+                    return true;
+                } else if (mLastTrigger) {
                     // animation was done, consume directly
                     applyAndFinish(st, ft, finishCallback);
+                    return true;
+                } else {
+                    // animation was cancelled but transition haven't happen, we must handle it
+                    if (mClosePrepareTransition == null && mCurrentTracker.isFinished()) {
+                        createClosePrepareTransition();
+                    }
                 }
-                return true;
             }
 
             if (handlePrepareTransition(info, st, ft, finishCallback)) {
@@ -1201,12 +1243,131 @@
             return handleCloseTransition(info, st, ft, finishCallback);
         }
 
+        void createClosePrepareTransition() {
+            final WindowContainerTransaction wct = new WindowContainerTransaction();
+            wct.restoreBackNavi();
+            mClosePrepareTransition = mTransitions.startTransition(
+                    TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION, wct, mBackTransitionHandler);
+        }
+        private void mergePendingTransitions(TransitionInfo info) {
+            if (mOpenTransitionInfo == null) {
+                return;
+            }
+            // Copy initial changes to final transition
+            final TransitionInfo init = mOpenTransitionInfo;
+            // find prepare open target
+            boolean openShowWallpaper = false;
+            ComponentName openComponent = null;
+            int tmpSize;
+            int openTaskId = INVALID_TASK_ID;
+            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);
+                    if (change.hasFlags(FLAG_SHOW_WALLPAPER)) {
+                        openShowWallpaper = true;
+                    }
+                    break;
+                }
+            }
+            if (openComponent == null && openTaskId == INVALID_TASK_ID) {
+                // shouldn't happen.
+                return;
+            }
+            // find first non-prepare open target
+            boolean isOpen = false;
+            tmpSize = info.getChanges().size();
+            for (int j = 0; j < tmpSize; ++j) {
+                final TransitionInfo.Change change = info.getChanges().get(j);
+                final ComponentName firstNonOpen = findComponentName(change);
+                final int firstTaskId = findTaskId(change);
+                if ((firstNonOpen != null && firstNonOpen != openComponent)
+                        || (firstTaskId != INVALID_TASK_ID && firstTaskId != openTaskId)) {
+                    // this is original close target, potential be close, but cannot determine from
+                    // it
+                    if (change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)) {
+                        isOpen = !TransitionUtil.isClosingMode(change.getMode());
+                    } else {
+                        isOpen = TransitionUtil.isOpeningMode(change.getMode());
+                        break;
+                    }
+                }
+            }
+
+            if (!isOpen) {
+                // Close transition, the transition info should be:
+                // init info(open A & wallpaper)
+                // current info(close B target)
+                // remove init info(open/change A target & wallpaper)
+                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)) {
+                        moveToTop = change.hasFlags(FLAG_MOVED_TO_TOP);
+                        info.getChanges().remove(j);
+                    } else if ((openShowWallpaper && change.hasFlags(FLAG_IS_WALLPAPER))
+                            || !change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)) {
+                        info.getChanges().remove(j);
+                    }
+                }
+                tmpSize = info.getChanges().size();
+                for (int i = 0; i < tmpSize; ++i) {
+                    final TransitionInfo.Change change = init.getChanges().get(i);
+                    if (moveToTop) {
+                        if (isSameChangeTarget(openComponent, openTaskId, change)) {
+                            change.setFlags(change.getFlags() | FLAG_MOVED_TO_TOP);
+                        }
+                    }
+                    info.getChanges().add(i, change);
+                }
+            } else {
+                // Open transition, the transition info should be:
+                // init info(open A & wallpaper)
+                // current info(open C target + close B target + close A & wallpaper)
+
+                // If close target isn't back navigated, filter out close A & wallpaper because the
+                // (open C + close B) pair didn't participant prepare close
+                boolean nonBackOpen = false;
+                boolean nonBackClose = false;
+                tmpSize = info.getChanges().size();
+                for (int j = 0; j < tmpSize; ++j) {
+                    final TransitionInfo.Change change = info.getChanges().get(j);
+                    if (!change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)
+                            && canBeTransitionTarget(change)) {
+                        final int mode = change.getMode();
+                        nonBackOpen |= TransitionUtil.isOpeningMode(mode);
+                        nonBackClose |= TransitionUtil.isClosingMode(mode);
+                    }
+                }
+                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)) {
+                            info.getChanges().remove(j);
+                        } else if ((openShowWallpaper && change.hasFlags(FLAG_IS_WALLPAPER))) {
+                            info.getChanges().remove(j);
+                        }
+                    }
+                }
+            }
+            ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation transition, merge pending "
+                    + "transitions result=%s", info);
+        }
+
         @Override
         public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
                 @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
                 @NonNull Transitions.TransitionFinishCallback finishCallback) {
-            if (!isGestureBackTransition(info)) {
-                if (mOpeningRunning) {
+            if (mClosePrepareTransition == transition) {
+                mClosePrepareTransition = null;
+            }
+            // try to handle unexpected transition
+            mergePendingTransitions(info);
+
+            if (isNotGestureBackTransition(info) || shouldCancelAnimation(info)
+                    || !mCloseTransitionRequested) {
+                if (mPrepareOpenTransition != null) {
                     applyFinishOpenTransition();
                 }
                 if (mQueuedTransition != null) {
@@ -1222,7 +1383,7 @@
                         // animation was done
                         applyFinishOpenTransition();
                         mCloseTransitionRequested = false;
-                    } // else, let queued transition to play
+                    } // let queued transition finish.
                 } else {
                     // we are animating, wait until animation finish
                     mOnAnimationFinishCallback = () -> {
@@ -1233,6 +1394,56 @@
             }
         }
 
+        // Cancel close animation if something happen unexpected, let another handler to handle
+        private boolean shouldCancelAnimation(@NonNull TransitionInfo info) {
+            final boolean noCloseAllowed = !mCloseTransitionRequested
+                    && info.getType() == WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION;
+            boolean unableToHandle = false;
+            boolean filterTargets = false;
+            for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+                final TransitionInfo.Change c = info.getChanges().get(i);
+                final boolean backGestureAnimated = c.hasFlags(FLAG_BACK_GESTURE_ANIMATED);
+                if (!backGestureAnimated && !c.hasFlags(FLAG_IS_WALLPAPER)) {
+                    // something we cannot handle?
+                    unableToHandle = true;
+                    filterTargets = true;
+                } else if (noCloseAllowed && backGestureAnimated
+                        && TransitionUtil.isClosingMode(c.getMode())) {
+                    // Prepare back navigation shouldn't contain close change, unless top app
+                    // request close.
+                    unableToHandle = true;
+                }
+            }
+            if (!unableToHandle) {
+                return false;
+            }
+            if (!filterTargets) {
+                return true;
+            }
+            if (TransitionUtil.isOpeningType(info.getType())
+                    || TransitionUtil.isClosingType(info.getType())) {
+                boolean removeWallpaper = false;
+                for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+                    final TransitionInfo.Change c = info.getChanges().get(i);
+                    // filter out opening target, keep original closing target in this transition
+                    if (c.hasFlags(FLAG_BACK_GESTURE_ANIMATED)
+                            && TransitionUtil.isOpeningMode(c.getMode())) {
+                        info.getChanges().remove(i);
+                        removeWallpaper |= c.hasFlags(FLAG_SHOW_WALLPAPER);
+                    }
+                }
+                if (removeWallpaper) {
+                    for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+                        final TransitionInfo.Change c = info.getChanges().get(i);
+                        if (c.hasFlags(FLAG_IS_WALLPAPER)) {
+                            info.getChanges().remove(i);
+                        }
+                    }
+                }
+            }
+            return true;
+        }
+
         /**
          * Check whether this transition is prepare for predictive back animation, which could
          * happen when core make an activity become visible.
@@ -1245,11 +1456,17 @@
             if (info.getType() != WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION) {
                 return false;
             }
-
+            // Must have open target, must not have close target.
+            if (hasAnimationInMode(info, TransitionUtil::isClosingMode)
+                    || !hasAnimationInMode(info, TransitionUtil::isOpeningMode)) {
+                return false;
+            }
             SurfaceControl openingLeash = null;
-            for (int i = mApps.length - 1; i >= 0; --i) {
-                if (mApps[i].mode == MODE_OPENING) {
-                    openingLeash = mApps[i].leash;
+            if (mApps != null) {
+                for (int i = mApps.length - 1; i >= 0; --i) {
+                    if (mApps[i].mode == MODE_OPENING) {
+                        openingLeash = mApps[i].leash;
+                    }
                 }
             }
             if (openingLeash != null) {
@@ -1259,27 +1476,17 @@
                         final Point offset = c.getEndRelOffset();
                         st.setPosition(c.getLeash(), offset.x, offset.y);
                         st.reparent(c.getLeash(), openingLeash);
+                        st.setAlpha(c.getLeash(), 1.0f);
                     }
                 }
             }
             st.apply();
             mFinishOpenTransaction = ft;
             mFinishOpenTransitionCallback = finishCallback;
-            mOpeningRunning = true;
+            mOpenTransitionInfo = info;
             return true;
         }
 
-        private boolean isGestureBackTransition(@NonNull TransitionInfo info) {
-            for (int i = info.getChanges().size() - 1; i >= 0; --i) {
-                final TransitionInfo.Change c = info.getChanges().get(i);
-                if (c.hasFlags(FLAG_BACK_GESTURE_ANIMATED)
-                        && (TransitionUtil.isOpeningMode(c.getMode())
-                        || TransitionUtil.isClosingMode(c.getMode()))) {
-                    return true;
-                }
-            }
-            return false;
-        }
         /**
          * Check whether this transition is triggered from back gesture commitment.
          * Reparent the transition targets to animation leashes, so the animation won't be broken.
@@ -1288,6 +1495,18 @@
                 @NonNull SurfaceControl.Transaction st,
                 @NonNull SurfaceControl.Transaction ft,
                 @NonNull Transitions.TransitionFinishCallback finishCallback) {
+            if (!mCloseTransitionRequested) {
+                return false;
+            }
+            // must have close target
+            if (!hasAnimationInMode(info, TransitionUtil::isClosingMode)) {
+                return false;
+            }
+            if (mApps == null) {
+                // animation is done
+                applyAndFinish(st, ft, finishCallback);
+                return true;
+            }
             SurfaceControl openingLeash = null;
             SurfaceControl closingLeash = null;
             for (int i = mApps.length - 1; i >= 0; --i) {
@@ -1305,6 +1524,7 @@
                         final Point offset = c.getEndRelOffset();
                         st.setPosition(c.getLeash(), offset.x, offset.y);
                         st.reparent(c.getLeash(), openingLeash);
+                        st.setAlpha(c.getLeash(), 1.0f);
                     } else if (TransitionUtil.isClosingMode(c.getMode())) {
                         st.reparent(c.getLeash(), closingLeash);
                     }
@@ -1325,7 +1545,12 @@
         public WindowContainerTransaction handleRequest(
                 @NonNull IBinder transition,
                 @NonNull TransitionRequestInfo request) {
-            if (request.getType() == WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION) {
+            final int type = request.getType();
+            if (type == WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION) {
+                mPrepareOpenTransition = transition;
+                return new WindowContainerTransaction();
+            }
+            if (type == WindowManager.TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION) {
                 return new WindowContainerTransaction();
             }
             if (TransitionUtil.isClosingType(request.getType()) && mCloseTransitionRequested) {
@@ -1369,4 +1594,51 @@
             }
         }
     }
+
+    private static boolean isNotGestureBackTransition(@NonNull TransitionInfo info) {
+        return !hasAnimationInMode(info, TransitionUtil::isOpenOrCloseMode);
+    }
+
+    private static boolean hasAnimationInMode(@NonNull TransitionInfo info,
+            Predicate<Integer> mode) {
+        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+            final TransitionInfo.Change c = info.getChanges().get(i);
+            if (c.hasFlags(FLAG_BACK_GESTURE_ANIMATED) && mode.test(c.getMode())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static ComponentName findComponentName(TransitionInfo.Change change) {
+        final ComponentName componentName = change.getActivityComponent();
+        if (componentName != null) {
+            return componentName;
+        }
+        final TaskInfo taskInfo = change.getTaskInfo();
+        if (taskInfo != null) {
+            return taskInfo.topActivity;
+        }
+        return null;
+    }
+
+    private static int findTaskId(TransitionInfo.Change change) {
+        final TaskInfo taskInfo = change.getTaskInfo();
+        if (taskInfo != null) {
+            return taskInfo.taskId;
+        }
+        return INVALID_TASK_ID;
+    }
+
+    private static boolean isSameChangeTarget(ComponentName topActivity, int taskId,
+            TransitionInfo.Change change) {
+        final ComponentName openChange = findComponentName(change);
+        final int firstTaskId = findTaskId(change);
+        return (openChange != null && openChange == topActivity)
+                || (firstTaskId != INVALID_TASK_ID && firstTaskId == taskId);
+    }
+
+    private static boolean canBeTransitionTarget(TransitionInfo.Change change) {
+        return findComponentName(change) != null || findTaskId(change) != INVALID_TASK_ID;
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
index f9a1d94..dc511be 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
@@ -357,7 +357,9 @@
 
     void showBadge() {
         Bitmap appBadgeBitmap = mBubble.getAppBadge();
-        if (appBadgeBitmap == null) {
+        final boolean isAppLaunchIntent = (mBubble instanceof Bubble)
+                && ((Bubble) mBubble).isAppLaunchIntent();
+        if (appBadgeBitmap == null || isAppLaunchIntent) {
             mAppIcon.setVisibility(GONE);
             return;
         }
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 7dbbb04..5cd2cb7 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
@@ -50,6 +50,7 @@
 import com.android.internal.logging.InstanceId;
 import com.android.internal.protolog.ProtoLog;
 import com.android.launcher3.icons.BubbleIconFactory;
+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;
@@ -246,7 +247,23 @@
         mAppIntent = intent;
         mDesiredHeight = Integer.MAX_VALUE;
         mPackageName = intent.getPackage();
+    }
 
+    private Bubble(ShortcutInfo info, Executor mainExecutor) {
+        mGroupKey = null;
+        mLocusId = null;
+        mFlags = 0;
+        mUser = info.getUserHandle();
+        mIcon = info.getIcon();
+        mIsAppBubble = false;
+        mKey = getBubbleKeyForShortcut(info);
+        mShowBubbleUpdateDot = false;
+        mMainExecutor = mainExecutor;
+        mTaskId = INVALID_TASK_ID;
+        mAppIntent = null;
+        mDesiredHeight = Integer.MAX_VALUE;
+        mPackageName = info.getPackage();
+        mShortcutInfo = info;
     }
 
     /** Creates an app bubble. */
@@ -263,6 +280,13 @@
                 mainExecutor);
     }
 
+    /** Creates a shortcut bubble. */
+    public static Bubble createShortcutBubble(
+            ShortcutInfo info,
+            Executor mainExecutor) {
+        return new Bubble(info, mainExecutor);
+    }
+
     /**
      * Returns the key for an app bubble from an app with package name, {@code packageName} on an
      * Android user, {@code user}.
@@ -273,6 +297,14 @@
         return KEY_APP_BUBBLE + ":" + user.getIdentifier()  + ":" + packageName;
     }
 
+    /**
+     * Returns the key for a shortcut bubble using {@code packageName}, {@code user}, and the
+     * {@code shortcutInfo} id.
+     */
+    public static String getBubbleKeyForShortcut(ShortcutInfo info) {
+        return info.getPackage() + ":" + info.getUserId() + ":" + info.getId();
+    }
+
     @VisibleForTesting(visibility = PRIVATE)
     public Bubble(@NonNull final BubbleEntry entry,
             final Bubbles.BubbleMetadataFlagListener listener,
@@ -888,6 +920,17 @@
         return mIntent;
     }
 
+    /**
+     * Whether this bubble represents the full app, i.e. the intent used is the launch
+     * intent for an app. In this case we don't show a badge on the icon.
+     */
+    public boolean isAppLaunchIntent() {
+        if (Flags.enableBubbleAnything() && mAppIntent != null) {
+            return mAppIntent.hasCategory("android.intent.category.LAUNCHER");
+        }
+        return false;
+    }
+
     @Nullable
     PendingIntent getDeleteIntent() {
         return mDeleteIntent;
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 949a723..29520ef 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
@@ -1335,6 +1335,40 @@
     }
 
     /**
+     * Expands and selects a bubble created or found via the provided shortcut info.
+     *
+     * @param info the shortcut info for the bubble.
+     */
+    public void expandStackAndSelectBubble(ShortcutInfo info) {
+        if (!Flags.enableBubbleAnything()) return;
+        Bubble b = mBubbleData.getOrCreateBubble(info); // Removes from overflow
+        ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - shortcut=%s", info);
+        if (b.isInflated()) {
+            mBubbleData.setSelectedBubbleAndExpandStack(b);
+        } else {
+            b.enable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE);
+            inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false);
+        }
+    }
+
+    /**
+     * Expands and selects a bubble created or found for this app.
+     *
+     * @param intent the intent for the bubble.
+     */
+    public void expandStackAndSelectBubble(Intent intent) {
+        if (!Flags.enableBubbleAnything()) return;
+        Bubble b = mBubbleData.getOrCreateBubble(intent); // Removes from overflow
+        ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - intent=%s", intent);
+        if (b.isInflated()) {
+            mBubbleData.setSelectedBubbleAndExpandStack(b);
+        } else {
+            b.enable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE);
+            inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false);
+        }
+    }
+
+    /**
      * Expands and selects a bubble based on the provided {@link BubbleEntry}. If no bubble
      * exists for this entry, and it is able to bubble, a new bubble will be created.
      *
@@ -2323,6 +2357,7 @@
      * @param entry   the entry to bubble.
      */
     static boolean canLaunchInTaskView(Context context, BubbleEntry entry) {
+        if (Flags.enableBubbleAnything()) return true;
         PendingIntent intent = entry.getBubbleMetadata() != null
                 ? entry.getBubbleMetadata().getIntent()
                 : null;
@@ -2439,6 +2474,16 @@
         }
 
         @Override
+        public void showShortcutBubble(ShortcutInfo info) {
+            mMainExecutor.execute(() -> mController.expandStackAndSelectBubble(info));
+        }
+
+        @Override
+        public void showAppBubble(Intent intent) {
+            mMainExecutor.execute(() -> mController.expandStackAndSelectBubble(intent));
+        }
+
+        @Override
         public void showBubble(String key, int topOnScreen) {
             mMainExecutor.execute(
                     () -> mController.expandStackAndSelectBubbleFromLauncher(key, topOnScreen));
@@ -2634,6 +2679,13 @@
         }
 
         @Override
+        public void expandStackAndSelectBubble(ShortcutInfo info) {
+            mMainExecutor.execute(() -> {
+                BubbleController.this.expandStackAndSelectBubble(info);
+            });
+        }
+
+        @Override
         public void expandStackAndSelectBubble(Bubble bubble) {
             mMainExecutor.execute(() -> {
                 BubbleController.this.expandStackAndSelectBubble(bubble);
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 434b512..3c6c6fa 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
@@ -23,8 +23,10 @@
 import android.annotation.NonNull;
 import android.app.PendingIntent;
 import android.content.Context;
+import android.content.Intent;
 import android.content.LocusId;
 import android.content.pm.ShortcutInfo;
+import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -174,6 +176,7 @@
         BubbleBarUpdate getInitialState() {
             BubbleBarUpdate bubbleBarUpdate = BubbleBarUpdate.createInitialState();
             bubbleBarUpdate.shouldShowEducation = shouldShowEducation;
+            bubbleBarUpdate.showOverflow = !overflowBubbles.isEmpty();
             for (int i = 0; i < bubbles.size(); i++) {
                 bubbleBarUpdate.currentBubbleList.add(bubbles.get(i).asBubbleBarBubble());
             }
@@ -420,23 +423,19 @@
         Bubble bubbleToReturn = getBubbleInStackWithKey(key);
 
         if (bubbleToReturn == null) {
-            bubbleToReturn = getOverflowBubbleWithKey(key);
-            if (bubbleToReturn != null) {
-                // Promoting from overflow
-                mOverflowBubbles.remove(bubbleToReturn);
-                if (mOverflowBubbles.isEmpty()) {
-                    mStateChange.showOverflowChanged = true;
+            // Check if it's in the overflow
+            bubbleToReturn = findAndRemoveBubbleFromOverflow(key);
+            if (bubbleToReturn == null) {
+                if (entry != null) {
+                    // Not in the overflow, have an entry, so it's a new bubble
+                    bubbleToReturn = new Bubble(entry,
+                            mBubbleMetadataFlagListener,
+                            mCancelledListener,
+                            mMainExecutor);
+                } else {
+                    // If there's no entry it must be a persisted bubble
+                    bubbleToReturn = persistedBubble;
                 }
-            } else if (mPendingBubbles.containsKey(key)) {
-                // Update while it was pending
-                bubbleToReturn = mPendingBubbles.get(key);
-            } else if (entry != null) {
-                // New bubble
-                bubbleToReturn = new Bubble(entry, mBubbleMetadataFlagListener, mCancelledListener,
-                        mMainExecutor);
-            } else {
-                // Persisted bubble being promoted
-                bubbleToReturn = persistedBubble;
             }
         }
 
@@ -447,6 +446,46 @@
         return bubbleToReturn;
     }
 
+    Bubble getOrCreateBubble(ShortcutInfo info) {
+        String bubbleKey = Bubble.getBubbleKeyForShortcut(info);
+        Bubble bubbleToReturn = findAndRemoveBubbleFromOverflow(bubbleKey);
+        if (bubbleToReturn == null) {
+            bubbleToReturn = Bubble.createShortcutBubble(info, mMainExecutor);
+        }
+        return bubbleToReturn;
+    }
+
+    Bubble getOrCreateBubble(Intent intent) {
+        UserHandle user = UserHandle.of(mCurrentUserId);
+        String bubbleKey = Bubble.getAppBubbleKeyForApp(intent.getPackage(),
+                user);
+        Bubble bubbleToReturn = findAndRemoveBubbleFromOverflow(bubbleKey);
+        if (bubbleToReturn == null) {
+            bubbleToReturn = Bubble.createAppBubble(intent, user, null, mMainExecutor);
+        }
+        return bubbleToReturn;
+    }
+
+    @Nullable
+    private Bubble findAndRemoveBubbleFromOverflow(String key) {
+        Bubble bubbleToReturn = getBubbleInStackWithKey(key);
+        if (bubbleToReturn != null) {
+            return bubbleToReturn;
+        }
+        bubbleToReturn = getOverflowBubbleWithKey(key);
+        if (bubbleToReturn != null) {
+            mOverflowBubbles.remove(bubbleToReturn);
+            // Promoting from overflow
+            mOverflowBubbles.remove(bubbleToReturn);
+            if (mOverflowBubbles.isEmpty()) {
+                mStateChange.showOverflowChanged = true;
+            }
+        } else if (mPendingBubbles.containsKey(key)) {
+            bubbleToReturn = mPendingBubbles.get(key);
+        }
+        return bubbleToReturn;
+    }
+
     /**
      * When this method is called it is expected that all info in the bubble has completed loading.
      * @see Bubble#inflate(BubbleViewInfoTask.Callback, Context, BubbleExpandedViewManager,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index fdb4523..a0c0a25 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -232,6 +232,9 @@
                     fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
                     fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
 
+                    final boolean isShortcutBubble = (mBubble.hasMetadataShortcutId()
+                            || (mBubble.getShortcutInfo() != null && Flags.enableBubbleAnything()));
+
                     if (mBubble.isAppBubble()) {
                         Context context =
                                 mContext.createContextAsUser(
@@ -246,7 +249,8 @@
                                 /* options= */ null);
                         mTaskView.startActivity(pi, /* fillInIntent= */ null, options,
                                 launchBounds);
-                    } else if (!mIsOverflow && mBubble.hasMetadataShortcutId()) {
+                    } else if (!mIsOverflow && isShortcutBubble) {
+                        ProtoLog.v(WM_SHELL_BUBBLES, "startingShortcutBubble=%s", getBubbleKey());
                         options.setApplyActivityFlagsForBubbles(true);
                         mTaskView.startShortcutActivity(mBubble.getShortcutInfo(),
                                 options, launchBounds);
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 3d9bf03..4e80e90 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,6 +16,8 @@
 
 package com.android.wm.shell.bubbles
 
+import com.android.wm.shell.common.bubbles.BubbleBarLocation
+
 /** Manager interface for bubble expanded views. */
 interface BubbleExpandedViewManager {
 
@@ -30,6 +32,7 @@
     fun isStackExpanded(): Boolean
     fun isShowingAsBubbleBar(): Boolean
     fun hideCurrentInputMethod()
+    fun updateBubbleBarLocation(location: BubbleBarLocation)
 
     companion object {
         /**
@@ -78,6 +81,10 @@
                 override fun hideCurrentInputMethod() {
                     controller.hideCurrentInputMethod()
                 }
+
+                override fun updateBubbleBarLocation(location: BubbleBarLocation) {
+                    controller.bubbleBarLocation = location
+                }
             }
         }
     }
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 8f8b77b..f002d89 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
@@ -1601,6 +1601,11 @@
                 getResources().getColor(android.R.color.system_neutral1_1000)));
         mManageMenuScrim.setBackgroundDrawable(new ColorDrawable(
                 getResources().getColor(android.R.color.system_neutral1_1000)));
+        if (mShowingManage) {
+            // the manage menu location depends on the manage button location which may need a
+            // layout pass, so post this to the looper
+            post(() -> showManageMenu(true));
+        }
     }
 
     /**
@@ -2004,6 +2009,7 @@
             // and then remove our views (removing the icon view triggers the removal of the
             // bubble window so do that at the end of the animation so we see the scrim animate).
             BadgedImageView iconView = bubble.getIconView();
+            final BubbleViewProvider expandedBubbleBeforeScrim = mExpandedBubble;
             showScrim(false, () -> {
                 mRemovingLastBubbleWhileExpanded = false;
                 bubble.cleanupExpandedView();
@@ -2012,7 +2018,17 @@
                 }
                 bubble.cleanupViews(); // cleans up the icon view
                 updateExpandedView(); // resets state for no expanded bubble
-                mExpandedBubble = null;
+                // Bubble keys may not have changed if we receive an update to the same bubble.
+                // Compare bubble object instances to see if the expanded bubble has changed.
+                if (expandedBubbleBeforeScrim == mExpandedBubble) {
+                    // Only clear expanded bubble if it has not changed since the scrim animation
+                    // started.
+                    // Scrim animation can take some time run and it is possible for a new bubble
+                    // to be added while the animation is running. This causes the expanded
+                    // bubble to change. Make sure we only clear the expanded bubble if it did
+                    // not change between when the scrim animation started and completed.
+                    mExpandedBubble = null;
+                }
             });
             logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED);
             return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
index 5e2141a..5f8f0fd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
@@ -36,6 +36,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.protolog.ProtoLog;
+import com.android.wm.shell.Flags;
 import com.android.wm.shell.taskview.TaskView;
 
 /**
@@ -110,6 +111,8 @@
                     fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
                     fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
 
+                    final boolean isShortcutBubble = (mBubble.hasMetadataShortcutId()
+                            || (mBubble.getShortcutInfo() != null && Flags.enableBubbleAnything()));
                     if (mBubble.isAppBubble()) {
                         Context context =
                                 mContext.createContextAsUser(
@@ -124,7 +127,7 @@
                                 /* options= */ null);
                         mTaskView.startActivity(pi, /* fillInIntent= */ null, options,
                                 launchBounds);
-                    } else if (mBubble.hasMetadataShortcutId()) {
+                    } else if (isShortcutBubble) {
                         options.setApplyActivityFlagsForBubbles(true);
                         mTaskView.startShortcutActivity(mBubble.getShortcutInfo(),
                                 options, launchBounds);
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 589dfd2..9a27fb6 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
@@ -23,6 +23,7 @@
 
 import android.app.NotificationChannel;
 import android.content.Intent;
+import android.content.pm.ShortcutInfo;
 import android.content.pm.UserInfo;
 import android.graphics.drawable.Icon;
 import android.hardware.HardwareBuffer;
@@ -118,6 +119,14 @@
 
     /**
      * Request the stack expand if needed, then select the specified Bubble as current.
+     * If no bubble exists for this entry, one is created.
+     *
+     * @param info the shortcut info to use to create the bubble.
+     */
+    void expandStackAndSelectBubble(ShortcutInfo info);
+
+    /**
+     * Request the stack expand if needed, then select the specified Bubble as current.
      *
      * @param bubble the bubble to be selected
      */
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 0907ddd..5c78974 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
@@ -18,6 +18,7 @@
 
 import android.content.Intent;
 import android.graphics.Rect;
+import android.content.pm.ShortcutInfo;
 import com.android.wm.shell.bubbles.IBubblesListener;
 import com.android.wm.shell.common.bubbles.BubbleBarLocation;
 
@@ -48,4 +49,8 @@
     oneway void updateBubbleBarTopOnScreen(in int topOnScreen) = 10;
 
     oneway void stopBubbleDrag(in BubbleBarLocation location, in int topOnScreen) = 11;
+
+    oneway void showShortcutBubble(in ShortcutInfo info) = 12;
+
+    oneway void showAppBubble(in Intent intent) = 13;
 }
\ No newline at end of file
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 24c568c..6d868d2 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
@@ -26,14 +26,18 @@
 import android.graphics.Insets;
 import android.graphics.Outline;
 import android.graphics.Rect;
+import android.os.Bundle;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewOutlineProvider;
+import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.FrameLayout;
 
+import androidx.annotation.NonNull;
+
 import com.android.wm.shell.R;
 import com.android.wm.shell.bubbles.Bubble;
 import com.android.wm.shell.bubbles.BubbleExpandedViewManager;
@@ -42,6 +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.taskview.TaskView;
 
 import java.util.function.Supplier;
@@ -81,6 +86,7 @@
     private static final String TAG = BubbleBarExpandedView.class.getSimpleName();
     private static final int INVALID_TASK_ID = -1;
 
+    private Bubble mBubble;
     private BubbleExpandedViewManager mManager;
     private BubblePositioner mPositioner;
     private boolean mIsOverflow;
@@ -188,12 +194,21 @@
 
             // Handle view needs to draw on top of task view.
             bringChildToFront(mHandleView);
+
+            mHandleView.setAccessibilityDelegate(new HandleViewAccessibilityDelegate());
         }
         mMenuViewController = new BubbleBarMenuViewController(mContext, this);
         mMenuViewController.setListener(new BubbleBarMenuViewController.Listener() {
             @Override
             public void onMenuVisibilityChanged(boolean visible) {
                 setObscured(visible);
+                if (visible) {
+                    mHandleView.setFocusable(false);
+                    mHandleView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
+                } else {
+                    mHandleView.setFocusable(true);
+                    mHandleView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_AUTO);
+                }
             }
 
             @Override
@@ -319,6 +334,7 @@
 
     /** Updates the bubble shown in the expanded view. */
     public void update(Bubble bubble) {
+        mBubble = bubble;
         mBubbleTaskViewHelper.update(bubble);
         mMenuViewController.updateMenu(bubble);
     }
@@ -457,4 +473,51 @@
             invalidateOutline();
         }
     }
+
+    private class HandleViewAccessibilityDelegate extends AccessibilityDelegate {
+        @Override
+        public void onInitializeAccessibilityNodeInfo(@NonNull View host,
+                @NonNull AccessibilityNodeInfo info) {
+            super.onInitializeAccessibilityNodeInfo(host, info);
+            info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
+                    AccessibilityNodeInfo.ACTION_CLICK, getResources().getString(
+                    R.string.bubble_accessibility_action_expand_menu)));
+            info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_COLLAPSE);
+            info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_DISMISS);
+            if (mPositioner.isBubbleBarOnLeft()) {
+                info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
+                        R.id.action_move_bubble_bar_right, getResources().getString(
+                        R.string.bubble_accessibility_action_move_bar_right)));
+            } else {
+                info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
+                        R.id.action_move_bubble_bar_left, getResources().getString(
+                        R.string.bubble_accessibility_action_move_bar_left)));
+            }
+        }
+
+        @Override
+        public boolean performAccessibilityAction(@NonNull View host, int action,
+                @Nullable Bundle args) {
+            if (super.performAccessibilityAction(host, action, args)) {
+                return true;
+            }
+            if (action == AccessibilityNodeInfo.ACTION_COLLAPSE) {
+                mManager.collapseStack();
+                return true;
+            }
+            if (action == AccessibilityNodeInfo.ACTION_DISMISS) {
+                mManager.dismissBubble(mBubble, Bubbles.DISMISS_USER_GESTURE);
+                return true;
+            }
+            if (action == R.id.action_move_bubble_bar_left) {
+                mManager.updateBubbleBarLocation(BubbleBarLocation.LEFT);
+                return true;
+            }
+            if (action == R.id.action_move_bubble_bar_right) {
+                mManager.updateBubbleBarLocation(BubbleBarLocation.RIGHT);
+                return true;
+            }
+            return false;
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java
index 00b9777..1c71ef4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java
@@ -64,7 +64,7 @@
     void update(Icon icon, String title, @ColorInt int tint) {
         if (tint == Color.TRANSPARENT) {
             final TypedArray typedArray = getContext().obtainStyledAttributes(
-                    new int[]{android.R.attr.textColorPrimary});
+                    new int[]{com.android.internal.R.attr.materialColorOnSurface});
             mTextView.setTextColor(typedArray.getColor(0, Color.BLACK));
         } else {
             icon.setTint(tint);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java
index d5f4924..0300869 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java
@@ -17,16 +17,21 @@
 
 import android.annotation.ColorInt;
 import android.content.Context;
+import android.content.res.ColorStateList;
 import android.content.res.TypedArray;
 import android.graphics.Color;
 import android.graphics.drawable.Icon;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
+import android.view.View;
 import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
+import androidx.core.widget.ImageViewCompat;
+
 import com.android.wm.shell.R;
 import com.android.wm.shell.bubbles.Bubble;
 
@@ -39,6 +44,7 @@
     private ViewGroup mBubbleSectionView;
     private ViewGroup mActionsSectionView;
     private ImageView mBubbleIconView;
+    private ImageView mBubbleDismissIconView;
     private TextView mBubbleTitleView;
 
     public BubbleBarMenuView(Context context) {
@@ -65,13 +71,28 @@
         mActionsSectionView = findViewById(R.id.bubble_bar_manage_menu_actions_section);
         mBubbleIconView = findViewById(R.id.bubble_bar_manage_menu_bubble_icon);
         mBubbleTitleView = findViewById(R.id.bubble_bar_manage_menu_bubble_title);
-        updateActionsBackgroundColor();
+        mBubbleDismissIconView = findViewById(R.id.bubble_bar_manage_menu_dismiss_icon);
+        updateThemeColors();
+
+        mBubbleSectionView.setAccessibilityDelegate(new AccessibilityDelegate() {
+            @Override
+            public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+                super.onInitializeAccessibilityNodeInfo(host, info);
+                info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
+                        AccessibilityNodeInfo.ACTION_CLICK, getResources().getString(
+                        R.string.bubble_accessibility_action_collapse_menu)));
+            }
+        });
     }
 
-    private void updateActionsBackgroundColor() {
+    private void updateThemeColors() {
         try (TypedArray ta = mContext.obtainStyledAttributes(new int[]{
-                com.android.internal.R.attr.materialColorSurfaceBright})) {
+                com.android.internal.R.attr.materialColorSurfaceBright,
+                com.android.internal.R.attr.materialColorOnSurface
+        })) {
             mActionsSectionView.getBackground().setTint(ta.getColor(0, Color.WHITE));
+            ImageViewCompat.setImageTintList(mBubbleDismissIconView,
+                    ColorStateList.valueOf(ta.getColor(1, Color.BLACK)));
         }
     }
 
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 02918db..0d72998 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
@@ -19,12 +19,13 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Color;
 import android.graphics.drawable.Icon;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 
-import androidx.core.content.ContextCompat;
 import androidx.dynamicanimation.animation.DynamicAnimation;
 import androidx.dynamicanimation.animation.SpringForce;
 
@@ -172,12 +173,17 @@
     private ArrayList<BubbleBarMenuView.MenuAction> createMenuActions(Bubble bubble) {
         ArrayList<BubbleBarMenuView.MenuAction> menuActions = new ArrayList<>();
         Resources resources = mContext.getResources();
-
+        int tintColor;
+        try (TypedArray ta = mContext.obtainStyledAttributes(new int[]{
+                com.android.internal.R.attr.materialColorOnSurface})) {
+            tintColor = ta.getColor(0, Color.TRANSPARENT);
+        }
         if (bubble.isConversation()) {
             // Don't bubble conversation action
             menuActions.add(new BubbleBarMenuView.MenuAction(
                     Icon.createWithResource(mContext, R.drawable.bubble_ic_stop_bubble),
                     resources.getString(R.string.bubbles_dont_bubble_conversation),
+                    tintColor,
                     view -> {
                         hideMenu(true /* animated */);
                         if (mListener != null) {
@@ -204,7 +210,7 @@
         menuActions.add(new BubbleBarMenuView.MenuAction(
                 Icon.createWithResource(resources, R.drawable.ic_remove_no_shadow),
                 resources.getString(R.string.bubble_dismiss_text),
-                ContextCompat.getColor(mContext, R.color.bubble_bar_expanded_view_menu_close),
+                tintColor,
                 view -> {
                     hideMenu(true /* animated */);
                     if (mListener != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
index 1fb0e17..c4c177c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
@@ -47,6 +47,8 @@
     private final SparseArray<PerDisplay> mInsetsPerDisplay = new SparseArray<>();
     private final SparseArray<CopyOnWriteArrayList<OnInsetsChangedListener>> mListeners =
             new SparseArray<>();
+    private final CopyOnWriteArrayList<OnInsetsChangedListener> mGlobalListeners =
+            new CopyOnWriteArrayList<>();
 
     public DisplayInsetsController(IWindowManager wmService,
             ShellInit shellInit,
@@ -81,6 +83,16 @@
     }
 
     /**
+     * Adds a callback to listen for insets changes for any display. Note that the
+     * listener will not be updated with the existing state of the insets on any display.
+     */
+    public void addGlobalInsetsChangedListener(OnInsetsChangedListener listener) {
+        if (!mGlobalListeners.contains(listener)) {
+            mGlobalListeners.add(listener);
+        }
+    }
+
+    /**
      * Removes a callback listening for insets changes from a particular display.
      */
     public void removeInsetsChangedListener(int displayId, OnInsetsChangedListener listener) {
@@ -91,6 +103,13 @@
         listeners.remove(listener);
     }
 
+    /**
+     * Removes a callback listening for insets changes from any display.
+     */
+    public void removeGlobalInsetsChangedListener(OnInsetsChangedListener listener) {
+        mGlobalListeners.remove(listener);
+    }
+
     @Override
     public void onDisplayAdded(int displayId) {
         PerDisplay pd = new PerDisplay(displayId);
@@ -138,12 +157,17 @@
 
         private void insetsChanged(InsetsState insetsState) {
             CopyOnWriteArrayList<OnInsetsChangedListener> listeners = mListeners.get(mDisplayId);
-            if (listeners == null) {
+            if (listeners == null && mGlobalListeners.isEmpty()) {
                 return;
             }
             mDisplayController.updateDisplayInsets(mDisplayId, insetsState);
-            for (OnInsetsChangedListener listener : listeners) {
-                listener.insetsChanged(insetsState);
+            for (OnInsetsChangedListener listener : mGlobalListeners) {
+                listener.insetsChanged(mDisplayId, insetsState);
+            }
+            if (listeners != null) {
+                for (OnInsetsChangedListener listener : listeners) {
+                    listener.insetsChanged(mDisplayId, insetsState);
+                }
             }
         }
 
@@ -285,6 +309,13 @@
         default void insetsChanged(InsetsState insetsState) {}
 
         /**
+         * Called when the window insets configuration has changed for the given display.
+         */
+        default void insetsChanged(int displayId, InsetsState insetsState) {
+            insetsChanged(insetsState);
+        }
+
+        /**
          * Called when this window retrieved control over a specified set of insets sources.
          */
         default void insetsControlChanged(InsetsState insetsState,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
index 19a109e..e2988bc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -23,7 +23,10 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 
+import static com.android.wm.shell.common.split.SplitLayout.BEHIND_APP_VEIL_LAYER;
+import static com.android.wm.shell.common.split.SplitLayout.FRONT_APP_VEIL_LAYER;
 import static com.android.wm.shell.common.split.SplitScreenConstants.FADE_DURATION;
+import static com.android.wm.shell.common.split.SplitScreenConstants.VEIL_DELAY_DURATION;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -74,7 +77,7 @@
     private final SurfaceSession mSurfaceSession;
 
     private Drawable mIcon;
-    private ImageView mResizingIconView;
+    private ImageView mVeilIconView;
     private SurfaceControlViewHost mViewHost;
     private SurfaceControl mHostLeash;
     private SurfaceControl mIconLeash;
@@ -83,13 +86,14 @@
     private SurfaceControl mScreenshot;
 
     private boolean mShown;
-    private boolean mIsResizing;
+    /** True if the task is going through some kind of transition (moving or changing size). */
+    private boolean mIsCurrentlyChanging;
     /** The original bounds of the main task, captured at the beginning of a resize transition. */
     private final Rect mOldMainBounds = new Rect();
     /** The original bounds of the side task, captured at the beginning of a resize transition. */
     private final Rect mOldSideBounds = new Rect();
     /** The current bounds of the main task, mid-resize. */
-    private final Rect mResizingBounds = new Rect();
+    private final Rect mInstantaneousBounds = new Rect();
     private final Rect mTempRect = new Rect();
     private ValueAnimator mFadeAnimator;
     private ValueAnimator mScreenshotAnimator;
@@ -134,7 +138,7 @@
         mIconSize = context.getResources().getDimensionPixelSize(R.dimen.split_icon_size);
         final FrameLayout rootLayout = (FrameLayout) LayoutInflater.from(context)
                 .inflate(R.layout.split_decor, null);
-        mResizingIconView = rootLayout.findViewById(R.id.split_resizing_icon);
+        mVeilIconView = rootLayout.findViewById(R.id.split_resizing_icon);
 
         final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
                 0 /* width */, 0 /* height */, TYPE_APPLICATION_OVERLAY,
@@ -191,28 +195,28 @@
         }
         mHostLeash = null;
         mIcon = null;
-        mResizingIconView = null;
-        mIsResizing = false;
+        mVeilIconView = null;
+        mIsCurrentlyChanging = false;
         mShown = false;
         mOldMainBounds.setEmpty();
         mOldSideBounds.setEmpty();
-        mResizingBounds.setEmpty();
+        mInstantaneousBounds.setEmpty();
     }
 
     /** Showing resizing hint. */
     public void onResizing(ActivityManager.RunningTaskInfo resizingTask, Rect newBounds,
             Rect sideBounds, SurfaceControl.Transaction t, int offsetX, int offsetY,
             boolean immediately) {
-        if (mResizingIconView == null) {
+        if (mVeilIconView == null) {
             return;
         }
 
-        if (!mIsResizing) {
-            mIsResizing = true;
+        if (!mIsCurrentlyChanging) {
+            mIsCurrentlyChanging = true;
             mOldMainBounds.set(newBounds);
             mOldSideBounds.set(sideBounds);
         }
-        mResizingBounds.set(newBounds);
+        mInstantaneousBounds.set(newBounds);
         mOffsetX = offsetX;
         mOffsetY = offsetY;
 
@@ -254,8 +258,8 @@
 
         if (mIcon == null && resizingTask.topActivityInfo != null) {
             mIcon = mIconProvider.getIcon(resizingTask.topActivityInfo);
-            mResizingIconView.setImageDrawable(mIcon);
-            mResizingIconView.setVisibility(View.VISIBLE);
+            mVeilIconView.setImageDrawable(mIcon);
+            mVeilIconView.setVisibility(View.VISIBLE);
 
             WindowManager.LayoutParams lp =
                     (WindowManager.LayoutParams) mViewHost.getView().getLayoutParams();
@@ -275,7 +279,12 @@
                 t.setAlpha(mIconLeash, showVeil ? 1f : 0f);
                 t.setVisibility(mIconLeash, showVeil);
             } else {
-                startFadeAnimation(showVeil, false, null);
+                startFadeAnimation(
+                        showVeil,
+                        false /* releaseSurface */,
+                        null /* finishedCallback */,
+                        false /* addDelay */
+                );
             }
             mShown = showVeil;
         }
@@ -320,19 +329,19 @@
             mScreenshotAnimator.start();
         }
 
-        if (mResizingIconView == null) {
+        if (mVeilIconView == null) {
             if (mRunningAnimationCount == 0 && animFinishedCallback != null) {
                 animFinishedCallback.accept(false);
             }
             return;
         }
 
-        mIsResizing = false;
+        mIsCurrentlyChanging = false;
         mOffsetX = 0;
         mOffsetY = 0;
         mOldMainBounds.setEmpty();
         mOldSideBounds.setEmpty();
-        mResizingBounds.setEmpty();
+        mInstantaneousBounds.setEmpty();
         if (mFadeAnimator != null && mFadeAnimator.isRunning()) {
             if (!mShown) {
                 // If fade-out animation is running, just add release callback to it.
@@ -356,7 +365,7 @@
                 if (mRunningAnimationCount == 0 && animFinishedCallback != null) {
                     animFinishedCallback.accept(true);
                 }
-            });
+            }, false /* addDelay */);
         } else {
             // Decor surface is hidden so release it directly.
             releaseDecor(t);
@@ -366,9 +375,94 @@
         }
     }
 
+    /**
+     * Called (on every frame) when two split apps are swapping, and a veil is needed.
+     */
+    public void drawNextVeilFrameForSwapAnimation(ActivityManager.RunningTaskInfo resizingTask,
+            Rect newBounds, SurfaceControl.Transaction t, boolean isGoingBehind,
+            SurfaceControl leash, float iconOffsetX, float iconOffsetY) {
+        if (mVeilIconView == null) {
+            return;
+        }
+
+        if (!mIsCurrentlyChanging) {
+            mIsCurrentlyChanging = true;
+        }
+
+        mInstantaneousBounds.set(newBounds);
+        mOffsetX = (int) iconOffsetX;
+        mOffsetY = (int) iconOffsetY;
+
+        t.setLayer(leash, isGoingBehind ? BEHIND_APP_VEIL_LAYER : FRONT_APP_VEIL_LAYER);
+
+        if (!mShown) {
+            if (mFadeAnimator != null && mFadeAnimator.isRunning()) {
+                // Cancel mFadeAnimator if it is running
+                mFadeAnimator.cancel();
+            }
+        }
+
+        if (mBackgroundLeash == null) {
+            // Initialize background
+            mBackgroundLeash = SurfaceUtils.makeColorLayer(mHostLeash,
+                    RESIZING_BACKGROUND_SURFACE_NAME, mSurfaceSession);
+            t.setColor(mBackgroundLeash, getResizingBackgroundColor(resizingTask))
+                    .setLayer(mBackgroundLeash, Integer.MAX_VALUE - 1);
+        }
+
+        if (mIcon == null && resizingTask.topActivityInfo != null) {
+            // Initialize icon
+            mIcon = mIconProvider.getIcon(resizingTask.topActivityInfo);
+            mVeilIconView.setImageDrawable(mIcon);
+            mVeilIconView.setVisibility(View.VISIBLE);
+
+            WindowManager.LayoutParams lp =
+                    (WindowManager.LayoutParams) mViewHost.getView().getLayoutParams();
+            lp.width = mIconSize;
+            lp.height = mIconSize;
+            mViewHost.relayout(lp);
+
+            t.setLayer(mIconLeash, Integer.MAX_VALUE);
+        }
+
+        t.setPosition(mIconLeash,
+                newBounds.width() / 2 - mIconSize / 2 - mOffsetX,
+                newBounds.height() / 2 - mIconSize / 2 - mOffsetY);
+
+        // If this is the first frame, we need to trigger the veil's fade-in animation.
+        if (!mShown) {
+            startFadeAnimation(
+                    true /* show */,
+                    false /* releaseSurface */,
+                    null /* finishedCallball */,
+                    false /* addDelay */
+            );
+            mShown = true;
+        }
+    }
+
+    /** Called at the end of the swap animation. */
+    public void fadeOutVeilAndCleanUp(SurfaceControl.Transaction t) {
+        if (mVeilIconView == null) {
+            return;
+        }
+
+        // Recenter icon
+        t.setPosition(mIconLeash,
+                mInstantaneousBounds.width() / 2f - mIconSize / 2f,
+                mInstantaneousBounds.height() / 2f - mIconSize / 2f);
+
+        mIsCurrentlyChanging = false;
+        mOffsetX = 0;
+        mOffsetY = 0;
+        mInstantaneousBounds.setEmpty();
+
+        fadeOutDecor(() -> {}, true /* addDelay */);
+    }
+
     /** Screenshot host leash and attach on it if meet some conditions */
     public void screenshotIfNeeded(SurfaceControl.Transaction t) {
-        if (!mShown && mIsResizing && !mOldMainBounds.equals(mResizingBounds)) {
+        if (!mShown && mIsCurrentlyChanging && !mOldMainBounds.equals(mInstantaneousBounds)) {
             if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) {
                 mScreenshotAnimator.cancel();
             } else if (mScreenshot != null) {
@@ -386,7 +480,7 @@
     public void setScreenshotIfNeeded(SurfaceControl screenshot, SurfaceControl.Transaction t) {
         if (screenshot == null || !screenshot.isValid()) return;
 
-        if (!mShown && mIsResizing && !mOldMainBounds.equals(mResizingBounds)) {
+        if (!mShown && mIsCurrentlyChanging && !mOldMainBounds.equals(mInstantaneousBounds)) {
             if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) {
                 mScreenshotAnimator.cancel();
             } else if (mScreenshot != null) {
@@ -401,24 +495,35 @@
 
     /** Fade-out decor surface with animation end callback, if decor is hidden, run the callback
      * directly. */
-    public void fadeOutDecor(Runnable finishedCallback) {
+    public void fadeOutDecor(Runnable finishedCallback, boolean addDelay) {
         if (mShown) {
             // If previous animation is running, just cancel it.
             if (mFadeAnimator != null && mFadeAnimator.isRunning()) {
                 mFadeAnimator.cancel();
             }
 
-            startFadeAnimation(false /* show */, true, finishedCallback);
+            startFadeAnimation(
+                    false /* show */, true /* releaseSurface */, finishedCallback, addDelay);
             mShown = false;
         } else {
             if (finishedCallback != null) finishedCallback.run();
         }
     }
 
+    /**
+     * Fades the veil in or out. Called at the first frame of a movement or resize when a veil is
+     * needed (with show = true), and called again at the end (with show = false).
+     * @param addDelay If true, adds a short delay before fading out to get the app behind the veil
+     *                 time to redraw.
+     */
     private void startFadeAnimation(boolean show, boolean releaseSurface,
-            Runnable finishedCallback) {
+            Runnable finishedCallback, boolean addDelay) {
         final SurfaceControl.Transaction animT = new SurfaceControl.Transaction();
+
         mFadeAnimator = ValueAnimator.ofFloat(0f, 1f);
+        if (addDelay) {
+            mFadeAnimator.setStartDelay(VEIL_DELAY_DURATION);
+        }
         mFadeAnimator.setDuration(FADE_DURATION);
         mFadeAnimator.addUpdateListener(valueAnimator-> {
             final float progress = (float) valueAnimator.getAnimatedValue();
@@ -481,8 +586,8 @@
         }
 
         if (mIcon != null) {
-            mResizingIconView.setVisibility(View.GONE);
-            mResizingIconView.setImageDrawable(null);
+            mVeilIconView.setVisibility(View.GONE);
+            mVeilIconView.setImageDrawable(null);
             t.hide(mIconLeash);
             mIcon = null;
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 51f9de8..0e050694 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -53,6 +53,8 @@
 import android.view.SurfaceControl;
 import android.view.WindowInsets;
 import android.view.WindowManager;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
@@ -68,10 +70,12 @@
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
 import com.android.wm.shell.common.split.SplitScreenConstants.SnapPosition;
 import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.splitscreen.StageTaskListener;
 
 import java.io.PrintWriter;
 import java.util.function.Consumer;
@@ -87,10 +91,29 @@
     public static final int PARALLAX_ALIGN_CENTER = 2;
 
     public static final int FLING_RESIZE_DURATION = 250;
-    private static final int FLING_SWITCH_DURATION = 350;
     private static final int FLING_ENTER_DURATION = 450;
     private static final int FLING_EXIT_DURATION = 450;
 
+    // Here are some (arbitrarily decided) layer definitions used during animations to make sure the
+    // layers stay in order. Note: This does not affect any other layer numbering systems because
+    // the layer system in WindowManager is local within sibling groups. So, for example, each
+    // "veil layer" defined here actually has two sub-layers; and *their* layer values, which we set
+    // in SplitDecorManager, are only important relative to each other.
+    public static final int DIVIDER_LAYER = 0;
+    public static final int FRONT_APP_VEIL_LAYER = DIVIDER_LAYER + 20;
+    public static final int FRONT_APP_LAYER = DIVIDER_LAYER + 10;
+    public static final int BEHIND_APP_VEIL_LAYER = DIVIDER_LAYER - 10;
+    public static final int BEHIND_APP_LAYER = DIVIDER_LAYER - 20;
+
+    // Animation specs for the swap animation
+    private static final int SWAP_ANIMATION_TOTAL_DURATION = 500;
+    private static final float SWAP_ANIMATION_SHRINK_DURATION = 83;
+    private static final float SWAP_ANIMATION_SHRINK_MARGIN_DP = 14;
+    private static final Interpolator SHRINK_INTERPOLATOR =
+            new PathInterpolator(0.2f, 0f, 0f, 1f);
+    private static final Interpolator GROW_INTERPOLATOR =
+            new PathInterpolator(0.45f, 0f, 0.5f, 1f);
+
     private int mDividerWindowWidth;
     private int mDividerInsets;
     private int mDividerSize;
@@ -134,6 +157,7 @@
     private final InteractionJankMonitor mInteractionJankMonitor;
     private boolean mIsLeftRightSplit;
     private ValueAnimator mDividerFlingAnimator;
+    private AnimatorSet mSwapAnimator;
 
     public SplitLayout(String windowName, Context context, Configuration configuration,
             SplitLayoutHandler splitLayoutHandler,
@@ -579,6 +603,10 @@
     }
 
     void onDoubleTappedDivider() {
+        if (isCurrentlySwapping()) {
+            return;
+        }
+
         mSplitLayoutHandler.onDoubleTappedDivider();
     }
 
@@ -685,36 +713,43 @@
     }
 
     /** Switch both surface position with animation. */
-    public void splitSwitching(SurfaceControl.Transaction t, SurfaceControl leash1,
-            SurfaceControl leash2, Consumer<Rect> finishCallback) {
+    public void playSwapAnimation(SurfaceControl.Transaction t, StageTaskListener topLeftStage,
+            StageTaskListener bottomRightStage, Consumer<Rect> finishCallback) {
         final Rect insets = getDisplayStableInsets(mContext);
+        // If we have insets in the direction of the swap, the animation won't look correct because
+        // window contents will shift and redraw again at the end. So we show a veil to hide that.
         insets.set(mIsLeftRightSplit ? insets.left : 0, mIsLeftRightSplit ? 0 : insets.top,
                 mIsLeftRightSplit ? insets.right : 0, mIsLeftRightSplit ? 0 : insets.bottom);
+        final boolean shouldVeil =
+                insets.left != 0 || insets.top != 0 || insets.right != 0 || insets.bottom != 0;
 
         final int dividerPos = mDividerSnapAlgorithm.calculateNonDismissingSnapTarget(
                 mIsLeftRightSplit ? mBounds2.width() : mBounds2.height()).position;
-        final Rect distBounds1 = new Rect();
-        final Rect distBounds2 = new Rect();
-        final Rect distDividerBounds = new Rect();
-        // Compute dist bounds.
-        updateBounds(dividerPos, distBounds2, distBounds1, distDividerBounds,
+        final Rect endBounds1 = new Rect();
+        final Rect endBounds2 = new Rect();
+        final Rect endDividerBounds = new Rect();
+        // Compute destination bounds.
+        updateBounds(dividerPos, endBounds2, endBounds1, endDividerBounds,
                 false /* setEffectBounds */);
         // Offset to real position under root container.
-        distBounds1.offset(-mRootBounds.left, -mRootBounds.top);
-        distBounds2.offset(-mRootBounds.left, -mRootBounds.top);
-        distDividerBounds.offset(-mRootBounds.left, -mRootBounds.top);
+        endBounds1.offset(-mRootBounds.left, -mRootBounds.top);
+        endBounds2.offset(-mRootBounds.left, -mRootBounds.top);
+        endDividerBounds.offset(-mRootBounds.left, -mRootBounds.top);
 
-        ValueAnimator animator1 = moveSurface(t, leash1, getRefBounds1(), distBounds1,
-                -insets.left, -insets.top);
-        ValueAnimator animator2 = moveSurface(t, leash2, getRefBounds2(), distBounds2,
-                insets.left, insets.top);
-        ValueAnimator animator3 = moveSurface(t, getDividerLeash(), getRefDividerBounds(),
-                distDividerBounds, 0 /* offsetX */, 0 /* offsetY */);
+        ValueAnimator animator1 = moveSurface(t, topLeftStage, getRefBounds1(), endBounds1,
+                -insets.left, -insets.top, true /* roundCorners */, true /* isGoingBehind */,
+                shouldVeil);
+        ValueAnimator animator2 = moveSurface(t, bottomRightStage, getRefBounds2(), endBounds2,
+                insets.left, insets.top, true /* roundCorners */, false /* isGoingBehind */,
+                shouldVeil);
+        ValueAnimator animator3 = moveSurface(t, null /* stage */, getRefDividerBounds(),
+                endDividerBounds, 0 /* offsetX */, 0 /* offsetY */, false /* roundCorners */,
+                false /* isGoingBehind */, false /* addVeil */);
 
-        AnimatorSet set = new AnimatorSet();
-        set.playTogether(animator1, animator2, animator3);
-        set.setDuration(FLING_SWITCH_DURATION);
-        set.addListener(new AnimatorListenerAdapter() {
+        mSwapAnimator = new AnimatorSet();
+        mSwapAnimator.playTogether(animator1, animator2, animator3);
+        mSwapAnimator.setDuration(SWAP_ANIMATION_TOTAL_DURATION);
+        mSwapAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(Animator animation) {
                 mInteractionJankMonitor.begin(getDividerLeash(),
@@ -734,36 +769,144 @@
                 mInteractionJankMonitor.cancel(CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER);
             }
         });
-        set.start();
+        mSwapAnimator.start();
     }
 
-    private ValueAnimator moveSurface(SurfaceControl.Transaction t, SurfaceControl leash,
-            Rect start, Rect end, float offsetX, float offsetY) {
+    /** Returns true if a swap animation is currently playing. */
+    public boolean isCurrentlySwapping() {
+        return mSwapAnimator != null && mSwapAnimator.isRunning();
+    }
+
+    /**
+     * Animates a task leash across the screen. Currently used only for the swap animation.
+     *
+     * @param stage The stage holding the task being animated. If null, it is the divider.
+     * @param roundCorners Whether we should round the corners of the task while animating.
+     * @param isGoingBehind Whether we should a shrink-and-grow effect to the task while it is
+     *                           moving. (Simulates moving behind the divider.)
+     */
+    private ValueAnimator moveSurface(SurfaceControl.Transaction t, StageTaskListener stage,
+            Rect start, Rect end, float offsetX, float offsetY, boolean roundCorners,
+            boolean isGoingBehind, boolean addVeil) {
+        final boolean isApp = stage != null; // check if this is an app or a divider
+        final SurfaceControl leash = isApp ? stage.getRootLeash() : getDividerLeash();
+        final ActivityManager.RunningTaskInfo taskInfo = isApp ? stage.getRunningTaskInfo() : null;
+        final SplitDecorManager decorManager = isApp ? stage.getDecorManager() : null;
+
         Rect tempStart = new Rect(start);
         Rect tempEnd = new Rect(end);
         final float diffX = tempEnd.left - tempStart.left;
         final float diffY = tempEnd.top - tempStart.top;
         final float diffWidth = tempEnd.width() - tempStart.width();
         final float diffHeight = tempEnd.height() - tempStart.height();
+
+        // Get display measurements (for possible shrink animation).
+        final RoundedCorner roundedCorner = mSplitWindowManager.getDividerView().getDisplay()
+                .getRoundedCorner(0 /* position */);
+        float cornerRadius = roundedCorner == null ? 0 : roundedCorner.getRadius();
+        float shrinkMarginPx = PipUtils.dpToPx(
+                SWAP_ANIMATION_SHRINK_MARGIN_DP, mContext.getResources().getDisplayMetrics());
+        float shrinkAmountPx = shrinkMarginPx * 2;
+
+        // Timing calculations
+        float shrinkPortion = SWAP_ANIMATION_SHRINK_DURATION / SWAP_ANIMATION_TOTAL_DURATION;
+        float growPortion = 1 - shrinkPortion;
+
         ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
+        animator.setInterpolator(Interpolators.EMPHASIZED);
         animator.addUpdateListener(animation -> {
             if (leash == null) return;
+            if (roundCorners) {
+                // Add rounded corners to the task leash while it is animating.
+                t.setCornerRadius(leash, cornerRadius);
+            }
 
-            final float scale = (float) animation.getAnimatedValue();
-            final float distX = tempStart.left + scale * diffX;
-            final float distY = tempStart.top + scale * diffY;
-            final int width = (int) (tempStart.width() + scale * diffWidth);
-            final int height = (int) (tempStart.height() + scale * diffHeight);
-            if (offsetX == 0 && offsetY == 0) {
-                t.setPosition(leash, distX, distY);
-                t.setWindowCrop(leash, width, height);
+            final float progress = (float) animation.getAnimatedValue();
+            float instantaneousX = tempStart.left + progress * diffX;
+            float instantaneousY = tempStart.top + progress * diffY;
+            int width = (int) (tempStart.width() + progress * diffWidth);
+            int height = (int) (tempStart.height() + progress * diffHeight);
+
+            if (isGoingBehind) {
+                float shrinkDiffX; // the position adjustments needed for this frame
+                float shrinkDiffY;
+                float shrinkScaleX; // the scale adjustments needed for this frame
+                float shrinkScaleY;
+
+                // Find the max amount we will be shrinking this leash, as a proportion (e.g. 0.1f).
+                float maxShrinkX = shrinkAmountPx / height;
+                float maxShrinkY = shrinkAmountPx / width;
+
+                // Find if we are in the shrinking part of the animation, or the growing part.
+                boolean shrinking = progress <= shrinkPortion;
+
+                if (shrinking) {
+                    // Find how far into the shrink portion we are (e.g. 0.5f).
+                    float shrinkProgress = progress / shrinkPortion;
+                    // Find how much we should have progressed in shrinking the leash (e.g. 0.8f).
+                    float interpolatedShrinkProgress =
+                            SHRINK_INTERPOLATOR.getInterpolation(shrinkProgress);
+                    // Find how much width proportion we should be taking off (e.g. 0.1f)
+                    float widthProportionLost =  maxShrinkX * interpolatedShrinkProgress;
+                    shrinkScaleX = 1 - widthProportionLost;
+                    // Find how much height proportion we should be taking off (e.g. 0.1f)
+                    float heightProportionLost =  maxShrinkY * interpolatedShrinkProgress;
+                    shrinkScaleY = 1 - heightProportionLost;
+                    // Add a small amount to the leash's position to keep the task centered.
+                    shrinkDiffX = (width * widthProportionLost) / 2;
+                    shrinkDiffY = (height * heightProportionLost) / 2;
+                } else {
+                    // Find how far into the grow portion we are (e.g. 0.5f).
+                    float growProgress = (progress - shrinkPortion) / growPortion;
+                    // Find how much we should have progressed in growing the leash (e.g. 0.8f).
+                    float interpolatedGrowProgress =
+                            GROW_INTERPOLATOR.getInterpolation(growProgress);
+                    // Find how much width proportion we should be taking off (e.g. 0.1f)
+                    float widthProportionLost =  maxShrinkX * (1 - interpolatedGrowProgress);
+                    shrinkScaleX = 1 - widthProportionLost;
+                    // Find how much height proportion we should be taking off (e.g. 0.1f)
+                    float heightProportionLost =  maxShrinkY * (1 - interpolatedGrowProgress);
+                    shrinkScaleY = 1 - heightProportionLost;
+                    // Add a small amount to the leash's position to keep the task centered.
+                    shrinkDiffX = (width * widthProportionLost) / 2;
+                    shrinkDiffY = (height * heightProportionLost) / 2;
+                }
+
+                instantaneousX += shrinkDiffX;
+                instantaneousY += shrinkDiffY;
+                width *= shrinkScaleX;
+                height *= shrinkScaleY;
+                // Set scale on the leash's contents.
+                t.setScale(leash, shrinkScaleX, shrinkScaleY);
+            }
+
+            // Set layers
+            if (taskInfo != null) {
+                t.setLayer(leash, isGoingBehind ? BEHIND_APP_LAYER : FRONT_APP_LAYER);
             } else {
-                final int diffOffsetX = (int) (scale * offsetX);
-                final int diffOffsetY = (int) (scale * offsetY);
-                t.setPosition(leash, distX + diffOffsetX, distY + diffOffsetY);
+                t.setLayer(leash, DIVIDER_LAYER);
+            }
+
+            if (offsetX == 0 && offsetY == 0) {
+                t.setPosition(leash, instantaneousX, instantaneousY);
+                mTempRect.set((int) instantaneousX, (int) instantaneousY,
+                        (int) (instantaneousX + width), (int) (instantaneousY + height));
+                t.setWindowCrop(leash, width, height);
+                if (addVeil) {
+                    decorManager.drawNextVeilFrameForSwapAnimation(
+                            taskInfo, mTempRect, t, isGoingBehind, leash, 0, 0);
+                }
+            } else {
+                final int diffOffsetX = (int) (progress * offsetX);
+                final int diffOffsetY = (int) (progress * offsetY);
+                t.setPosition(leash, instantaneousX + diffOffsetX, instantaneousY + diffOffsetY);
                 mTempRect.set(0, 0, width, height);
                 mTempRect.offsetTo(-diffOffsetX, -diffOffsetY);
                 t.setCrop(leash, mTempRect);
+                if (addVeil) {
+                    decorManager.drawNextVeilFrameForSwapAnimation(
+                            taskInfo, mTempRect, t, isGoingBehind, leash, diffOffsetX, diffOffsetY);
+                }
             }
             t.apply();
         });
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
index e8c809e..8c06de7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
@@ -29,6 +29,8 @@
 public class SplitScreenConstants {
     /** Duration used for every split fade-in or fade-out. */
     public static final int FADE_DURATION = 133;
+    /** Duration where we keep an app veiled to allow it to redraw itself behind the scenes. */
+    public static final int VEIL_DELAY_DURATION = 400;
 
     /** Key for passing in widget intents when invoking split from launcher workspace. */
     public static final String KEY_EXTRA_WIDGET_INTENT = "key_extra_widget_intent";
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 7c0455e..c2ee223 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
@@ -186,6 +186,9 @@
      */
     private boolean mIsFirstReachabilityEducationRunning;
 
+    @NonNull
+    private final CompatUIStatusManager mCompatUIStatusManager;
+
     public CompatUIController(@NonNull Context context,
             @NonNull ShellInit shellInit,
             @NonNull ShellController shellController,
@@ -198,7 +201,8 @@
             @NonNull DockStateReader dockStateReader,
             @NonNull CompatUIConfiguration compatUIConfiguration,
             @NonNull CompatUIShellCommandHandler compatUIShellCommandHandler,
-            @NonNull AccessibilityManager accessibilityManager) {
+            @NonNull AccessibilityManager accessibilityManager,
+            @NonNull CompatUIStatusManager compatUIStatusManager) {
         mContext = context;
         mShellController = shellController;
         mDisplayController = displayController;
@@ -213,6 +217,7 @@
         mCompatUIShellCommandHandler = compatUIShellCommandHandler;
         mDisappearTimeSupplier = flags -> accessibilityManager.getRecommendedTimeoutMillis(
                 DISAPPEAR_DELAY_MS, flags);
+        mCompatUIStatusManager = compatUIStatusManager;
         shellInit.addInitCallback(this::onInit, this);
     }
 
@@ -520,7 +525,7 @@
                 mSyncQueue, taskListener, mDisplayController.getDisplayLayout(taskInfo.displayId),
                 mTransitionsLazy.get(),
                 stateInfo -> createOrUpdateReachabilityEduLayout(stateInfo.first, stateInfo.second),
-                mDockStateReader, mCompatUIConfiguration);
+                mDockStateReader, mCompatUIConfiguration, mCompatUIStatusManager);
     }
 
     private void createOrUpdateRestartDialogLayout(@NonNull TaskInfo taskInfo,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIStatusManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIStatusManager.java
new file mode 100644
index 0000000..915a8a1
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIStatusManager.java
@@ -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.compatui;
+
+import android.annotation.NonNull;
+
+import java.util.function.IntConsumer;
+import java.util.function.IntSupplier;
+
+/** Handle the visibility state of the Compat UI components. */
+public class CompatUIStatusManager {
+
+    public static final int COMPAT_UI_EDUCATION_HIDDEN = 0;
+    public static final int COMPAT_UI_EDUCATION_VISIBLE = 1;
+
+    @NonNull
+    private final IntConsumer mWriter;
+    @NonNull
+    private final IntSupplier mReader;
+
+    public CompatUIStatusManager(@NonNull IntConsumer writer, @NonNull IntSupplier reader) {
+        mWriter = writer;
+        mReader = reader;
+    }
+
+    public CompatUIStatusManager() {
+        this(i -> { }, () -> COMPAT_UI_EDUCATION_HIDDEN);
+    }
+
+    void onEducationShown() {
+        mWriter.accept(COMPAT_UI_EDUCATION_VISIBLE);
+    }
+
+    void onEducationHidden() {
+        mWriter.accept(COMPAT_UI_EDUCATION_HIDDEN);
+    }
+
+    boolean isEducationVisible() {
+        return mReader.getAsInt() == COMPAT_UI_EDUCATION_VISIBLE;
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduWindowManager.java
index 2347032..3124a39 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduWindowManager.java
@@ -19,6 +19,7 @@
 import static android.provider.Settings.Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING;
 import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.TaskInfo;
 import android.content.Context;
@@ -76,15 +77,19 @@
 
     private final DockStateReader mDockStateReader;
 
+    @NonNull
+    private final CompatUIStatusManager mCompatUIStatusManager;
+
     LetterboxEduWindowManager(Context context, TaskInfo taskInfo,
             SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener,
             DisplayLayout displayLayout, Transitions transitions,
             Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onDismissCallback,
-            DockStateReader dockStateReader, CompatUIConfiguration compatUIConfiguration) {
+            DockStateReader dockStateReader, CompatUIConfiguration compatUIConfiguration,
+            @NonNull CompatUIStatusManager compatUIStatusManager) {
         this(context, taskInfo, syncQueue, taskListener, displayLayout, transitions,
                 onDismissCallback,
                 new DialogAnimationController<>(context, /* tag */ "LetterboxEduWindowManager"),
-                dockStateReader, compatUIConfiguration);
+                dockStateReader, compatUIConfiguration, compatUIStatusManager);
     }
 
     @VisibleForTesting
@@ -93,7 +98,8 @@
             DisplayLayout displayLayout, Transitions transitions,
             Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onDismissCallback,
             DialogAnimationController<LetterboxEduDialogLayout> animationController,
-            DockStateReader dockStateReader, CompatUIConfiguration compatUIConfiguration) {
+            DockStateReader dockStateReader, CompatUIConfiguration compatUIConfiguration,
+            @NonNull CompatUIStatusManager compatUIStatusManager) {
         super(context, taskInfo, syncQueue, taskListener, displayLayout);
         mTransitions = transitions;
         mOnDismissCallback = onDismissCallback;
@@ -103,6 +109,7 @@
                 R.dimen.letterbox_education_dialog_margin);
         mDockStateReader = dockStateReader;
         mCompatUIConfiguration = compatUIConfiguration;
+        mCompatUIStatusManager = compatUIStatusManager;
         mEligibleForLetterboxEducation =
                 taskInfo.appCompatTaskInfo.eligibleForLetterboxEducation();
     }
@@ -139,7 +146,7 @@
     protected View createLayout() {
         mLayout = inflateLayout();
         updateDialogMargins();
-
+        mCompatUIStatusManager.onEducationShown();
         // startEnterAnimation will be called immediately if shell-transitions are disabled.
         mTransitions.runOnIdle(this::startEnterAnimation);
         return mLayout;
@@ -199,6 +206,7 @@
     @Override
     public void release() {
         mAnimationController.cancelAnimation();
+        mCompatUIStatusManager.onEducationHidden();
         super.release();
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/OWNERS
new file mode 100644
index 0000000..1875675
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/OWNERS
@@ -0,0 +1,4 @@
+# WM shell sub-module compat ui owners
+mariiasand@google.com
+gracielawputri@google.com
+mcarli@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUISpec.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUISpec.kt
index a520d5e..022906c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUISpec.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUISpec.kt
@@ -16,6 +16,9 @@
 
 package com.android.wm.shell.compatui.api
 
+import com.android.internal.protolog.ProtoLog
+import com.android.wm.shell.protolog.ShellProtoLogGroup
+
 /**
  * Defines the predicates to invoke for understanding if a component can be created or destroyed.
  */
@@ -39,6 +42,7 @@
  * Describes each compat ui component to the framework.
  */
 class CompatUISpec(
+    val log: (String) -> Unit = { str -> ProtoLog.v(ShellProtoLogGroup.WM_SHELL_COMPAT_UI, str) },
     // Unique name for the component. It's used for debug and for generating the
     // unique component identifier in the system.
     val name: String,
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 f22dcce..04cd225 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
@@ -16,6 +16,9 @@
 
 package com.android.wm.shell.dagger;
 
+import static android.provider.Settings.Secure.COMPAT_UI_EDUCATION_SHOWING;
+
+import static com.android.wm.shell.compatui.CompatUIStatusManager.COMPAT_UI_EDUCATION_HIDDEN;
 import static com.android.wm.shell.onehanded.OneHandedController.SUPPORT_ONE_HANDED_MODE;
 
 import android.annotation.NonNull;
@@ -24,6 +27,7 @@
 import android.content.pm.PackageManager;
 import android.os.Handler;
 import android.os.SystemProperties;
+import android.provider.Settings;
 import android.view.IWindowManager;
 import android.view.accessibility.AccessibilityManager;
 import android.window.SystemPerformanceHinter;
@@ -72,6 +76,7 @@
 import com.android.wm.shell.compatui.CompatUIConfiguration;
 import com.android.wm.shell.compatui.CompatUIController;
 import com.android.wm.shell.compatui.CompatUIShellCommandHandler;
+import com.android.wm.shell.compatui.CompatUIStatusManager;
 import com.android.wm.shell.compatui.api.CompatUIComponentIdGenerator;
 import com.android.wm.shell.compatui.api.CompatUIHandler;
 import com.android.wm.shell.compatui.api.CompatUIRepository;
@@ -254,7 +259,8 @@
             Lazy<AccessibilityManager> accessibilityManager,
             CompatUIRepository compatUIRepository,
             @NonNull CompatUIState compatUIState,
-            @NonNull CompatUIComponentIdGenerator componentIdGenerator) {
+            @NonNull CompatUIComponentIdGenerator componentIdGenerator,
+            CompatUIStatusManager compatUIStatusManager) {
         if (!context.getResources().getBoolean(R.bool.config_enableCompatUIController)) {
             return Optional.empty();
         }
@@ -276,7 +282,22 @@
                         dockStateReader.get(),
                         compatUIConfiguration.get(),
                         compatUIShellCommandHandler.get(),
-                        accessibilityManager.get()));
+                        accessibilityManager.get(),
+                        compatUIStatusManager));
+    }
+
+    @WMSingleton
+    @Provides
+    static CompatUIStatusManager provideCompatUIStatusManager(@NonNull Context context) {
+        if (Flags.enableCompatUiVisibilityStatus()) {
+            return new CompatUIStatusManager(
+                    newState -> Settings.Secure.putInt(context.getContentResolver(),
+                            COMPAT_UI_EDUCATION_SHOWING, newState),
+                    () -> Settings.Secure.getInt(context.getContentResolver(),
+                            COMPAT_UI_EDUCATION_SHOWING, COMPAT_UI_EDUCATION_HIDDEN));
+        } else {
+            return new CompatUIStatusManager();
+        }
     }
 
     @WMSingleton
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 a18bbad..e787a3d 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
@@ -32,6 +32,7 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.launcher3.icons.IconProvider;
+import com.android.window.flags.Flags;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.WindowManagerShellWrapper;
@@ -58,6 +59,7 @@
 import com.android.wm.shell.common.TransactionPool;
 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.DesktopModeEventLogger;
 import com.android.wm.shell.desktopmode.DesktopModeLoggerTransitionObserver;
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
@@ -68,7 +70,9 @@
 import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler;
 import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler;
 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.data.AppHandleEducationDatastoreRepository;
 import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.draganddrop.GlobalDragListener;
 import com.android.wm.shell.freeform.FreeformComponents;
@@ -603,10 +607,12 @@
             Context context,
             Transitions transitions,
             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
-            Optional<DesktopTasksLimiter> desktopTasksLimiter,
             InteractionJankMonitor interactionJankMonitor) {
-        return new DragToDesktopTransitionHandler(context, transitions,
-                rootTaskDisplayAreaOrganizer, interactionJankMonitor);
+        return Flags.enableDesktopWindowingTransitions()
+                ? new SpringDragToDesktopTransitionHandler(context, transitions,
+                        rootTaskDisplayAreaOrganizer, interactionJankMonitor)
+                : new DefaultDragToDesktopTransitionHandler(context, transitions,
+                        rootTaskDisplayAreaOrganizer, interactionJankMonitor);
     }
 
     @WMSingleton
@@ -674,6 +680,13 @@
         return new DesktopModeEventLogger();
     }
 
+    @WMSingleton
+    @Provides
+    static AppHandleEducationDatastoreRepository provideAppHandleEducationDatastoreRepository(
+            Context context) {
+        return new AppHandleEducationDatastoreRepository(context);
+    }
+
     //
     // Drag and drop
     //
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 400882a..05c9d02 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
@@ -73,25 +73,9 @@
             sessionId,
             taskUpdate.instanceId
         )
-        FrameworkStatsLog.write(
-            DESKTOP_MODE_TASK_UPDATE_ATOM_ID,
-            /* task_event */
+        logTaskUpdate(
             FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_ADDED,
-            /* instance_id */
-            taskUpdate.instanceId,
-            /* uid */
-            taskUpdate.uid,
-            /* task_height */
-            taskUpdate.taskHeight,
-            /* task_width */
-            taskUpdate.taskWidth,
-            /* task_x */
-            taskUpdate.taskX,
-            /* task_y */
-            taskUpdate.taskY,
-            /* session_id */
-            sessionId
-        )
+            sessionId, taskUpdate)
     }
 
     /**
@@ -105,25 +89,9 @@
             sessionId,
             taskUpdate.instanceId
         )
-        FrameworkStatsLog.write(
-            DESKTOP_MODE_TASK_UPDATE_ATOM_ID,
-            /* task_event */
+        logTaskUpdate(
             FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_REMOVED,
-            /* instance_id */
-            taskUpdate.instanceId,
-            /* uid */
-            taskUpdate.uid,
-            /* task_height */
-            taskUpdate.taskHeight,
-            /* task_width */
-            taskUpdate.taskWidth,
-            /* task_x */
-            taskUpdate.taskX,
-            /* task_y */
-            taskUpdate.taskY,
-            /* session_id */
-            sessionId
-        )
+            sessionId, taskUpdate)
     }
 
     /**
@@ -137,10 +105,16 @@
             sessionId,
             taskUpdate.instanceId
         )
+        logTaskUpdate(
+            FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED,
+            sessionId, taskUpdate)
+    }
+
+    private fun logTaskUpdate(taskEvent: Int, sessionId: Int, taskUpdate: TaskUpdate) {
         FrameworkStatsLog.write(
             DESKTOP_MODE_TASK_UPDATE_ATOM_ID,
             /* task_event */
-            FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED,
+            taskEvent,
             /* instance_id */
             taskUpdate.instanceId,
             /* uid */
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 73aa7ce..a6ed3b8 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.Trace
 import android.util.SparseArray
 import android.view.SurfaceControl
 import android.view.WindowManager
@@ -51,6 +52,8 @@
 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
@@ -292,8 +295,14 @@
             val previousTaskInfo = preTransitionVisibleFreeformTasks[taskId]
             when {
                 // new tasks added
-                previousTaskInfo == null ->
+                previousTaskInfo == null -> {
                     desktopModeEventLogger.logTaskAdded(sessionId, currentTaskUpdate)
+                    Trace.setCounter(
+                        Trace.TRACE_TAG_WINDOW_MANAGER,
+                        VISIBLE_TASKS_COUNTER_NAME,
+                        postTransitionVisibleFreeformTasks.size().toLong()
+                    )
+                }
                 // old tasks that were resized or repositioned
                 // TODO(b/347935387): Log changes only once they are stable.
                 buildTaskUpdateForTask(previousTaskInfo) != currentTaskUpdate ->
@@ -305,6 +314,11 @@
         preTransitionVisibleFreeformTasks.forEach { taskId, taskInfo ->
             if (!postTransitionVisibleFreeformTasks.containsKey(taskId)) {
                 desktopModeEventLogger.logTaskRemoved(sessionId, buildTaskUpdateForTask(taskInfo))
+                Trace.setCounter(
+                    Trace.TRACE_TAG_WINDOW_MANAGER,
+                    VISIBLE_TASKS_COUNTER_NAME,
+                    postTransitionVisibleFreeformTasks.size().toLong()
+                )
             }
         }
     }
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 3e7b4fe..6c03dc3 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
@@ -54,6 +54,12 @@
     // Instead default to the desired initial bounds.
     val stableBounds = Rect()
     displayLayout.getStableBoundsForDesktopMode(stableBounds)
+    if (hasFullscreenOverride(taskInfo)) {
+        // If the activity has a fullscreen override applied, it should be treated as
+        // resizeable and match the device orientation. Thus the ideal size can be
+        // applied.
+        return positionInScreen(idealSize, stableBounds)
+    }
     val topActivityInfo =
         taskInfo.topActivityInfo ?: return positionInScreen(idealSize, stableBounds)
 
@@ -62,13 +68,17 @@
             ORIENTATION_LANDSCAPE -> {
                 if (taskInfo.isResizeable) {
                     if (isFixedOrientationPortrait(topActivityInfo.screenOrientation)) {
-                        // Respect apps fullscreen width
+                        // For portrait resizeable activities, respect apps fullscreen width but
+                        // apply ideal size height.
                         Size(taskInfo.appCompatTaskInfo.topActivityLetterboxAppWidth,
                             idealSize.height)
                     } else {
+                        // For landscape resizeable activities, simply apply ideal size.
                         idealSize
                     }
                 } else {
+                    // If activity is unresizeable, regardless of orientation, calculate maximum
+                    // size (within the ideal size) maintaining original aspect ratio.
                     maximizeSizeGivenAspectRatio(taskInfo, idealSize, appAspectRatio)
                 }
             }
@@ -77,23 +87,29 @@
                     screenBounds.width() - (DESKTOP_MODE_LANDSCAPE_APP_PADDING * 2)
                 if (taskInfo.isResizeable) {
                     if (isFixedOrientationLandscape(topActivityInfo.screenOrientation)) {
-                        // Respect apps fullscreen height and apply custom app width
+                        // For landscape resizeable activities, respect apps fullscreen height and
+                        // apply custom app width.
                         Size(
                             customPortraitWidthForLandscapeApp,
                             taskInfo.appCompatTaskInfo.topActivityLetterboxAppHeight
                         )
                     } else {
+                        // For portrait resizeable activities, simply apply ideal size.
                         idealSize
                     }
                 } else {
                     if (isFixedOrientationLandscape(topActivityInfo.screenOrientation)) {
-                        // Apply custom app width and calculate maximum size
+                        // For landscape unresizeable activities, apply custom app width to ideal
+                        // size and calculate maximum size with this area while maintaining original
+                        // aspect ratio.
                         maximizeSizeGivenAspectRatio(
                             taskInfo,
                             Size(customPortraitWidthForLandscapeApp, idealSize.height),
                             appAspectRatio
                         )
                     } else {
+                        // For portrait unresizeable activities, calculate maximum size (within the
+                        // ideal size) maintaining original aspect ratio.
                         maximizeSizeGivenAspectRatio(taskInfo, idealSize, appAspectRatio)
                     }
                 }
@@ -209,3 +225,8 @@
         else -> isFixedOrientationPortrait(configuration.orientation)
     }
 }
+
+private fun hasFullscreenOverride(taskInfo: RunningTaskInfo): Boolean {
+    return taskInfo.appCompatTaskInfo.isUserFullscreenOverrideEnabled
+            || taskInfo.appCompatTaskInfo.isSystemFullscreenOverrideEnabled
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
index 6011db7..09f9139 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -27,6 +27,7 @@
 import android.animation.RectEvaluator;
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.WindowConfiguration;
 import android.content.Context;
@@ -262,12 +263,22 @@
 
     /**
      * Fade out indicator without fully releasing it. Animator fades it out while shrinking bounds.
+     *
+     * @param finishCallback called when animation ends or gets cancelled
      */
-    private void fadeOutIndicator() {
+    void fadeOutIndicator(@Nullable Runnable finishCallback) {
         final VisualIndicatorAnimator animator = VisualIndicatorAnimator
                 .fadeBoundsOut(mView, mCurrentType,
                         mDisplayController.getDisplayLayout(mTaskInfo.displayId));
         animator.start();
+        if (finishCallback != null) {
+            animator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    finishCallback.run();
+                }
+            });
+        }
         mCurrentType = IndicatorType.NO_INDICATOR;
     }
 
@@ -282,7 +293,7 @@
         if (mCurrentType == IndicatorType.NO_INDICATOR) {
             fadeInIndicator(newType);
         } else if (newType == IndicatorType.NO_INDICATOR) {
-            fadeOutIndicator();
+            fadeOutIndicator(null /* finishCallback */);
         } else {
             final VisualIndicatorAnimator animator = VisualIndicatorAnimator.animateIndicatorType(
                     mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId), mCurrentType,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskPosition.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskPosition.kt
index 97abda8..65f12cf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskPosition.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskPosition.kt
@@ -116,10 +116,10 @@
 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
 fun Rect.getDesktopTaskPosition(bounds: Rect): DesktopTaskPosition {
     return when {
-        top == bounds.top && left == bounds.left -> TopLeft
-        top == bounds.top && right == bounds.right -> TopRight
-        bottom == bounds.bottom && left == bounds.left -> BottomLeft
-        bottom == bounds.bottom && right == bounds.right -> BottomRight
+        top == bounds.top && left == bounds.left && bottom != bounds.bottom -> TopLeft
+        top == bounds.top && right == bounds.right && bottom != bounds.bottom -> TopRight
+        bottom == bounds.bottom && left == bounds.left && top != bounds.top -> BottomLeft
+        bottom == bounds.bottom && right == bounds.right && top != bounds.top -> BottomRight
         else -> Center
     }
 }
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 e154da5..f54b44b 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
@@ -163,8 +163,10 @@
             }
 
             private fun removeVisualIndicator(tx: SurfaceControl.Transaction) {
-                visualIndicator?.releaseVisualIndicator(tx)
-                visualIndicator = null
+                visualIndicator?.fadeOutIndicator {
+                    visualIndicator?.releaseVisualIndicator(tx)
+                    visualIndicator = null
+                }
             }
         }
 
@@ -193,7 +195,7 @@
         )
         transitions.addHandler(this)
         taskRepository.addVisibleTasksListener(taskVisibilityListener, mainExecutor)
-        dragToDesktopTransitionHandler.setDragToDesktopStateListener(dragToDesktopStateListener)
+        dragToDesktopTransitionHandler.dragToDesktopStateListener = dragToDesktopStateListener
         recentsTransitionHandler.addTransitionStateListener(
             object : RecentsTransitionStateListener {
                 override fun onAnimationStateChanged(running: Boolean) {
@@ -213,7 +215,7 @@
     fun setOnTaskResizeAnimationListener(listener: OnTaskResizeAnimationListener) {
         toggleResizeDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener)
         enterDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener)
-        dragToDesktopTransitionHandler.setOnTaskResizeAnimatorListener(listener)
+        dragToDesktopTransitionHandler.onTaskResizeAnimationListener = listener
     }
 
     fun setOnTaskRepositionAnimationListener(listener: OnTaskRepositionAnimationListener) {
@@ -1068,6 +1070,11 @@
                 // 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) }
                 // 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)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
index 38675129..597637d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
@@ -70,11 +70,11 @@
 
     // TODO(b/333018485): replace this observer when implementing the minimize-animation
     private inner class MinimizeTransitionObserver : TransitionObserver {
-        private val mPendingTransitionTokensAndTasks = mutableMapOf<IBinder, TaskDetails>()
-        private val mActiveTransitionTokensAndTasks = mutableMapOf<IBinder, TaskDetails>()
+        private val pendingTransitionTokensAndTasks = mutableMapOf<IBinder, TaskDetails>()
+        private val activeTransitionTokensAndTasks = mutableMapOf<IBinder, TaskDetails>()
 
         fun addPendingTransitionToken(transition: IBinder, taskDetails: TaskDetails) {
-            mPendingTransitionTokensAndTasks[transition] = taskDetails
+            pendingTransitionTokensAndTasks[transition] = taskDetails
         }
 
         override fun onTransitionReady(
@@ -83,9 +83,7 @@
             startTransaction: SurfaceControl.Transaction,
             finishTransaction: SurfaceControl.Transaction
         ) {
-            val taskToMinimize = mPendingTransitionTokensAndTasks.remove(transition) ?: return
-            taskToMinimize.transitionInfo = info
-            mActiveTransitionTokensAndTasks[transition] = taskToMinimize
+            val taskToMinimize = pendingTransitionTokensAndTasks.remove(transition) ?: return
 
             if (!taskRepository.isActiveTask(taskToMinimize.taskId)) return
 
@@ -97,6 +95,8 @@
                 return
             }
 
+            taskToMinimize.transitionInfo = info
+            activeTransitionTokensAndTasks[transition] = taskToMinimize
             this@DesktopTasksLimiter.markTaskMinimized(
                     taskToMinimize.displayId, taskToMinimize.taskId)
         }
@@ -121,7 +121,7 @@
         }
 
         override fun onTransitionStarting(transition: IBinder) {
-            val mActiveTaskDetails = mActiveTransitionTokensAndTasks[transition]
+            val mActiveTaskDetails = activeTransitionTokensAndTasks[transition]
             if (mActiveTaskDetails != null && mActiveTaskDetails.transitionInfo != null) {
                 // Begin minimize window CUJ instrumentation.
                 interactionJankMonitor.begin(
@@ -132,11 +132,11 @@
         }
 
         override fun onTransitionMerged(merged: IBinder, playing: IBinder) {
-            if (mActiveTransitionTokensAndTasks.remove(merged) != null) {
+            if (activeTransitionTokensAndTasks.remove(merged) != null) {
                 interactionJankMonitor.end(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW)
             }
-            mPendingTransitionTokensAndTasks.remove(merged)?.let { taskToTransfer ->
-                mPendingTransitionTokensAndTasks[playing] = taskToTransfer
+            pendingTransitionTokensAndTasks.remove(merged)?.let { taskToTransfer ->
+                pendingTransitionTokensAndTasks[playing] = taskToTransfer
             }
         }
 
@@ -144,14 +144,14 @@
             ProtoLog.v(
                     ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
                     "DesktopTasksLimiter: transition %s finished", transition)
-            if (mActiveTransitionTokensAndTasks.remove(transition) != null) {
+            if (activeTransitionTokensAndTasks.remove(transition) != null) {
                 if (aborted) {
                     interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW)
                 } else {
                     interactionJankMonitor.end(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW)
                 }
             }
-            mPendingTransitionTokensAndTasks.remove(transition)
+            pendingTransitionTokensAndTasks.remove(transition)
         }
     }
 
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 5221a45..9874f4c 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
@@ -27,19 +27,21 @@
 import android.window.TransitionInfo
 import android.window.TransitionInfo.Change
 import android.window.TransitionRequestInfo
-import android.window.WindowContainerToken
 import android.window.WindowContainerTransaction
+import androidx.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
 import com.android.internal.protolog.ProtoLog
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.animation.FloatProperties
 import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
 import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
 import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED
 import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition
 import com.android.wm.shell.protolog.ShellProtoLogGroup
 import com.android.wm.shell.shared.TransitionUtil
+import com.android.wm.shell.shared.animation.PhysicsAnimator
 import com.android.wm.shell.splitscreen.SplitScreenController
 import com.android.wm.shell.transition.Transitions
 import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP
@@ -50,40 +52,31 @@
 import com.android.wm.shell.windowdecor.MoveToDesktopAnimator.Companion.DRAG_FREEFORM_SCALE
 import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
 import java.util.function.Supplier
+import kotlin.math.max
 
 /**
  * Handles the transition to enter desktop from fullscreen by dragging on the handle bar. It also
  * handles the cancellation case where the task is dragged back to the status bar area in the same
  * gesture.
+ *
+ * It's a base sealed class that delegates flag dependant logic to its subclasses:
+ * [DefaultDragToDesktopTransitionHandler] and [SpringDragToDesktopTransitionHandler]
+ *
+ * TODO(b/356764679): Clean up after the full flag rollout
  */
-class DragToDesktopTransitionHandler(
+sealed class DragToDesktopTransitionHandler(
     private val context: Context,
     private val transitions: Transitions,
     private val taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
-    private val interactionJankMonitor: InteractionJankMonitor,
-    private val transactionSupplier: Supplier<SurfaceControl.Transaction>,
+    protected val interactionJankMonitor: InteractionJankMonitor,
+    protected val transactionSupplier: Supplier<SurfaceControl.Transaction>,
 ) : TransitionHandler {
 
-    constructor(
-        context: Context,
-        transitions: Transitions,
-        rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
-        interactionJankMonitor: InteractionJankMonitor
-    ) : this(
-        context,
-        transitions,
-        rootTaskDisplayAreaOrganizer,
-        interactionJankMonitor,
-        Supplier { SurfaceControl.Transaction() }
-    )
-
-    private val rectEvaluator = RectEvaluator(Rect())
+    protected val rectEvaluator = RectEvaluator(Rect())
     private val launchHomeIntent = Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
 
-    private var dragToDesktopStateListener: DragToDesktopStateListener? = null
     private lateinit var splitScreenController: SplitScreenController
     private var transitionState: TransitionState? = null
-    private lateinit var onTaskResizeAnimationListener: OnTaskResizeAnimationListener
 
     /** Whether a drag-to-desktop transition is in progress. */
     val inProgress: Boolean
@@ -92,20 +85,18 @@
     /** The task id of the task currently being dragged from fullscreen/split. */
     val draggingTaskId: Int
         get() = transitionState?.draggedTaskId ?: INVALID_TASK_ID
-    /** Sets a listener to receive callback about events during the transition animation. */
-    fun setDragToDesktopStateListener(listener: DragToDesktopStateListener) {
-        dragToDesktopStateListener = listener
-    }
+
+    /** Listener to receive callback about events during the transition animation. */
+    var dragToDesktopStateListener: DragToDesktopStateListener? = null
+
+    /** Task listener for animation start, task bounds resize, and the animation finish */
+    lateinit var onTaskResizeAnimationListener: OnTaskResizeAnimationListener
 
     /** Setter needed to avoid cyclic dependency. */
     fun setSplitScreenController(controller: SplitScreenController) {
         splitScreenController = controller
     }
 
-    fun setOnTaskResizeAnimatorListener(listener: OnTaskResizeAnimationListener) {
-        onTaskResizeAnimationListener = listener
-    }
-
     /**
      * Starts a transition that performs a transient launch of Home so that Home is brought to the
      * front while still keeping the currently focused task that is being dragged resumed. This
@@ -307,24 +298,18 @@
             return false
         }
 
-        // Layering: non-wallpaper, non-home tasks excluding the dragged task go at the bottom,
-        // then Home on top of that, wallpaper on top of that and finally the dragged task on top
-        // of everything.
-        val appLayers = info.changes.size
-        val homeLayers = info.changes.size * 2
-        val wallpaperLayers = info.changes.size * 3
-        val dragLayer = wallpaperLayers
+        val layers = calculateStartDragToDesktopLayers(info)
         val leafTaskFilter = TransitionUtil.LeafTaskFilter()
         info.changes.withIndex().forEach { (i, change) ->
             if (TransitionUtil.isWallpaper(change)) {
-                val layer = wallpaperLayers - i
+                val layer = layers.wallpaperLayers - i
                 startTransaction.apply {
                     setLayer(change.leash, layer)
                     show(change.leash)
                 }
             } else if (isHomeChange(change)) {
-                state.homeToken = change.container
-                val layer = homeLayers - i
+                state.homeChange = change
+                val layer = layers.homeLayers - i
                 startTransaction.apply {
                     setLayer(change.leash, layer)
                     show(change.leash)
@@ -338,11 +323,11 @@
                             if (state.cancelState == CancelState.NO_CANCEL) {
                                 // Normal case, split root goes to the bottom behind everything
                                 // else.
-                                appLayers - i
+                                layers.appLayers - i
                             } else {
                                 // Cancel-early case, pretend nothing happened so split root stays
                                 // top.
-                                dragLayer
+                                layers.dragLayer
                             }
                         startTransaction.apply {
                             setLayer(change.leash, layer)
@@ -357,7 +342,7 @@
                             state.draggedTaskChange = change
                             val bounds = change.endAbsBounds
                             startTransaction.apply {
-                                setLayer(change.leash, dragLayer)
+                                setLayer(change.leash, layers.dragLayer)
                                 setWindowCrop(change.leash, bounds.width(), bounds.height())
                                 show(change.leash)
                             }
@@ -370,7 +355,7 @@
                             state.otherRootChanges.add(change)
                             val bounds = change.endAbsBounds
                             startTransaction.apply {
-                                setLayer(change.leash, appLayers - i)
+                                setLayer(change.leash, layers.appLayers - i)
                                 setWindowCrop(change.leash, bounds.width(), bounds.height())
                                 show(change.leash)
                             }
@@ -404,7 +389,7 @@
                     )
                     val bounds = change.endAbsBounds
                     startTransaction.apply {
-                        setLayer(change.leash, dragLayer)
+                        setLayer(change.leash, layers.dragLayer)
                         setWindowCrop(change.leash, bounds.width(), bounds.height())
                         show(change.leash)
                     }
@@ -452,6 +437,15 @@
         return true
     }
 
+    /**
+     * Calculates start drag to desktop layers for transition [info]. The leash layer is calculated
+     * based on its change position in the transition, e.g. `appLayer = appLayers - i`, where i is
+     * the change index.
+     */
+    protected abstract fun calculateStartDragToDesktopLayers(
+        info: TransitionInfo
+    ): DragToDesktopLayers
+
     override fun mergeAnimation(
         transition: IBinder,
         info: TransitionInfo,
@@ -483,104 +477,14 @@
             state.startTransitionFinishCb
                 ?: error("Start transition expected to be waiting for merge but wasn't")
         if (isEndTransition) {
-            info.changes.withIndex().forEach { (i, change) ->
-                // If we're exiting split, hide the remaining split task.
-                if (
-                    state is TransitionState.FromSplit &&
-                        change.taskInfo?.taskId == state.otherSplitTask
-                ) {
-                    t.hide(change.leash)
-                    startTransactionFinishT.hide(change.leash)
-                }
-                if (change.mode == TRANSIT_CLOSE) {
-                    t.hide(change.leash)
-                    startTransactionFinishT.hide(change.leash)
-                } else if (change.taskInfo?.taskId == state.draggedTaskId) {
-                    t.show(change.leash)
-                    startTransactionFinishT.show(change.leash)
-                    state.draggedTaskChange = change
-                } else if (change.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM) {
-                    // Other freeform tasks that are being restored go behind the dragged task.
-                    val draggedTaskLeash =
-                        state.draggedTaskChange?.leash
-                            ?: error("Expected dragged leash to be non-null")
-                    t.setRelativeLayer(change.leash, draggedTaskLeash, -i)
-                    startTransactionFinishT.setRelativeLayer(change.leash, draggedTaskLeash, -i)
-                }
-            }
-
-            val draggedTaskChange =
-                state.draggedTaskChange
-                    ?: throw IllegalStateException("Expected non-null change of dragged task")
-            val draggedTaskLeash = draggedTaskChange.leash
-            val startBounds = draggedTaskChange.startAbsBounds
-            val endBounds = draggedTaskChange.endAbsBounds
-
-            // Pause any animation that may be currently playing; we will use the relevant
-            // details of that animation here.
-            state.dragAnimator.cancelAnimator()
-            // We still apply scale to task bounds; as we animate the bounds to their
-            // end value, animate scale to 1.
-            val startScale = state.dragAnimator.scale
-            val startPosition = state.dragAnimator.position
-            val unscaledStartWidth = startBounds.width()
-            val unscaledStartHeight = startBounds.height()
-            val unscaledStartBounds =
-                Rect(
-                    startPosition.x.toInt(),
-                    startPosition.y.toInt(),
-                    startPosition.x.toInt() + unscaledStartWidth,
-                    startPosition.y.toInt() + unscaledStartHeight
-                )
-
-            dragToDesktopStateListener?.onCommitToDesktopAnimationStart(t)
-            // Accept the merge by applying the merging transaction (applied by #showResizeVeil)
-            // and finish callback. Show the veil and position the task at the first frame before
-            // starting the final animation.
-            onTaskResizeAnimationListener.onAnimationStart(
-                state.draggedTaskId,
-                t,
-                unscaledStartBounds
+            setupEndDragToDesktop(
+                info,
+                startTransaction = t,
+                finishTransaction = startTransactionFinishT
             )
+            // Call finishCallback to merge animation before startTransitionFinishCb is called
             finishCallback.onTransitionFinished(null /* wct */)
-            val tx: SurfaceControl.Transaction = transactionSupplier.get()
-            ValueAnimator.ofObject(rectEvaluator, unscaledStartBounds, endBounds)
-                .setDuration(DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS)
-                .apply {
-                    addUpdateListener { animator ->
-                        val animBounds = animator.animatedValue as Rect
-                        val animFraction = animator.animatedFraction
-                        // Progress scale from starting value to 1 as animation plays.
-                        val animScale = startScale + animFraction * (1 - startScale)
-                        tx.apply {
-                            setScale(draggedTaskLeash, animScale, animScale)
-                            setPosition(
-                                draggedTaskLeash,
-                                animBounds.left.toFloat(),
-                                animBounds.top.toFloat()
-                            )
-                            setWindowCrop(draggedTaskLeash, animBounds.width(), animBounds.height())
-                        }
-                        onTaskResizeAnimationListener.onBoundsChange(
-                            state.draggedTaskId,
-                            tx,
-                            animBounds
-                        )
-                    }
-                    addListener(
-                        object : AnimatorListenerAdapter() {
-                            override fun onAnimationEnd(animation: Animator) {
-                                onTaskResizeAnimationListener.onAnimationEnd(state.draggedTaskId)
-                                startTransitionFinishCb.onTransitionFinished(null /* null */)
-                                clearState()
-                                interactionJankMonitor.end(
-                                    CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE
-                                )
-                            }
-                        }
-                    )
-                    start()
-                }
+            animateEndDragToDesktop(startTransaction = t, startTransitionFinishCb)
         } else if (isCancelTransition) {
             info.changes.forEach { change ->
                 t.show(change.leash)
@@ -593,6 +497,122 @@
         }
     }
 
+    protected open fun setupEndDragToDesktop(
+        info: TransitionInfo,
+        startTransaction: SurfaceControl.Transaction,
+        finishTransaction: SurfaceControl.Transaction
+    ) {
+        val state = requireTransitionState()
+        val freeformTaskChanges = mutableListOf<Change>()
+        info.changes.forEachIndexed { i, change ->
+            when {
+                state is TransitionState.FromSplit &&
+                    change.taskInfo?.taskId == state.otherSplitTask -> {
+                    // If we're exiting split, hide the remaining split task.
+                    startTransaction.hide(change.leash)
+                    finishTransaction.hide(change.leash)
+                }
+                change.mode == TRANSIT_CLOSE -> {
+                    startTransaction.hide(change.leash)
+                    finishTransaction.hide(change.leash)
+                }
+                change.taskInfo?.taskId == state.draggedTaskId -> {
+                    startTransaction.show(change.leash)
+                    finishTransaction.show(change.leash)
+                    state.draggedTaskChange = change
+                }
+                change.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM -> {
+                    // Other freeform tasks that are being restored go behind the dragged task.
+                    val draggedTaskLeash =
+                        state.draggedTaskChange?.leash
+                            ?: error("Expected dragged leash to be non-null")
+                    startTransaction.setRelativeLayer(change.leash, draggedTaskLeash, -i)
+                    finishTransaction.setRelativeLayer(change.leash, draggedTaskLeash, -i)
+                    freeformTaskChanges.add(change)
+                }
+            }
+        }
+
+        state.freeformTaskChanges = freeformTaskChanges
+    }
+
+    protected open fun animateEndDragToDesktop(
+        startTransaction: SurfaceControl.Transaction,
+        startTransitionFinishCb: Transitions.TransitionFinishCallback
+    ) {
+        val state = requireTransitionState()
+        val draggedTaskChange =
+            state.draggedTaskChange ?: error("Expected non-null change of dragged task")
+        val draggedTaskLeash = draggedTaskChange.leash
+        val startBounds = draggedTaskChange.startAbsBounds
+        val endBounds = draggedTaskChange.endAbsBounds
+
+        // Cancel any animation that may be currently playing; we will use the relevant
+        // details of that animation here.
+        state.dragAnimator.cancelAnimator()
+        // We still apply scale to task bounds; as we animate the bounds to their
+        // end value, animate scale to 1.
+        val startScale = state.dragAnimator.scale
+        val startPosition = state.dragAnimator.position
+        val unscaledStartWidth = startBounds.width()
+        val unscaledStartHeight = startBounds.height()
+        val unscaledStartBounds =
+            Rect(
+                startPosition.x.toInt(),
+                startPosition.y.toInt(),
+                startPosition.x.toInt() + unscaledStartWidth,
+                startPosition.y.toInt() + unscaledStartHeight
+            )
+
+        dragToDesktopStateListener?.onCommitToDesktopAnimationStart(startTransaction)
+        // Accept the merge by applying the merging transaction (applied by #showResizeVeil)
+        // and finish callback. Show the veil and position the task at the first frame before
+        // starting the final animation.
+        onTaskResizeAnimationListener.onAnimationStart(
+            state.draggedTaskId,
+            startTransaction,
+            unscaledStartBounds
+        )
+        val tx: SurfaceControl.Transaction = transactionSupplier.get()
+        ValueAnimator.ofObject(rectEvaluator, unscaledStartBounds, endBounds)
+            .setDuration(DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS)
+            .apply {
+                addUpdateListener { animator ->
+                    val animBounds = animator.animatedValue as Rect
+                    val animFraction = animator.animatedFraction
+                    // Progress scale from starting value to 1 as animation plays.
+                    val animScale = startScale + animFraction * (1 - startScale)
+                    tx.apply {
+                        setScale(draggedTaskLeash, animScale, animScale)
+                        setPosition(
+                            draggedTaskLeash,
+                            animBounds.left.toFloat(),
+                            animBounds.top.toFloat()
+                        )
+                        setWindowCrop(draggedTaskLeash, animBounds.width(), animBounds.height())
+                    }
+                    onTaskResizeAnimationListener.onBoundsChange(
+                        state.draggedTaskId,
+                        tx,
+                        animBounds
+                    )
+                }
+                addListener(
+                    object : AnimatorListenerAdapter() {
+                        override fun onAnimationEnd(animation: Animator) {
+                            onTaskResizeAnimationListener.onAnimationEnd(state.draggedTaskId)
+                            startTransitionFinishCb.onTransitionFinished(/* wct = */ null)
+                            clearState()
+                            interactionJankMonitor.end(
+                                CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE
+                            )
+                        }
+                    }
+                )
+                start()
+            }
+    }
+
     override fun handleRequest(
         transition: IBinder,
         request: TransitionRequestInfo
@@ -707,11 +727,12 @@
                 wct.reorder(wc, true /* toTop */)
             }
         }
-        val homeWc = state.homeToken ?: error("Home task should be non-null before cancelling")
+        val homeWc =
+            state.homeChange?.container ?: error("Home task should be non-null before cancelling")
         wct.restoreTransientOrder(homeWc)
     }
 
-    private fun clearState() {
+    protected fun clearState() {
         transitionState = null
     }
 
@@ -731,10 +752,21 @@
         return splitScreenController.getTaskInfo(otherTaskPos)?.taskId
     }
 
-    private fun requireTransitionState(): TransitionState {
+    protected fun requireTransitionState(): TransitionState {
         return transitionState ?: error("Expected non-null transition state")
     }
 
+    /**
+     * Represents the layering (Z order) that will be given to any window based on its type during
+     * the "start" transition of the drag-to-desktop transition
+     */
+    protected data class DragToDesktopLayers(
+        val appLayers: Int,
+        val homeLayers: Int,
+        val wallpaperLayers: Int,
+        val dragLayer: Int,
+    )
+
     interface DragToDesktopStateListener {
         fun onCommitToDesktopAnimationStart(tx: SurfaceControl.Transaction)
 
@@ -748,8 +780,9 @@
         abstract var startTransitionFinishCb: Transitions.TransitionFinishCallback?
         abstract var startTransitionFinishTransaction: SurfaceControl.Transaction?
         abstract var cancelTransitionToken: IBinder?
-        abstract var homeToken: WindowContainerToken?
+        abstract var homeChange: Change?
         abstract var draggedTaskChange: Change?
+        abstract var freeformTaskChanges: List<Change>
         abstract var cancelState: CancelState
         abstract var startAborted: Boolean
 
@@ -760,8 +793,9 @@
             override var startTransitionFinishCb: Transitions.TransitionFinishCallback? = null,
             override var startTransitionFinishTransaction: SurfaceControl.Transaction? = null,
             override var cancelTransitionToken: IBinder? = null,
-            override var homeToken: WindowContainerToken? = null,
+            override var homeChange: Change? = null,
             override var draggedTaskChange: Change? = null,
+            override var freeformTaskChanges: List<Change> = emptyList(),
             override var cancelState: CancelState = CancelState.NO_CANCEL,
             override var startAborted: Boolean = false,
             var otherRootChanges: MutableList<Change> = mutableListOf()
@@ -774,8 +808,9 @@
             override var startTransitionFinishCb: Transitions.TransitionFinishCallback? = null,
             override var startTransitionFinishTransaction: SurfaceControl.Transaction? = null,
             override var cancelTransitionToken: IBinder? = null,
-            override var homeToken: WindowContainerToken? = null,
+            override var homeChange: Change? = null,
             override var draggedTaskChange: Change? = null,
+            override var freeformTaskChanges: List<Change> = emptyList(),
             override var cancelState: CancelState = CancelState.NO_CANCEL,
             override var startAborted: Boolean = false,
             var splitRootChange: Change? = null,
@@ -797,6 +832,210 @@
 
     companion object {
         /** The duration of the animation to commit or cancel the drag-to-desktop gesture. */
-        private const val DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS = 336L
+        internal const val DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS = 336L
+    }
+}
+
+/** Enables flagged rollout of the [SpringDragToDesktopTransitionHandler] */
+class DefaultDragToDesktopTransitionHandler
+@JvmOverloads
+constructor(
+    context: Context,
+    transitions: Transitions,
+    taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+    interactionJankMonitor: InteractionJankMonitor,
+    transactionSupplier: Supplier<SurfaceControl.Transaction> = Supplier {
+        SurfaceControl.Transaction()
+    },
+) :
+    DragToDesktopTransitionHandler(
+        context,
+        transitions,
+        taskDisplayAreaOrganizer,
+        interactionJankMonitor,
+        transactionSupplier
+    ) {
+
+    /**
+     * @return layers in order:
+     * - appLayers - non-wallpaper, non-home tasks excluding the dragged task go at the bottom
+     * - homeLayers - home task on top of apps
+     * - wallpaperLayers - wallpaper on top of home
+     * - dragLayer - the dragged task on top of everything, there's only 1 dragged task
+     */
+    override fun calculateStartDragToDesktopLayers(info: TransitionInfo): DragToDesktopLayers =
+        DragToDesktopLayers(
+            appLayers = info.changes.size,
+            homeLayers = info.changes.size * 2,
+            wallpaperLayers = info.changes.size * 3,
+            dragLayer = info.changes.size * 3
+        )
+}
+
+/** Desktop transition handler with spring based animation for the end drag to desktop transition */
+class SpringDragToDesktopTransitionHandler
+@JvmOverloads
+constructor(
+    context: Context,
+    transitions: Transitions,
+    taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+    interactionJankMonitor: InteractionJankMonitor,
+    transactionSupplier: Supplier<SurfaceControl.Transaction> = Supplier {
+        SurfaceControl.Transaction()
+    },
+) :
+    DragToDesktopTransitionHandler(
+        context,
+        transitions,
+        taskDisplayAreaOrganizer,
+        interactionJankMonitor,
+        transactionSupplier
+    ) {
+
+    private val positionSpringConfig =
+        PhysicsAnimator.SpringConfig(
+            SpringForce.STIFFNESS_LOW,
+            SpringForce.DAMPING_RATIO_LOW_BOUNCY
+        )
+
+    private val sizeSpringConfig =
+        PhysicsAnimator.SpringConfig(SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_NO_BOUNCY)
+
+    /**
+     * @return layers in order:
+     * - appLayers - below everything z < 0, effectively hides the leash
+     * - homeLayers - home task on top of apps, z in 0..<size
+     * - wallpaperLayers - wallpaper on top of home, z in size..<size*2
+     * - dragLayer - the dragged task on top of everything, z == size*2
+     */
+    override fun calculateStartDragToDesktopLayers(info: TransitionInfo): DragToDesktopLayers =
+        DragToDesktopLayers(
+            appLayers = -1,
+            homeLayers = info.changes.size - 1,
+            wallpaperLayers = info.changes.size * 2 - 1,
+            dragLayer = info.changes.size * 2
+        )
+
+    override fun setupEndDragToDesktop(
+        info: TransitionInfo,
+        startTransaction: SurfaceControl.Transaction,
+        finishTransaction: SurfaceControl.Transaction
+    ) {
+        super.setupEndDragToDesktop(info, startTransaction, finishTransaction)
+
+        val state = requireTransitionState()
+        val homeLeash = state.homeChange?.leash ?: error("Expects home leash to be non-null")
+        // Hide home on finish to prevent flickering when wallpaper activity flag is enabled
+        finishTransaction.hide(homeLeash)
+        // Setup freeform tasks before animation
+        state.freeformTaskChanges.forEach { change ->
+            val startScale = DRAG_TO_DESKTOP_FREEFORM_TASK_INITIAL_SCALE
+            val startX =
+                change.endAbsBounds.left + change.endAbsBounds.width() * (1 - startScale) / 2
+            val startY =
+                change.endAbsBounds.top + change.endAbsBounds.height() * (1 - startScale) / 2
+            startTransaction.setPosition(change.leash, startX, startY)
+            startTransaction.setScale(change.leash, startScale, startScale)
+            startTransaction.setAlpha(change.leash, 0f)
+        }
+    }
+
+    override fun animateEndDragToDesktop(
+        startTransaction: SurfaceControl.Transaction,
+        startTransitionFinishCb: Transitions.TransitionFinishCallback
+    ) {
+        val state = requireTransitionState()
+        val draggedTaskChange =
+            state.draggedTaskChange ?: error("Expected non-null change of dragged task")
+        val draggedTaskLeash = draggedTaskChange.leash
+        val freeformTaskChanges = state.freeformTaskChanges
+        val startBounds = draggedTaskChange.startAbsBounds
+        val endBounds = draggedTaskChange.endAbsBounds
+        val currentVelocity = state.dragAnimator.computeCurrentVelocity()
+
+        // Cancel any animation that may be currently playing; we will use the relevant
+        // details of that animation here.
+        state.dragAnimator.cancelAnimator()
+        // We still apply scale to task bounds; as we animate the bounds to their
+        // end value, animate scale to 1.
+        val startScale = state.dragAnimator.scale
+        val startPosition = state.dragAnimator.position
+        val startBoundsWithOffset =
+            Rect(startBounds).apply { offset(startPosition.x.toInt(), startPosition.y.toInt()) }
+
+        dragToDesktopStateListener?.onCommitToDesktopAnimationStart(startTransaction)
+        // Accept the merge by applying the merging transaction (applied by #showResizeVeil)
+        // and finish callback. Show the veil and position the task at the first frame before
+        // starting the final animation.
+        onTaskResizeAnimationListener.onAnimationStart(
+            state.draggedTaskId,
+            startTransaction,
+            startBoundsWithOffset
+        )
+
+        val tx: SurfaceControl.Transaction = transactionSupplier.get()
+        PhysicsAnimator.getInstance(startBoundsWithOffset)
+            .spring(
+                FloatProperties.RECT_X,
+                endBounds.left.toFloat(),
+                currentVelocity.x,
+                positionSpringConfig
+            )
+            .spring(
+                FloatProperties.RECT_Y,
+                endBounds.top.toFloat(),
+                currentVelocity.y,
+                positionSpringConfig
+            )
+            .spring(FloatProperties.RECT_WIDTH, endBounds.width().toFloat(), sizeSpringConfig)
+            .spring(FloatProperties.RECT_HEIGHT, endBounds.height().toFloat(), sizeSpringConfig)
+            .addUpdateListener { animBounds, _ ->
+                val animFraction =
+                    (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
+                val freeformAnimScale =
+                    freeformStartScale + freeformAnimFraction * (1 - freeformStartScale)
+                tx.apply {
+                    // Update dragged task
+                    setScale(draggedTaskLeash, animScale, animScale)
+                    setPosition(
+                        draggedTaskLeash,
+                        animBounds.left.toFloat(),
+                        animBounds.top.toFloat()
+                    )
+                    // Update freeform tasks
+                    freeformTaskChanges.forEach {
+                        val startX =
+                            it.endAbsBounds.left +
+                                it.endAbsBounds.width() * (1 - freeformAnimScale) / 2
+                        val startY =
+                            it.endAbsBounds.top +
+                                it.endAbsBounds.height() * (1 - freeformAnimScale) / 2
+                        setPosition(it.leash, startX, startY)
+                        setScale(it.leash, freeformAnimScale, freeformAnimScale)
+                        setAlpha(it.leash, freeformAnimFraction)
+                    }
+                }
+                onTaskResizeAnimationListener.onBoundsChange(state.draggedTaskId, tx, animBounds)
+            }
+            .withEndActions({
+                onTaskResizeAnimationListener.onAnimationEnd(state.draggedTaskId)
+                startTransitionFinishCb.onTransitionFinished(/* wct = */ null)
+                clearState()
+                interactionJankMonitor.end(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE)
+            })
+            .start()
+    }
+
+    companion object {
+        /**
+         * The initial scale of the freeform tasks in the animation to commit the drag-to-desktop
+         * gesture.
+         */
+        private const val DRAG_TO_DESKTOP_FREEFORM_TASK_INITIAL_SCALE = 0.9f
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS
index b01b2b7..afdda8f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS
@@ -5,3 +5,6 @@
 pbdr@google.com
 tkachenkoi@google.com
 vaniadesmonda@google.com
+pragyabajoria@google.com
+uysalorhan@google.com
+gsennton@google.com
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
new file mode 100644
index 0000000..bf4a2ab
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.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.wm.shell.desktopmode.education.data
+
+import android.content.Context
+import android.util.Log
+import androidx.datastore.core.CorruptionException
+import androidx.datastore.core.DataStore
+import androidx.datastore.core.DataStoreFactory
+import androidx.datastore.core.Serializer
+import androidx.datastore.dataStore
+import androidx.datastore.dataStoreFile
+import com.android.framework.protobuf.InvalidProtocolBufferException
+import com.android.internal.annotations.VisibleForTesting
+import java.io.InputStream
+import java.io.OutputStream
+import kotlinx.coroutines.flow.first
+
+/**
+ * Manages interactions with the App Handle education datastore.
+ *
+ * This class provides a layer of abstraction between the UI/business logic and the underlying
+ * DataStore.
+ */
+class AppHandleEducationDatastoreRepository
+@VisibleForTesting
+constructor(private val dataStore: DataStore<WindowingEducationProto>) {
+  constructor(
+      context: Context
+  ) : this(
+      DataStoreFactory.create(
+          serializer = WindowingEducationProtoSerializer,
+          produceFile = { context.dataStoreFile(APP_HANDLE_EDUCATION_DATASTORE_FILEPATH) }))
+
+  /**
+   * 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()
+      }
+
+  companion object {
+    private const val TAG = "AppHandleEducationDatastoreRepository"
+    private const val APP_HANDLE_EDUCATION_DATASTORE_FILEPATH = "app_handle_education.pb"
+
+    object WindowingEducationProtoSerializer : Serializer<WindowingEducationProto> {
+
+      override val defaultValue: WindowingEducationProto =
+          WindowingEducationProto.getDefaultInstance()
+
+      override suspend fun readFrom(input: InputStream): WindowingEducationProto =
+          try {
+            WindowingEducationProto.parseFrom(input)
+          } catch (exception: InvalidProtocolBufferException) {
+            throw CorruptionException("Cannot read proto.", exception)
+          }
+
+      override suspend fun writeTo(windowingProto: WindowingEducationProto, output: OutputStream) =
+          windowingProto.writeTo(output)
+    }
+  }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/proto/windowing_education.proto b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/proto/windowing_education.proto
new file mode 100644
index 0000000..d29ec53
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/proto/windowing_education.proto
@@ -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.
+ */
+
+syntax = "proto2";
+
+option java_package = "com.android.wm.shell.desktopmode.education.data";
+option java_multiple_files = true;
+
+// Desktop Windowing education data
+message WindowingEducationProto {
+  // Timestamp in milliseconds of when the education was last viewed.
+  optional int64 education_viewed_timestamp_millis = 1;
+  // Timestamp in milliseconds of when the feature was last used.
+  optional int64 feature_used_timestamp_millis = 2;
+  oneof education_data {
+    // Fields specific to app handle education
+    AppHandleEducation app_handle_education = 3;
+  }
+
+  message AppHandleEducation {
+    // Map that stores app launch count for corresponding package
+    map<string, int32> app_usage_stats = 1;
+    // Timestamp of when app_usage_stats was last cached
+    optional int64 app_usage_stats_last_update_timestamp_millis = 2;
+  }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS
index 8a0eea0..93351c3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS
@@ -5,3 +5,7 @@
 nmusgrave@google.com
 pbdr@google.com
 tkachenkoi@google.com
+vaniadesmonda@google.com
+pragyabajoria@google.com
+uysalorhan@google.com
+gsennton@google.com
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 723a531..428cc91 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
@@ -693,16 +693,6 @@
                 return;
             }
 
-            if (mSplitScreenOptional.isPresent()) {
-                // If pip activity will reparent to origin task case and if the origin task still
-                // under split root, apply exit split transaction to make it expand to fullscreen.
-                SplitScreenController split = mSplitScreenOptional.get();
-                if (split.isTaskInSplitScreen(mTaskInfo.lastParentTaskIdBeforePip)) {
-                    split.prepareExitSplitScreen(wct, split.getStageOfTask(
-                            mTaskInfo.lastParentTaskIdBeforePip),
-                            SplitScreenController.EXIT_REASON_APP_FINISHED);
-                }
-            }
             mPipTransitionController.startExitTransition(TRANSIT_EXIT_PIP, wct, destinationBounds);
             return;
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 284620e..da6221e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -632,6 +632,12 @@
                     public void insetsChanged(InsetsState insetsState) {
                         DisplayLayout pendingLayout = mDisplayController
                                 .getDisplayLayout(mPipDisplayLayoutState.getDisplayId());
+                        if (pendingLayout == null) {
+                            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                                    "insetsChanged: no display layout for displayId=%d",
+                                    mPipDisplayLayoutState.getDisplayId());
+                            return;
+                        }
                         if (mIsInFixedRotation
                                 || mIsKeyguardShowingOrAnimating
                                 || pendingLayout.rotation()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
index c189642..0d7f7f6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
@@ -34,11 +34,13 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ActivityManager;
 import android.app.PendingIntent;
 import android.app.RemoteAction;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
@@ -151,6 +153,10 @@
     // How long the shell will wait for the app to close the PiP if a custom action is set.
     private final int mPipForceCloseDelay;
 
+    // Context for the currently active user. This may differ from the regular systemui Context
+    // in cases such as secondary users or HSUM.
+    private Context mContextForUser;
+
     public PipMenuView(Context context, PhonePipMenuController controller,
             ShellExecutor mainExecutor, Handler mainHandler,
             PipUiEventLogger pipUiEventLogger) {
@@ -202,6 +208,7 @@
                 .getInteger(R.integer.config_pipExitAnimationDuration);
 
         initAccessibility();
+        setContextForUser();
     }
 
     private void initAccessibility() {
@@ -476,7 +483,7 @@
                         actionView.setImageDrawable(null);
                     } else {
                         // TODO: Check if the action drawable has changed before we reload it
-                        action.getIcon().loadDrawableAsync(mContext, d -> {
+                        action.getIcon().loadDrawableAsync(mContextForUser, d -> {
                             if (d != null) {
                                 d.setTint(Color.WHITE);
                                 actionView.setImageDrawable(d);
@@ -510,6 +517,33 @@
         expandContainer.requestLayout();
     }
 
+    /**
+     * Sets the Context for the current user. If the user is the same as systemui, then simply
+     * use systemui Context.
+     */
+    private void setContextForUser() {
+        int userId = ActivityManager.getCurrentUser();
+
+        if (mContext.getUserId() != userId) {
+            try {
+                mContextForUser = mContext.createPackageContextAsUser(mContext.getPackageName(),
+                        Context.CONTEXT_RESTRICTED, new UserHandle(userId));
+            } catch (PackageManager.NameNotFoundException e) {
+                // Shouldn't happen, use systemui context as backup
+                ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                        "%s: Failed to get context for user. Sysui userid=%d,"
+                                + " current userid=%d, error=%s",
+                        TAG,
+                        mContext.getUserId(),
+                        userId,
+                        e);
+                mContextForUser = mContext;
+            }
+        } else {
+            mContextForUser = mContext;
+        }
+    }
+
     private void notifyMenuStateChangeStart(int menuState, boolean resize, Runnable callback) {
         mController.onMenuStateChangeStart(menuState, resize, callback);
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index 7774384..dc21f82 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -31,6 +31,8 @@
 import android.os.Bundle;
 import android.view.InsetsState;
 import android.view.SurfaceControl;
+import android.window.DisplayAreaInfo;
+import android.window.WindowContainerTransaction;
 
 import androidx.annotation.BinderThread;
 import androidx.annotation.Nullable;
@@ -40,6 +42,7 @@
 import com.android.internal.util.Preconditions;
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayChangeController;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.DisplayLayout;
@@ -71,7 +74,8 @@
  */
 public class PipController implements ConfigurationChangeListener,
         PipTransitionState.PipTransitionStateChangedListener,
-        DisplayController.OnDisplaysChangedListener, RemoteCallable<PipController> {
+        DisplayController.OnDisplaysChangedListener,
+        DisplayChangeController.OnDisplayChangingListener, RemoteCallable<PipController> {
     private static final String TAG = PipController.class.getSimpleName();
     private static final String SWIPE_TO_PIP_APP_BOUNDS = "pip_app_bounds";
     private static final String SWIPE_TO_PIP_OVERLAY = "swipe_to_pip_overlay";
@@ -197,11 +201,12 @@
         mPipDisplayLayoutState.setDisplayLayout(layout);
 
         mDisplayController.addDisplayWindowListener(this);
+        mDisplayController.addDisplayChangingController(this);
         mDisplayInsetsController.addInsetsChangedListener(mPipDisplayLayoutState.getDisplayId(),
                 new DisplayInsetsController.OnInsetsChangedListener() {
                     @Override
                     public void insetsChanged(InsetsState insetsState) {
-                        onDisplayChanged(mDisplayController
+                        setDisplayLayout(mDisplayController
                                         .getDisplayLayout(mPipDisplayLayoutState.getDisplayId()));
                     }
                 });
@@ -264,11 +269,12 @@
 
     @Override
     public void onThemeChanged() {
-        onDisplayChanged(new DisplayLayout(mContext, mContext.getDisplay()));
+        setDisplayLayout(new DisplayLayout(mContext, mContext.getDisplay()));
     }
 
     //
-    // DisplayController.OnDisplaysChangedListener implementations
+    // DisplayController.OnDisplaysChangedListener and
+    // DisplayChangeController.OnDisplayChangingListener implementations
     //
 
     @Override
@@ -276,7 +282,7 @@
         if (displayId != mPipDisplayLayoutState.getDisplayId()) {
             return;
         }
-        onDisplayChanged(mDisplayController.getDisplayLayout(displayId));
+        setDisplayLayout(mDisplayController.getDisplayLayout(displayId));
     }
 
     @Override
@@ -284,10 +290,35 @@
         if (displayId != mPipDisplayLayoutState.getDisplayId()) {
             return;
         }
-        onDisplayChanged(mDisplayController.getDisplayLayout(displayId));
+        setDisplayLayout(mDisplayController.getDisplayLayout(displayId));
     }
 
-    private void onDisplayChanged(DisplayLayout layout) {
+    /**
+     * A callback for any observed transition that contains a display change in its
+     * {@link android.window.TransitionRequestInfo} with a non-zero rotation delta.
+     */
+    @Override
+    public void onDisplayChange(int displayId, int fromRotation, int toRotation,
+            @Nullable DisplayAreaInfo newDisplayAreaInfo, WindowContainerTransaction t) {
+        if (!mPipTransitionState.isInPip()) {
+            return;
+        }
+
+        // Calculate the snap fraction pre-rotation.
+        float snapFraction = mPipBoundsAlgorithm.getSnapFraction(mPipBoundsState.getBounds());
+
+        // Update the caches to reflect the new display layout and movement bounds.
+        mPipDisplayLayoutState.rotateTo(toRotation);
+        mPipTouchHandler.updateMovementBounds();
+
+        // The policy is to keep PiP width, height and snap fraction invariant.
+        Rect toBounds = mPipBoundsState.getBounds();
+        mPipBoundsAlgorithm.applySnapFraction(toBounds, snapFraction);
+        mPipBoundsState.setBounds(toBounds);
+        t.setBounds(mPipTransitionState.mPipTaskToken, toBounds);
+    }
+
+    private void setDisplayLayout(DisplayLayout layout) {
         mPipDisplayLayoutState.setDisplayLayout(layout);
     }
 
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 d7c225b..d75fa00 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
@@ -1081,7 +1081,7 @@
      * Updates the current movement bounds based on whether the menu is currently visible and
      * resized.
      */
-    private void updateMovementBounds() {
+    void updateMovementBounds() {
         Rect insetBounds = new Rect();
         mPipBoundsAlgorithm.getInsetBounds(insetBounds);
         mPipBoundsAlgorithm.getMovementBounds(mPipBoundsState.getBounds(),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index 497c3f7..f739d65 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -61,6 +61,8 @@
             Consts.TAG_WM_SHELL),
     WM_SHELL_BUBBLES(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
             "Bubbles"),
+    WM_SHELL_COMPAT_UI(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+            Consts.TAG_WM_COMPAT_UI),
     TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest");
 
     private final boolean mEnabled;
@@ -128,6 +130,7 @@
         private static final String TAG_WM_STARTING_WINDOW = "ShellStartingWindow";
         private static final String TAG_WM_SPLIT_SCREEN = "ShellSplitScreen";
         private static final String TAG_WM_DESKTOP_MODE = "ShellDesktopMode";
+        private static final String TAG_WM_COMPAT_UI = "CompatUi";
 
         private static final boolean ENABLE_DEBUG = true;
         private static final boolean ENABLE_LOG_TO_PROTO_DEBUG = true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index 48d17ec6..c11a112 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -439,9 +439,9 @@
         ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setResizeTransition: hasPendingResize=%b",
                 mPendingResize != null);
         if (mPendingResize != null) {
+            mPendingResize.cancel(null);
             mainDecor.cancelRunningAnimations();
             sideDecor.cancelRunningAnimations();
-            mPendingResize.cancel(null);
             mAnimations.clear();
             onFinish(null /* wct */);
         }
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 a7551bd..9bf5159 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
@@ -123,10 +123,10 @@
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.LaunchAdjacentController;
-import com.android.wm.shell.common.ScreenshotUtils;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.common.split.SplitDecorManager;
 import com.android.wm.shell.common.split.SplitLayout;
 import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
 import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
@@ -1010,40 +1010,41 @@
         mTempRect1.setEmpty();
         final StageTaskListener topLeftStage =
                 mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
-        final SurfaceControl topLeftScreenshot = ScreenshotUtils.takeScreenshot(t,
-                topLeftStage.mRootLeash, mTempRect1, Integer.MAX_VALUE - 1);
         final StageTaskListener bottomRightStage =
                 mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
-        final SurfaceControl bottomRightScreenshot = ScreenshotUtils.takeScreenshot(t,
-                bottomRightStage.mRootLeash, mTempRect1, Integer.MAX_VALUE - 1);
-        mSplitLayout.splitSwitching(t, topLeftStage.mRootLeash, bottomRightStage.mRootLeash,
+
+        // Don't allow windows or divider to be focused during animation (mRootTaskInfo is the
+        // parent of all 3 leaves). We don't want the user to be able to tap and focus a window
+        // while it is moving across the screen, because granting focus also recalculates the
+        // layering order, which is in delicate balance during this animation.
+        WindowContainerTransaction noFocus = new WindowContainerTransaction();
+        noFocus.setFocusable(mRootTaskInfo.token, false);
+        mSyncQueue.queue(noFocus);
+
+        mSplitLayout.playSwapAnimation(t, topLeftStage, bottomRightStage,
                 insets -> {
+                    // Runs at the end of the swap animation
+                    SplitDecorManager decorManager1 = topLeftStage.getDecorManager();
+                    SplitDecorManager decorManager2 = bottomRightStage.getDecorManager();
+
                     WindowContainerTransaction wct = new WindowContainerTransaction();
+
+                    // Restore focus-ability to the windows and divider
+                    wct.setFocusable(mRootTaskInfo.token, true);
+
                     setSideStagePosition(reverseSplitPosition(mSideStagePosition), wct);
                     mSyncQueue.queue(wct);
                     mSyncQueue.runInSync(st -> {
                         updateSurfaceBounds(mSplitLayout, st, false /* applyResizingOffset */);
-                        st.setPosition(topLeftScreenshot, -insets.left, -insets.top);
-                        st.setPosition(bottomRightScreenshot, insets.left, insets.top);
 
-                        final ValueAnimator va = ValueAnimator.ofFloat(1, 0);
-                        va.addUpdateListener(valueAnimator-> {
-                            final float progress = (float) valueAnimator.getAnimatedValue();
-                            t.setAlpha(topLeftScreenshot, progress);
-                            t.setAlpha(bottomRightScreenshot, progress);
-                            t.apply();
-                        });
-                        va.addListener(new AnimatorListenerAdapter() {
-                            @Override
-                            public void onAnimationEnd(
-                                    @androidx.annotation.NonNull Animator animation) {
-                                t.remove(topLeftScreenshot);
-                                t.remove(bottomRightScreenshot);
-                                t.apply();
-                                mTransactionPool.release(t);
-                            }
-                        });
-                        va.start();
+                        // updateSurfaceBounds(), above, officially puts the two apps in their new
+                        // stages. Starting on the next frame, all calculations are made using the
+                        // new layouts/insets. So any follow-up animations on the same leashes below
+                        // should contain some cleanup/repositioning to prevent jank.
+
+                        // Play follow-up animations if needed
+                        decorManager1.fadeOutVeilAndCleanUp(st);
+                        decorManager2.fadeOutVeilAndCleanUp(st);
                     });
                 });
 
@@ -2242,6 +2243,7 @@
         final @WindowManager.TransitionType int type = request.getType();
         final boolean isOpening = isOpeningType(type);
         final boolean inFullscreen = triggerTask.getWindowingMode() == WINDOWING_MODE_FULLSCREEN;
+        final StageTaskListener stage = getStageOfTask(triggerTask);
 
         if (isOpening && inFullscreen) {
             // One task is opening into fullscreen mode, remove the corresponding split record.
@@ -2257,7 +2259,6 @@
                             + " sideChildren=%d", triggerTask.taskId, transitTypeToString(type),
                     mMainStage.getChildCount(), mSideStage.getChildCount());
             out = new WindowContainerTransaction();
-            final StageTaskListener stage = getStageOfTask(triggerTask);
             if (stage != null) {
                 if (isClosingType(type) && stage.getChildCount() == 1) {
                     // Dismiss split if the last task in one of the stages is going away
@@ -2330,16 +2331,8 @@
             // Don't intercept the transition if we are not handling it as a part of one of the
             // cases above and it is not already visible
             return null;
-        } else {
-            if (triggerTask.parentTaskId == mMainStage.mRootTaskInfo.taskId
-                    || triggerTask.parentTaskId == mSideStage.mRootTaskInfo.taskId) {
-                ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "handleRequest: transition=%d "
-                                + "restoring to split", request.getDebugId());
-                out = new WindowContainerTransaction();
-                mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(),
-                        TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, false /* resizeAnim */);
-            }
-            if (isOpening && getStageOfTask(triggerTask) != null) {
+        } else if (stage != null) {
+            if (isOpening) {
                 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "handleRequest: transition=%d enter split",
                         request.getDebugId());
                 // One task is appearing into split, prepare to enter split screen.
@@ -2347,9 +2340,15 @@
                 prepareEnterSplitScreen(out);
                 mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(),
                         TRANSIT_SPLIT_SCREEN_PAIR_OPEN, !mIsDropEntering);
+                return out;
             }
-            return out;
+            ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "handleRequest: transition=%d "
+                    + "restoring to split", request.getDebugId());
+            out = new WindowContainerTransaction();
+            mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(),
+                    TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, false /* resizeAnim */);
         }
+        return out;
     }
 
     /**
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 d1ab3e9..f19eb3f 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
@@ -69,7 +69,7 @@
  *
  * @see StageCoordinator
  */
-class StageTaskListener implements ShellTaskOrganizer.TaskListener {
+public class StageTaskListener implements ShellTaskOrganizer.TaskListener {
     private static final String TAG = StageTaskListener.class.getSimpleName();
 
     /** Callback interface for listening to changes in a split-screen stage. */
@@ -162,6 +162,18 @@
         return getChildTaskInfo(predicate) != null;
     }
 
+    public SurfaceControl getRootLeash() {
+        return mRootLeash;
+    }
+
+    public ActivityManager.RunningTaskInfo getRunningTaskInfo() {
+        return mRootTaskInfo;
+    }
+
+    public SplitDecorManager getDecorManager() {
+        return mSplitDecorManager;
+    }
+
     @Nullable
     private ActivityManager.RunningTaskInfo getChildTaskInfo(
             Predicate<ActivityManager.RunningTaskInfo> predicate) {
@@ -335,7 +347,7 @@
 
     void fadeOutDecor(Runnable finishedCallback) {
         if (mSplitDecorManager != null) {
-            mSplitDecorManager.fadeOutDecor(finishedCallback);
+            mSplitDecorManager.fadeOutDecor(finishedCallback, false /* addDelay */);
         } else {
             finishedCallback.run();
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java
index f372557..1a38449 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.startingsurface;
 
-import static android.graphics.Color.WHITE;
 import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
 
 import android.app.ActivityManager;
@@ -69,8 +68,9 @@
             // Can't show splash screen on requested display, so skip showing at all.
             return;
         }
+        final int theme = getSplashScreenTheme(0 /* splashScreenThemeResId */, activityInfo);
         final Context myContext = SplashscreenContentDrawer.createContext(mContext, windowInfo,
-                0 /* theme */, STARTING_WINDOW_TYPE_SPLASH_SCREEN, mDisplayManager);
+                theme, STARTING_WINDOW_TYPE_SPLASH_SCREEN, mDisplayManager);
         if (myContext == null) {
             return;
         }
@@ -86,19 +86,11 @@
         final Rect windowBounds = taskInfo.configuration.windowConfiguration.getBounds();
         lp.width = windowBounds.width();
         lp.height = windowBounds.height();
-        final ActivityManager.TaskDescription taskDescription;
-        if (taskInfo.taskDescription != null) {
-            taskDescription = taskInfo.taskDescription;
-        } else {
-            taskDescription = new ActivityManager.TaskDescription();
-            taskDescription.setBackgroundColor(WHITE);
-        }
 
         final FrameLayout rootLayout = new FrameLayout(
                 mSplashscreenContentDrawer.createViewContextWrapper(myContext));
         viewHost.setView(rootLayout, lp);
-
-        final int bgColor = taskDescription.getBackgroundColor();
+        final int bgColor = mSplashscreenContentDrawer.estimateTaskBackgroundColor(myContext);
         final SplashScreenView splashScreenView = mSplashscreenContentDrawer
                 .makeSimpleSplashScreenContentView(myContext, windowInfo, bgColor);
         rootLayout.addView(splashScreenView);
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 7784784..de6887a2 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
@@ -502,15 +502,19 @@
                 backgroundColorForTransition = getTransitionBackgroundColorIfSet(info, change, a,
                         backgroundColorForTransition);
 
-                if (!isTask && a.hasExtension()) {
-                    if (!TransitionUtil.isOpeningType(mode)) {
-                        // Can screenshot now (before startTransaction is applied)
-                        edgeExtendWindow(change, a, startTransaction, finishTransaction);
+                if (!isTask && a.getExtensionEdges() != 0x0) {
+                    if (com.android.graphics.libgui.flags.Flags.edgeExtensionShader()) {
+                        finishTransaction.setEdgeExtensionEffect(change.getLeash(), /* edge */ 0);
                     } else {
-                        // Need to screenshot after startTransaction is applied otherwise activity
-                        // may not be visible or ready yet.
-                        postStartTransactionCallbacks
-                                .add(t -> edgeExtendWindow(change, a, t, finishTransaction));
+                        if (!TransitionUtil.isOpeningType(mode)) {
+                            // Can screenshot now (before startTransaction is applied)
+                            edgeExtendWindow(change, a, startTransaction, finishTransaction);
+                        } else {
+                            // Need to screenshot after startTransaction is applied otherwise
+                            // activity may not be visible or ready yet.
+                            postStartTransactionCallbacks
+                                    .add(t -> edgeExtendWindow(change, a, t, finishTransaction));
+                        }
                     }
                 }
 
@@ -558,7 +562,7 @@
 
                 buildSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish,
                         mTransactionPool, mMainExecutor, animRelOffset, cornerRadius,
-                        clipRect);
+                        clipRect, change.getActivityComponent() != null);
 
                 final TransitionInfo.AnimationOptions options;
                 if (Flags.moveAnimationOptionsToChange()) {
@@ -823,7 +827,7 @@
             @NonNull Animation anim, @NonNull SurfaceControl leash,
             @NonNull Runnable finishCallback, @NonNull TransactionPool pool,
             @NonNull ShellExecutor mainExecutor, @Nullable Point position, float cornerRadius,
-            @Nullable Rect clipRect) {
+            @Nullable Rect clipRect, boolean isActivity) {
         final SurfaceControl.Transaction transaction = pool.acquire();
         final ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
         final Transformation transformation = new Transformation();
@@ -835,13 +839,13 @@
             final long currentPlayTime = Math.min(va.getDuration(), va.getCurrentPlayTime());
 
             applyTransformation(currentPlayTime, transaction, leash, anim, transformation, matrix,
-                    position, cornerRadius, clipRect);
+                    position, cornerRadius, clipRect, isActivity);
         };
         va.addUpdateListener(updateListener);
 
         final Runnable finisher = () -> {
             applyTransformation(va.getDuration(), transaction, leash, anim, transformation, matrix,
-                    position, cornerRadius, clipRect);
+                    position, cornerRadius, clipRect, isActivity);
 
             pool.release(transaction);
             mainExecutor.execute(() -> {
@@ -931,7 +935,8 @@
         a.restrictDuration(MAX_ANIMATION_DURATION);
         a.scaleCurrentDuration(mTransitionAnimationScaleSetting);
         buildSurfaceAnimation(animations, a, wt.getSurface(), finisher, mTransactionPool,
-                mMainExecutor, change.getEndRelOffset(), cornerRadius, change.getEndAbsBounds());
+                mMainExecutor, change.getEndRelOffset(), cornerRadius, change.getEndAbsBounds(),
+                change.getActivityComponent() != null);
     }
 
     private void attachThumbnailAnimation(@NonNull ArrayList<Animator> animations,
@@ -955,7 +960,8 @@
         a.restrictDuration(MAX_ANIMATION_DURATION);
         a.scaleCurrentDuration(mTransitionAnimationScaleSetting);
         buildSurfaceAnimation(animations, a, wt.getSurface(), finisher, mTransactionPool,
-                mMainExecutor, change.getEndRelOffset(), cornerRadius, change.getEndAbsBounds());
+                mMainExecutor, change.getEndRelOffset(), cornerRadius, change.getEndAbsBounds(),
+                change.getActivityComponent() != null);
     }
 
     private static int getWallpaperTransitType(TransitionInfo info) {
@@ -1005,9 +1011,14 @@
 
     private static void applyTransformation(long time, SurfaceControl.Transaction t,
             SurfaceControl leash, Animation anim, Transformation tmpTransformation, float[] matrix,
-            Point position, float cornerRadius, @Nullable Rect immutableClipRect) {
+            Point position, float cornerRadius, @Nullable Rect immutableClipRect,
+            boolean isActivity) {
         tmpTransformation.clear();
         anim.getTransformation(time, tmpTransformation);
+        if (com.android.graphics.libgui.flags.Flags.edgeExtensionShader()
+                && anim.getExtensionEdges() != 0x0 && isActivity) {
+            t.setEdgeExtensionEffect(leash, anim.getExtensionEdges());
+        }
         if (position != null) {
             tmpTransformation.getMatrix().postTranslate(position.x, position.y);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
index e196254..1958825 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
@@ -325,21 +325,21 @@
             @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) {
         buildSurfaceAnimation(animations, mRotateEnterAnimation, mSurfaceControl, finishCallback,
                 mTransactionPool, mainExecutor, null /* position */, 0 /* cornerRadius */,
-                null /* clipRect */);
+                null /* clipRect */, false /* isActivity */);
     }
 
     private void startScreenshotRotationAnimation(@NonNull ArrayList<Animator> animations,
             @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) {
         buildSurfaceAnimation(animations, mRotateExitAnimation, mAnimLeash, finishCallback,
                 mTransactionPool, mainExecutor, null /* position */, 0 /* cornerRadius */,
-                null /* clipRect */);
+                null /* clipRect */, false /* isActivity */);
     }
 
     private void buildScreenshotAlphaAnimation(@NonNull ArrayList<Animator> animations,
             @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) {
         buildSurfaceAnimation(animations, mRotateAlphaAnimation, mAnimLeash, finishCallback,
                 mTransactionPool, mainExecutor, null /* position */, 0 /* cornerRadius */,
-                null /* clipRect */);
+                null /* clipRect */, false /* isActivity */);
     }
 
     private void startColorAnimation(float animationScale, @NonNull ShellExecutor animExecutor) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
index 75e7ddf..a27c14bd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
@@ -19,7 +19,9 @@
 import static android.app.ActivityOptions.ANIM_FROM_STYLE;
 import static android.app.ActivityOptions.ANIM_NONE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION;
 import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 import static android.view.WindowManager.transitTypeToString;
@@ -221,6 +223,15 @@
      */
     public static int getTransitionTypeFromInfo(@NonNull TransitionInfo info) {
         final int type = info.getType();
+        // This back navigation is canceled, check whether the transition should be open or close
+        if (type == TRANSIT_PREPARE_BACK_NAVIGATION
+                || type == TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION) {
+            if (!info.getChanges().isEmpty()) {
+                final TransitionInfo.Change change = info.getChanges().get(0);
+                return TransitionUtil.isOpeningMode(change.getMode())
+                        ? TRANSIT_OPEN : TRANSIT_CLOSE;
+            }
+        }
         // If the info transition type is opening transition, iterate its changes to see if it
         // has any opening change, if none, returns TRANSIT_CLOSE type for closing animation.
         if (type == TRANSIT_OPEN) {
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 a242b8a..8c8f205 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
@@ -69,7 +69,6 @@
 import android.view.InputEvent;
 import android.view.InputEventReceiver;
 import android.view.InputMonitor;
-import android.view.InsetsSource;
 import android.view.InsetsState;
 import android.view.MotionEvent;
 import android.view.SurfaceControl;
@@ -115,6 +114,7 @@
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
 import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.ExclusionRegionListener;
+import com.android.wm.shell.windowdecor.extension.InsetsStateKt;
 import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
 import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
 
@@ -321,7 +321,7 @@
     private void onInit() {
         mShellController.addKeyguardChangeListener(mDesktopModeKeyguardChangeListener);
         mShellCommandHandler.addDumpCallback(this::dump, this);
-        mDisplayInsetsController.addInsetsChangedListener(mContext.getDisplayId(),
+        mDisplayInsetsController.addGlobalInsetsChangedListener(
                 new DesktopModeOnInsetsChangedListener());
         mDesktopTasksController.setOnTaskResizeAnimationListener(
                 new DesktopModeOnTaskResizeAnimationListener());
@@ -1196,10 +1196,6 @@
                 && mSplitScreenController.isTaskRootOrStageRoot(taskInfo.taskId)) {
             return false;
         }
-        if (mDesktopModeKeyguardChangeListener.isKeyguardVisibleAndOccluded()
-                && taskInfo.isFocused) {
-            return false;
-        }
         if (DesktopModeFlags.MODALS_POLICY.isEnabled(mContext)
                 && isTopActivityExemptFromDesktopWindowing(mContext, taskInfo)) {
             return false;
@@ -1397,19 +1393,17 @@
         }
     }
 
-    static class DesktopModeKeyguardChangeListener implements KeyguardChangeListener {
-        private boolean mIsKeyguardVisible;
-        private boolean mIsKeyguardOccluded;
-
+    class DesktopModeKeyguardChangeListener implements KeyguardChangeListener {
         @Override
         public void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
                 boolean animatingDismiss) {
-            mIsKeyguardVisible = visible;
-            mIsKeyguardOccluded = occluded;
-        }
-
-        public boolean isKeyguardVisibleAndOccluded() {
-            return mIsKeyguardVisible && mIsKeyguardOccluded;
+            final int size = mWindowDecorByTaskId.size();
+            for (int i = size - 1; i >= 0; i--) {
+                final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
+                if (decor != null) {
+                    decor.onKeyguardStateChanged(visible, occluded);
+                }
+            }
         }
     }
 
@@ -1417,28 +1411,26 @@
     class DesktopModeOnInsetsChangedListener implements
             DisplayInsetsController.OnInsetsChangedListener {
         @Override
-        public void insetsChanged(InsetsState insetsState) {
-            for (int i = 0; i < insetsState.sourceSize(); i++) {
-                final InsetsSource source = insetsState.sourceAt(i);
-                if (source.getType() != statusBars()) {
+        public void insetsChanged(int displayId, @NonNull InsetsState insetsState) {
+            final int size = mWindowDecorByTaskId.size();
+            for (int i = size - 1; i >= 0; i--) {
+                final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
+                if (decor == null) {
                     continue;
                 }
-
-                final DesktopModeWindowDecoration decor = getFocusedDecor();
-                if (decor == null) {
-                    return;
+                if (decor.mTaskInfo.displayId == displayId
+                        && Flags.enableDesktopWindowingImmersiveHandleHiding()) {
+                    decor.onInsetsStateChanged(insetsState);
                 }
-                // If status bar inset is visible, top task is not in immersive mode
-                final boolean inImmersiveMode = !source.isVisible();
-                // Calls WindowDecoration#relayout if decoration visibility needs to be updated
-                if (inImmersiveMode != mInImmersiveMode) {
-                    if (Flags.enableDesktopWindowingImmersiveHandleHiding()) {
-                        decor.relayout(decor.mTaskInfo);
-                    }
-                    mInImmersiveMode = inImmersiveMode;
+                if (!Flags.enableAdditionalWindowsAboveStatusBar()) {
+                    // If status bar inset is visible, top task is not in immersive mode.
+                    // This value is only needed when the App Handle input is being handled
+                    // through the global input monitor (hence the flag check) to ignore gestures
+                    // when the app is in immersive mode. When disabled, the view itself handles
+                    // input, and since it's removed when in immersive there's no need to track
+                    // this here.
+                    mInImmersiveMode = !InsetsStateKt.isVisible(insetsState, statusBars());
                 }
-
-                return;
             }
         }
     }
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 54b33e93..095d337 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
@@ -60,6 +60,7 @@
 import com.android.wm.shell.R
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.animation.Interpolators.EMPHASIZED_DECELERATE
+import com.android.wm.shell.animation.Interpolators.FAST_OUT_LINEAR_IN
 import com.android.wm.shell.common.DisplayController
 import com.android.wm.shell.common.SyncTransactionQueue
 import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer
@@ -120,8 +121,9 @@
 
     /** Closes the maximize window and releases its view. */
     fun close() {
-        maximizeMenuView?.cancelAnimation()
-        maximizeMenu?.releaseView()
+        maximizeMenuView?.animateCloseMenu {
+            maximizeMenu?.releaseView()
+        }
         maximizeMenu = null
         maximizeMenuView = null
     }
@@ -255,7 +257,7 @@
             .getDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_buttons_fill_radius)
 
         private val hoverTempRect = Rect()
-        private val openMenuAnimatorSet = AnimatorSet()
+        private var menuAnimatorSet: AnimatorSet? = null
         private lateinit var taskInfo: RunningTaskInfo
         private lateinit var style: MenuStyle
 
@@ -346,15 +348,16 @@
         fun animateOpenMenu() {
             maximizeButton.setLayerType(View.LAYER_TYPE_HARDWARE, null)
             maximizeText.setLayerType(View.LAYER_TYPE_HARDWARE, null)
-            openMenuAnimatorSet.playTogether(
+            menuAnimatorSet = AnimatorSet()
+            menuAnimatorSet?.playTogether(
                 ObjectAnimator.ofFloat(rootView, SCALE_Y, STARTING_MENU_HEIGHT_SCALE, 1f)
                     .apply {
-                        duration = MENU_HEIGHT_ANIMATION_DURATION_MS
+                        duration = OPEN_MENU_HEIGHT_ANIMATION_DURATION_MS
                         interpolator = EMPHASIZED_DECELERATE
                     },
                 ValueAnimator.ofFloat(STARTING_MENU_HEIGHT_SCALE, 1f)
                     .apply {
-                        duration = MENU_HEIGHT_ANIMATION_DURATION_MS
+                        duration = OPEN_MENU_HEIGHT_ANIMATION_DURATION_MS
                         interpolator = EMPHASIZED_DECELERATE
                         addUpdateListener {
                             // Animate padding so that controls stay pinned to the bottom of
@@ -367,7 +370,7 @@
                         }
                     },
                 ValueAnimator.ofFloat(1 / STARTING_MENU_HEIGHT_SCALE, 1f).apply {
-                    duration = MENU_HEIGHT_ANIMATION_DURATION_MS
+                    duration = OPEN_MENU_HEIGHT_ANIMATION_DURATION_MS
                     interpolator = EMPHASIZED_DECELERATE
                     addUpdateListener {
                         // Scale up the children of the maximize menu so that the menu
@@ -381,7 +384,7 @@
                 },
                 ObjectAnimator.ofFloat(rootView, TRANSLATION_Y,
                     (STARTING_MENU_HEIGHT_SCALE - 1) * menuHeight, 0f).apply {
-                    duration = MENU_HEIGHT_ANIMATION_DURATION_MS
+                    duration = OPEN_MENU_HEIGHT_ANIMATION_DURATION_MS
                     interpolator = EMPHASIZED_DECELERATE
                 },
                 ObjectAnimator.ofInt(rootView.background, "alpha",
@@ -391,7 +394,7 @@
                 ValueAnimator.ofFloat(0f, 1f)
                     .apply {
                         duration = ALPHA_ANIMATION_DURATION_MS
-                        startDelay = CONTROLS_ALPHA_ANIMATION_DELAY_MS
+                        startDelay = CONTROLS_ALPHA_OPEN_MENU_ANIMATION_DELAY_MS
                         addUpdateListener {
                             val value = animatedValue as Float
                             maximizeButton.alpha = value
@@ -403,21 +406,96 @@
                 ObjectAnimator.ofFloat(rootView, TRANSLATION_Z, MENU_Z_TRANSLATION)
                     .apply {
                         duration = ELEVATION_ANIMATION_DURATION_MS
-                        startDelay = CONTROLS_ALPHA_ANIMATION_DELAY_MS
+                        startDelay = CONTROLS_ALPHA_OPEN_MENU_ANIMATION_DELAY_MS
                     }
             )
-            openMenuAnimatorSet.addListener(
+            menuAnimatorSet?.addListener(
                 onEnd = {
                     maximizeButton.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
                     maximizeText.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
                 }
             )
-            openMenuAnimatorSet.start()
+            menuAnimatorSet?.start()
         }
 
-        /** Cancel the open menu animation. */
-        fun cancelAnimation() {
-            openMenuAnimatorSet.cancel()
+        /** Animate the closing of the menu */
+        fun animateCloseMenu(onEnd: (() -> Unit)) {
+            maximizeButton.setLayerType(View.LAYER_TYPE_HARDWARE, null)
+            maximizeText.setLayerType(View.LAYER_TYPE_HARDWARE, null)
+            cancelAnimation()
+            menuAnimatorSet = AnimatorSet()
+            menuAnimatorSet?.playTogether(
+                    ObjectAnimator.ofFloat(rootView, SCALE_Y, 1f, STARTING_MENU_HEIGHT_SCALE)
+                            .apply {
+                                duration = CLOSE_MENU_HEIGHT_ANIMATION_DURATION_MS
+                                interpolator = FAST_OUT_LINEAR_IN
+                            },
+                    ValueAnimator.ofFloat(1f, STARTING_MENU_HEIGHT_SCALE)
+                            .apply {
+                                duration = CLOSE_MENU_HEIGHT_ANIMATION_DURATION_MS
+                                interpolator = FAST_OUT_LINEAR_IN
+                                addUpdateListener {
+                                    // Animate padding so that controls stay pinned to the bottom of
+                                    // the menu.
+                                    val value = animatedValue as Float
+                                    val topPadding = menuPadding -
+                                            ((1 - value) * menuHeight).toInt()
+                                    container.setPadding(menuPadding, topPadding,
+                                            menuPadding, menuPadding)
+                                }
+                            },
+                    ValueAnimator.ofFloat(1f, 1 / STARTING_MENU_HEIGHT_SCALE).apply {
+                        duration = CLOSE_MENU_HEIGHT_ANIMATION_DURATION_MS
+                        interpolator = FAST_OUT_LINEAR_IN
+                        addUpdateListener {
+                            // Scale up the children of the maximize menu so that the menu
+                            // scale is cancelled out and only the background is scaled.
+                            val value = animatedValue as Float
+                            maximizeButton.scaleY = value
+                            snapButtonsLayout.scaleY = value
+                            maximizeText.scaleY = value
+                            snapWindowText.scaleY = value
+                        }
+                    },
+                    ObjectAnimator.ofFloat(rootView, TRANSLATION_Y,
+                            0f, (STARTING_MENU_HEIGHT_SCALE - 1) * menuHeight).apply {
+                        duration = CLOSE_MENU_HEIGHT_ANIMATION_DURATION_MS
+                        interpolator = FAST_OUT_LINEAR_IN
+                    },
+                    ObjectAnimator.ofInt(rootView.background, "alpha",
+                            MAX_DRAWABLE_ALPHA_VALUE, 0).apply {
+                        startDelay = CONTAINER_ALPHA_CLOSE_MENU_ANIMATION_DELAY_MS
+                        duration = ALPHA_ANIMATION_DURATION_MS
+                    },
+                    ValueAnimator.ofFloat(1f, 0f)
+                            .apply {
+                                duration = ALPHA_ANIMATION_DURATION_MS
+                                addUpdateListener {
+                                    val value = animatedValue as Float
+                                    maximizeButton.alpha = value
+                                    snapButtonsLayout.alpha = value
+                                    maximizeText.alpha = value
+                                    snapWindowText.alpha = value
+                                }
+                            },
+                    ObjectAnimator.ofFloat(rootView, TRANSLATION_Z, MENU_Z_TRANSLATION, 0f)
+                            .apply {
+                                duration = ELEVATION_ANIMATION_DURATION_MS
+                            }
+            )
+            menuAnimatorSet?.addListener(
+                    onEnd = {
+                        maximizeButton.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
+                        maximizeText.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
+                        onEnd?.invoke()
+                    }
+            )
+            menuAnimatorSet?.start()
+        }
+
+        /** Cancel the menu animation. */
+        private fun cancelAnimation() {
+            menuAnimatorSet?.cancel()
         }
 
         /** Update the view state to a new snap to half selection. */
@@ -645,9 +723,11 @@
         private const val ALPHA_ANIMATION_DURATION_MS = 50L
         private const val MAX_DRAWABLE_ALPHA_VALUE = 255
         private const val STARTING_MENU_HEIGHT_SCALE = 0.8f
-        private const val MENU_HEIGHT_ANIMATION_DURATION_MS = 300L
+        private const val OPEN_MENU_HEIGHT_ANIMATION_DURATION_MS = 300L
+        private const val CLOSE_MENU_HEIGHT_ANIMATION_DURATION_MS = 200L
         private const val ELEVATION_ANIMATION_DURATION_MS = 50L
-        private const val CONTROLS_ALPHA_ANIMATION_DELAY_MS = 33L
+        private const val CONTROLS_ALPHA_OPEN_MENU_ANIMATION_DELAY_MS = 33L
+        private const val CONTAINER_ALPHA_CLOSE_MENU_ANIMATION_DELAY_MS = 33L
         private const val MENU_Z_TRANSLATION = 1f
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt
index 9741667..70c0b54 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt
@@ -7,6 +7,7 @@
 import android.graphics.Rect
 import android.view.MotionEvent
 import android.view.SurfaceControl
+import android.view.VelocityTracker
 import com.android.wm.shell.R
 
 /**
@@ -34,6 +35,7 @@
     val scale: Float
         get() = dragToDesktopAnimator.animatedValue as Float
     private val mostRecentInput = PointF()
+    private val velocityTracker = VelocityTracker.obtain()
     private val dragToDesktopAnimator: ValueAnimator = ValueAnimator.ofFloat(1f,
             DRAG_FREEFORM_SCALE)
             .setDuration(ANIMATION_DURATION.toLong())
@@ -90,6 +92,7 @@
         if (!allowSurfaceChangesOnMove || dragToDesktopAnimator.isRunning) {
             return
         }
+        velocityTracker.addMovement(ev)
         setTaskPosition(ev.rawX, ev.rawY)
         val t = transactionFactory()
         t.setPosition(taskSurface, position.x, position.y)
@@ -109,6 +112,15 @@
      * Cancels the animation, intended to be used when another animator will take over.
      */
     fun cancelAnimator() {
+        velocityTracker.clear()
         dragToDesktopAnimator.cancel()
     }
+
+    /**
+     * Computes the current velocity per second based on the points that have been collected.
+     */
+    fun computeCurrentVelocity(): PointF {
+        velocityTracker.computeCurrentVelocity(/* units = */ 1000)
+        return PointF(velocityTracker.xVelocity, velocityTracker.yVelocity)
+    }
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 0c58987..4af5b2c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -61,6 +61,7 @@
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams.OccludingCaptionElement;
 import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer;
+import com.android.wm.shell.windowdecor.extension.InsetsStateKt;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -143,6 +144,9 @@
     TaskDragResizer mTaskDragResizer;
     boolean mIsCaptionVisible;
 
+    private boolean mIsStatusBarVisible;
+    private boolean mIsKeyguardVisibleAndOccluded;
+
     /** The most recent set of insets applied to this window decoration. */
     private WindowDecorationInsets mWindowDecorationInsets;
     private final Binder mOwner = new Binder();
@@ -184,6 +188,9 @@
         mWindowContainerTransactionSupplier = windowContainerTransactionSupplier;
         mSurfaceControlViewHostFactory = surfaceControlViewHostFactory;
         mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId);
+        final InsetsState insetsState = mDisplayController.getInsetsState(mTaskInfo.displayId);
+        mIsStatusBarVisible = insetsState != null
+                && InsetsStateKt.isVisible(insetsState, statusBars());
     }
 
     /**
@@ -234,7 +241,7 @@
         }
         rootView = null; // Clear it just in case we use it accidentally
 
-        updateCaptionVisibility(outResult.mRootView, mTaskInfo.displayId);
+        updateCaptionVisibility(outResult.mRootView);
 
         final Rect taskBounds = mTaskInfo.getConfiguration().windowConfiguration.getBounds();
         outResult.mWidth = taskBounds.width();
@@ -284,17 +291,20 @@
             mDecorWindowContext = mContext.createConfigurationContext(mWindowDecorConfig);
             mDecorWindowContext.setTheme(mContext.getThemeResId());
             if (params.mLayoutResId != 0) {
-                outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext)
-                        .inflate(params.mLayoutResId, null);
+                outResult.mRootView = inflateLayout(mDecorWindowContext, params.mLayoutResId);
             }
         }
 
         if (outResult.mRootView == null) {
-            outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext)
-                    .inflate(params.mLayoutResId, null);
+            outResult.mRootView = inflateLayout(mDecorWindowContext, params.mLayoutResId);
         }
     }
 
+    @VisibleForTesting
+    T inflateLayout(Context context, int layoutResId) {
+        return (T) LayoutInflater.from(context).inflate(layoutResId, null);
+    }
+
     private void updateDecorationContainerSurface(
             SurfaceControl.Transaction startT, RelayoutResult<T> outResult) {
         if (mDecorationContainerSurface == null) {
@@ -497,22 +507,31 @@
         throw new IllegalArgumentException("Unexpected alignment " + element.mAlignment);
     }
 
+    void onKeyguardStateChanged(boolean visible, boolean occluded) {
+        final boolean prevVisAndOccluded = mIsKeyguardVisibleAndOccluded;
+        mIsKeyguardVisibleAndOccluded = visible && occluded;
+        final boolean changed = prevVisAndOccluded != mIsKeyguardVisibleAndOccluded;
+        if (changed) {
+            relayout(mTaskInfo);
+        }
+    }
+
+    void onInsetsStateChanged(@NonNull InsetsState insetsState) {
+        final boolean prevStatusBarVisibility = mIsStatusBarVisible;
+        mIsStatusBarVisible = InsetsStateKt.isVisible(insetsState, statusBars());
+        final boolean changed = prevStatusBarVisibility != mIsStatusBarVisible;
+
+        if (changed) {
+            relayout(mTaskInfo);
+        }
+    }
+
     /**
      * Checks if task has entered/exited immersive mode and requires a change in caption visibility.
      */
-    private void updateCaptionVisibility(View rootView, int displayId) {
-        final InsetsState insetsState = mDisplayController.getInsetsState(displayId);
-        for (int i = 0; i < insetsState.sourceSize(); i++) {
-            final InsetsSource source = insetsState.sourceAt(i);
-            if (source.getType() != statusBars()) {
-                continue;
-            }
-
-            mIsCaptionVisible = source.isVisible();
-            setCaptionVisibility(rootView, mIsCaptionVisible);
-
-            return;
-        }
+    private void updateCaptionVisibility(View rootView) {
+        mIsCaptionVisible = mIsStatusBarVisible && !mIsKeyguardVisibleAndOccluded;
+        setCaptionVisibility(rootView, mIsCaptionVisible);
     }
 
     void setTaskDragResizer(TaskDragResizer taskDragResizer) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/InsetsState.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/InsetsState.kt
new file mode 100644
index 0000000..be01a20
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/InsetsState.kt
@@ -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.
+ */
+package com.android.wm.shell.windowdecor.extension
+
+import android.view.InsetsState
+import android.view.WindowInsets
+
+/**
+ * Whether the source of the given [type] is visible or false if there is no source of that type.
+ */
+fun InsetsState.isVisible(@WindowInsets.Type.InsetsType type: Int): Boolean {
+    for (i in 0 until sourceSize()) {
+        val source = sourceAt(i)
+        if (source.type != type) {
+            continue
+        }
+        return source.isVisible
+    }
+    return false
+}
diff --git a/libs/WindowManager/Shell/tests/OWNERS b/libs/WindowManager/Shell/tests/OWNERS
index 9f735c4..d1be12f 100644
--- a/libs/WindowManager/Shell/tests/OWNERS
+++ b/libs/WindowManager/Shell/tests/OWNERS
@@ -15,3 +15,6 @@
 mpodolian@google.com
 jeremysim@google.com
 peanutbutter@google.com
+pragyabajoria@google.com
+uysalorhan@google.com
+gsennton@google.com
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/OWNERS b/libs/WindowManager/Shell/tests/e2e/desktopmode/OWNERS
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/OWNERS
rename to libs/WindowManager/Shell/tests/e2e/desktopmode/OWNERS
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/Android.bp b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/Android.bp
new file mode 100644
index 0000000..50581f7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/Android.bp
@@ -0,0 +1,38 @@
+//
+// 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 {
+    // 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 {
+    name: "WMShellFlickerTestsDesktopMode",
+    defaults: ["WMShellFlickerTestsDefault"],
+    manifest: "AndroidManifest.xml",
+    test_config_template: "AndroidTestTemplate.xml",
+    srcs: ["src/**/*.kt"],
+    static_libs: [
+        "WMShellFlickerTestsBase",
+        "WMShellScenariosDesktopMode",
+        "WMShellTestUtils",
+    ],
+    data: ["trace_config/*"],
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/AndroidManifest.xml b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/AndroidManifest.xml
similarity index 95%
copy from libs/WindowManager/Shell/tests/flicker/service/AndroidManifest.xml
copy to libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/AndroidManifest.xml
index d54b694..1bbbefa 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/AndroidManifest.xml
@@ -16,7 +16,7 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           xmlns:tools="http://schemas.android.com/tools"
-          package="com.android.wm.shell.flicker.service">
+          package="com.android.wm.shell.flicker">
 
     <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/>
     <!-- Read and write traces from external storage -->
@@ -71,7 +71,7 @@
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.wm.shell.flicker.service"
-                     android:label="WindowManager Flicker Service Tests">
+                     android:targetPackage="com.android.wm.shell.flicker"
+                     android:label="WindowManager Shell Flicker Tests">
     </instrumentation>
 </manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/service/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/AndroidTestTemplate.xml
similarity index 92%
rename from libs/WindowManager/Shell/tests/flicker/service/AndroidTestTemplate.xml
rename to libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/AndroidTestTemplate.xml
index a66dfb4..40dbbac 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/AndroidTestTemplate.xml
@@ -59,13 +59,6 @@
         <option name="test-file-name" value="{MODULE}.apk"/>
         <option name="test-file-name" value="FlickerTestApp.apk"/>
     </target_preparer>
-    <!-- Enable mocking GPS location by the test app -->
-    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
-        <option name="run-command"
-                value="appops set com.android.wm.shell.flicker.pip.apps android:mock_location allow"/>
-        <option name="teardown-command"
-                value="appops set com.android.wm.shell.flicker.pip.apps android:mock_location deny"/>
-    </target_preparer>
 
     <!-- Needed for pushing the trace config file -->
     <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
@@ -97,7 +90,7 @@
     <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
         <option name="pull-pattern-keys" value="perfetto_file_path"/>
         <option name="directory-keys"
-                value="/data/user/0/com.android.wm.shell.flicker.service/files"/>
+                value="/data/user/0/com.android.wm.shell.flicker/files"/>
         <option name="collect-on-run-ended-only" value="true"/>
         <option name="clean-up" value="true"/>
     </metrics_collector>
diff --git a/libs/WindowManager/Shell/tests/flicker/service/res/xml/network_security_config.xml b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/res/xml/network_security_config.xml
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/service/res/xml/network_security_config.xml
rename to libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/res/xml/network_security_config.xml
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/CloseAllAppWithAppHeaderExitLandscape.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/CloseAllAppWithAppHeaderExitLandscape.kt
similarity index 80%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/CloseAllAppWithAppHeaderExitLandscape.kt
rename to libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/CloseAllAppWithAppHeaderExitLandscape.kt
index 5563bb9..b697d80 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/CloseAllAppWithAppHeaderExitLandscape.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/CloseAllAppWithAppHeaderExitLandscape.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.desktopmode.flicker
+package com.android.wm.shell.flicker
 
 import android.tools.Rotation
 import android.tools.flicker.FlickerConfig
@@ -23,9 +23,9 @@
 import android.tools.flicker.config.FlickerConfig
 import android.tools.flicker.config.FlickerServiceConfig
 import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.desktopmode.flicker.DesktopModeFlickerScenarios.Companion.CLOSE_APP
-import com.android.wm.shell.flicker.service.desktopmode.flicker.DesktopModeFlickerScenarios.Companion.CLOSE_LAST_APP
-import com.android.wm.shell.flicker.service.desktopmode.scenarios.CloseAllAppsWithAppHeaderExit
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.CLOSE_APP
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.CLOSE_LAST_APP
+import com.android.wm.shell.scenarios.CloseAllAppsWithAppHeaderExit
 import org.junit.Test
 import org.junit.runner.RunWith
 
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/CloseAllAppWithAppHeaderExitPortrait.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/CloseAllAppWithAppHeaderExitPortrait.kt
similarity index 80%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/CloseAllAppWithAppHeaderExitPortrait.kt
rename to libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/CloseAllAppWithAppHeaderExitPortrait.kt
index 3d16d2219..a11e876 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/CloseAllAppWithAppHeaderExitPortrait.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/CloseAllAppWithAppHeaderExitPortrait.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.desktopmode.flicker
+package com.android.wm.shell.flicker
 
 import android.tools.Rotation
 import android.tools.flicker.FlickerConfig
@@ -23,9 +23,9 @@
 import android.tools.flicker.config.FlickerConfig
 import android.tools.flicker.config.FlickerServiceConfig
 import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.desktopmode.flicker.DesktopModeFlickerScenarios.Companion.CLOSE_APP
-import com.android.wm.shell.flicker.service.desktopmode.flicker.DesktopModeFlickerScenarios.Companion.CLOSE_LAST_APP
-import com.android.wm.shell.flicker.service.desktopmode.scenarios.CloseAllAppsWithAppHeaderExit
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.CLOSE_APP
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.CLOSE_LAST_APP
+import com.android.wm.shell.scenarios.CloseAllAppsWithAppHeaderExit
 import org.junit.Test
 import org.junit.runner.RunWith
 
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
similarity index 98%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt
rename to libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
index 430f80b..8584b59 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.desktopmode.flicker
+package com.android.wm.shell.flicker
 
 import android.tools.flicker.AssertionInvocationGroup
 import android.tools.flicker.assertors.assertions.AppLayerIsInvisibleAtEnd
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/EnterDesktopWithDragLandscape.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/EnterDesktopWithDragLandscape.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/EnterDesktopWithDragLandscape.kt
rename to libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/EnterDesktopWithDragLandscape.kt
index 9dfafe9..f7b2556 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/EnterDesktopWithDragLandscape.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/EnterDesktopWithDragLandscape.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.desktopmode.flicker
+package com.android.wm.shell.flicker
 
 import android.tools.Rotation
 import android.tools.flicker.FlickerConfig
@@ -23,8 +23,8 @@
 import android.tools.flicker.config.FlickerConfig
 import android.tools.flicker.config.FlickerServiceConfig
 import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.desktopmode.flicker.DesktopModeFlickerScenarios.Companion.END_DRAG_TO_DESKTOP
-import com.android.wm.shell.flicker.service.desktopmode.scenarios.EnterDesktopWithDrag
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.END_DRAG_TO_DESKTOP
+import com.android.wm.shell.scenarios.EnterDesktopWithDrag
 import org.junit.Test
 import org.junit.runner.RunWith
 
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/EnterDesktopWithDragPortrait.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/EnterDesktopWithDragPortrait.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/EnterDesktopWithDragPortrait.kt
rename to libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/EnterDesktopWithDragPortrait.kt
index 1c7d623..f4bf0f9 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/EnterDesktopWithDragPortrait.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/EnterDesktopWithDragPortrait.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.desktopmode.flicker
+package com.android.wm.shell.flicker
 
 import android.tools.Rotation
 import android.tools.flicker.FlickerConfig
@@ -23,8 +23,8 @@
 import android.tools.flicker.config.FlickerConfig
 import android.tools.flicker.config.FlickerServiceConfig
 import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.desktopmode.flicker.DesktopModeFlickerScenarios.Companion.END_DRAG_TO_DESKTOP
-import com.android.wm.shell.flicker.service.desktopmode.scenarios.EnterDesktopWithDrag
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.END_DRAG_TO_DESKTOP
+import com.android.wm.shell.scenarios.EnterDesktopWithDrag
 import org.junit.Test
 import org.junit.runner.RunWith
 
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppToMinimumWindowSizeLandscape.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppToMinimumWindowSizeLandscape.kt
similarity index 86%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppToMinimumWindowSizeLandscape.kt
rename to libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppToMinimumWindowSizeLandscape.kt
index 6319cf7..45e5fdc 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppToMinimumWindowSizeLandscape.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppToMinimumWindowSizeLandscape.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.desktopmode.flicker
+package com.android.wm.shell.flicker
 
 import android.tools.Rotation
 import android.tools.flicker.FlickerConfig
@@ -23,8 +23,8 @@
 import android.tools.flicker.config.FlickerConfig
 import android.tools.flicker.config.FlickerServiceConfig
 import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.desktopmode.flicker.DesktopModeFlickerScenarios.Companion.CORNER_RESIZE_TO_MINIMUM_SIZE
-import com.android.wm.shell.flicker.service.desktopmode.scenarios.ResizeAppWithCornerResize
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.CORNER_RESIZE_TO_MINIMUM_SIZE
+import com.android.wm.shell.scenarios.ResizeAppWithCornerResize
 import org.junit.Test
 import org.junit.runner.RunWith
 
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppToMinimumWindowSizePortrait.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppToMinimumWindowSizePortrait.kt
similarity index 85%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppToMinimumWindowSizePortrait.kt
rename to libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppToMinimumWindowSizePortrait.kt
index 431f6e3..62a2571 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppToMinimumWindowSizePortrait.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppToMinimumWindowSizePortrait.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.desktopmode.flicker
+package com.android.wm.shell.flicker
 
 import android.tools.flicker.FlickerConfig
 import android.tools.flicker.annotation.ExpectedScenarios
@@ -22,8 +22,8 @@
 import android.tools.flicker.config.FlickerConfig
 import android.tools.flicker.config.FlickerServiceConfig
 import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.desktopmode.flicker.DesktopModeFlickerScenarios.Companion.CORNER_RESIZE_TO_MINIMUM_SIZE
-import com.android.wm.shell.flicker.service.desktopmode.scenarios.ResizeAppWithCornerResize
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.CORNER_RESIZE_TO_MINIMUM_SIZE
+import com.android.wm.shell.scenarios.ResizeAppWithCornerResize
 import org.junit.Test
 import org.junit.runner.RunWith
 
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppWithCornerResizeLandscape.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppWithCornerResizeLandscape.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppWithCornerResizeLandscape.kt
rename to libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppWithCornerResizeLandscape.kt
index 8d1a530..ea8b10b 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppWithCornerResizeLandscape.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppWithCornerResizeLandscape.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.desktopmode.flicker
+package com.android.wm.shell.flicker
 
 import android.tools.Rotation
 import android.tools.flicker.FlickerConfig
@@ -23,8 +23,8 @@
 import android.tools.flicker.config.FlickerConfig
 import android.tools.flicker.config.FlickerServiceConfig
 import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.desktopmode.flicker.DesktopModeFlickerScenarios.Companion.CORNER_RESIZE
-import com.android.wm.shell.flicker.service.desktopmode.scenarios.ResizeAppWithCornerResize
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.CORNER_RESIZE
+import com.android.wm.shell.scenarios.ResizeAppWithCornerResize
 import org.junit.Test
 import org.junit.runner.RunWith
 
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppWithCornerResizePortrait.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppWithCornerResizePortrait.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppWithCornerResizePortrait.kt
rename to libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppWithCornerResizePortrait.kt
index 2d81c8c..d7bba6e 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppWithCornerResizePortrait.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppWithCornerResizePortrait.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.desktopmode.flicker
+package com.android.wm.shell.flicker
 
 import android.tools.Rotation
 import android.tools.flicker.FlickerConfig
@@ -23,8 +23,8 @@
 import android.tools.flicker.config.FlickerConfig
 import android.tools.flicker.config.FlickerServiceConfig
 import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.desktopmode.flicker.DesktopModeFlickerScenarios.Companion.CORNER_RESIZE
-import com.android.wm.shell.flicker.service.desktopmode.scenarios.ResizeAppWithCornerResize
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.CORNER_RESIZE
+import com.android.wm.shell.scenarios.ResizeAppWithCornerResize
 import org.junit.Test
 import org.junit.runner.RunWith
 
diff --git a/libs/WindowManager/Shell/tests/flicker/service/trace_config/trace_config.textproto b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/trace_config/trace_config.textproto
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/service/trace_config/trace_config.textproto
rename to libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/trace_config/trace_config.textproto
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/Android.bp b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/Android.bp
new file mode 100644
index 0000000..4389f09
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/Android.bp
@@ -0,0 +1,46 @@
+//
+// 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 {
+    // 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"],
+}
+
+java_library {
+    name: "WMShellScenariosDesktopMode",
+    platform_apis: true,
+    optimize: {
+        enabled: false,
+    },
+    srcs: ["src/**/*.kt"],
+    static_libs: [
+        "WMShellFlickerTestsBase",
+        "WMShellTestUtils",
+        "wm-shell-flicker-utils",
+        "androidx.test.ext.junit",
+        "flickertestapplib",
+        "flickerlib-helpers",
+        "flickerlib-trace_processor_shell",
+        "platform-test-annotations",
+        "wm-flicker-common-app-helpers",
+        "launcher-helper-lib",
+        "launcher-aosp-tapl",
+    ],
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/CloseAllAppsWithAppHeaderExit.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/CloseAllAppsWithAppHeaderExit.kt
similarity index 89%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/CloseAllAppsWithAppHeaderExit.kt
rename to libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/CloseAllAppsWithAppHeaderExit.kt
index e77a457..e9056f3 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/CloseAllAppsWithAppHeaderExit.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/CloseAllAppsWithAppHeaderExit.kt
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.desktopmode.scenarios
+package com.android.wm.shell.scenarios
 
+import android.platform.test.annotations.Postsubmit
 import android.app.Instrumentation
 import android.tools.NavBar
 import android.tools.Rotation
@@ -28,16 +29,18 @@
 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.flicker.service.common.Utils
+import com.android.wm.shell.Utils
 import org.junit.After
 import org.junit.Assume
 import org.junit.Before
-import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
 
-@Ignore("Base Test Class")
-abstract class CloseAllAppsWithAppHeaderExit
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class CloseAllAppsWithAppHeaderExit
 @JvmOverloads
 constructor(val rotation: Rotation = Rotation.ROTATION_0) {
 
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/DragAppWindowMultiWindow.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindow.kt
similarity index 87%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/DragAppWindowMultiWindow.kt
rename to libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindow.kt
index bbf0ce5..ca1dc1a 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/DragAppWindowMultiWindow.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindow.kt
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.desktopmode.scenarios
+package com.android.wm.shell.scenarios
 
+import android.platform.test.annotations.Postsubmit
 import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
 import com.android.server.wm.flicker.helpers.ImeAppHelper
 import com.android.server.wm.flicker.helpers.MailAppHelper
@@ -25,12 +26,13 @@
 import org.junit.After
 import org.junit.Assume
 import org.junit.Before
-import org.junit.Ignore
 import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
 
-/** Base scenario test for window drag CUJ with multiple windows. */
-@Ignore("Base Test Class")
-abstract class DragAppWindowMultiWindow : DragAppWindowScenarioTestBase()
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class DragAppWindowMultiWindow : DragAppWindowScenarioTestBase()
 {
     private val imeAppHelper = ImeAppHelper(instrumentation)
     private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/DragAppWindowScenarioTestBase.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowScenarioTestBase.kt
similarity index 93%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/DragAppWindowScenarioTestBase.kt
rename to libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowScenarioTestBase.kt
index a613ca1..7219287 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/DragAppWindowScenarioTestBase.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowScenarioTestBase.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.desktopmode.scenarios
+package com.android.wm.shell.scenarios
 
 import android.app.Instrumentation
 import android.tools.NavBar
@@ -24,7 +24,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.common.Utils
+import com.android.wm.shell.Utils
 import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/DragAppWindowSingleWindow.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowSingleWindow.kt
similarity index 83%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/DragAppWindowSingleWindow.kt
rename to libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowSingleWindow.kt
index 0655620..91cfd17 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/DragAppWindowSingleWindow.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowSingleWindow.kt
@@ -14,20 +14,21 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.desktopmode.scenarios
+package com.android.wm.shell.scenarios
 
+import android.platform.test.annotations.Postsubmit
 import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.window.flags.Flags
 import org.junit.After
 import org.junit.Assume
 import org.junit.Before
-import org.junit.Ignore
 import org.junit.Test
-
-/** Base scenario test for window drag CUJ with single window. */
-@Ignore("Base Test Class")
-abstract class DragAppWindowSingleWindow : DragAppWindowScenarioTestBase()
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class DragAppWindowSingleWindow : DragAppWindowScenarioTestBase()
 {
     private val simpleAppHelper = SimpleAppHelper(instrumentation)
     private val testApp = DesktopModeAppHelper(simpleAppHelper)
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/EnterDesktopWithAppHandleMenu.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithAppHandleMenu.kt
similarity index 87%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/EnterDesktopWithAppHandleMenu.kt
rename to libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithAppHandleMenu.kt
index 47a215a..1073050 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/EnterDesktopWithAppHandleMenu.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithAppHandleMenu.kt
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.desktopmode.scenarios
+package com.android.wm.shell.scenarios
 
+import android.platform.test.annotations.Postsubmit
 import android.app.Instrumentation
 import android.tools.traces.parsers.WindowManagerStateHelper
 import androidx.test.platform.app.InstrumentationRegistry
@@ -27,12 +28,12 @@
 import org.junit.After
 import org.junit.Assume
 import org.junit.Before
-import org.junit.Ignore
 import org.junit.Test
-
-/** Base test class for enter desktop with app handle menu CUJ. */
-@Ignore("Base Test Class")
-abstract class EnterDesktopWithAppHandleMenu {
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class EnterDesktopWithAppHandleMenu {
 
     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
     private val tapl = LauncherInstrumentation()
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/EnterDesktopWithDrag.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt
similarity index 87%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/EnterDesktopWithDrag.kt
rename to libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt
index fe139d2..0f0d2df 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/EnterDesktopWithDrag.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.desktopmode.scenarios
+package com.android.wm.shell.scenarios
 
+import android.platform.test.annotations.Postsubmit
 import android.app.Instrumentation
 import android.tools.NavBar
 import android.tools.Rotation
@@ -26,17 +27,18 @@
 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.flicker.service.common.Utils
+import com.android.wm.shell.Utils
 import org.junit.After
 import org.junit.Assume
 import org.junit.Before
-import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
 
-
-@Ignore("Base Test Class")
-abstract class EnterDesktopWithDrag
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class EnterDesktopWithDrag
 @JvmOverloads
 constructor(val rotation: Rotation = Rotation.ROTATION_0) {
 
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ExitDesktopWithDragToTopDragZone.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt
similarity index 87%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ExitDesktopWithDragToTopDragZone.kt
rename to libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt
index 0b6c9af..533be88 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ExitDesktopWithDragToTopDragZone.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.desktopmode.scenarios
+package com.android.wm.shell.scenarios
 
+import android.platform.test.annotations.Postsubmit
 import android.app.Instrumentation
 import android.tools.NavBar
 import android.tools.Rotation
@@ -26,17 +27,18 @@
 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.flicker.service.common.Utils
+import com.android.wm.shell.Utils
 import org.junit.After
 import org.junit.Assume
 import org.junit.Before
-import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
 
-
-@Ignore("Base Test Class")
-abstract class ExitDesktopWithDragToTopDragZone
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class ExitDesktopWithDragToTopDragZone
 @JvmOverloads
 constructor(val rotation: Rotation = Rotation.ROTATION_0) {
 
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/MaximizeAppWindow.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt
similarity index 87%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/MaximizeAppWindow.kt
rename to libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt
index 20e2167c..e3660fe 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/MaximizeAppWindow.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.desktopmode.scenarios
+package com.android.wm.shell.scenarios
 
+import android.platform.test.annotations.Postsubmit
 import android.app.Instrumentation
 import android.tools.NavBar
 import android.tools.Rotation
@@ -26,17 +27,17 @@
 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.flicker.service.common.Utils
+import com.android.wm.shell.Utils
 import org.junit.After
 import org.junit.Assume
 import org.junit.Before
-import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
-
-/** Base scenario test for maximize app window CUJ in desktop mode. */
-@Ignore("Base Test Class")
-abstract class MaximizeAppWindow
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class MaximizeAppWindow
 {
     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
     private val tapl = LauncherInstrumentation()
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/MinimizeWindowOnAppOpen.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeWindowOnAppOpen.kt
similarity index 91%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/MinimizeWindowOnAppOpen.kt
rename to libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeWindowOnAppOpen.kt
index c847710..b86765e 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/MinimizeWindowOnAppOpen.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeWindowOnAppOpen.kt
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.desktopmode.scenarios
+package com.android.wm.shell.scenarios
 
+import android.platform.test.annotations.Postsubmit
 import android.app.Instrumentation
 import android.tools.traces.parsers.WindowManagerStateHelper
 import androidx.test.platform.app.InstrumentationRegistry
@@ -31,16 +32,17 @@
 import org.junit.After
 import org.junit.Assume
 import org.junit.Before
-import org.junit.Ignore
 import org.junit.Test
-
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
 /**
  * Base scenario test for minimizing the least recently used window when a new window is opened
  * above the window limit. For tangor devices, which this test currently runs on, the window limit
  * is 4.
  */
-@Ignore("Base Test Class")
-abstract class MinimizeWindowOnAppOpen()
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class MinimizeWindowOnAppOpen()
 {
     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
     private val tapl = LauncherInstrumentation()
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ResizeAppWithCornerResize.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithCornerResize.kt
similarity index 88%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ResizeAppWithCornerResize.kt
rename to libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithCornerResize.kt
index 136cf37..63e7387 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ResizeAppWithCornerResize.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithCornerResize.kt
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.desktopmode.scenarios
+package com.android.wm.shell.scenarios
 
+import android.platform.test.annotations.Postsubmit
 import android.app.Instrumentation
 import android.tools.NavBar
 import android.tools.Rotation
@@ -26,17 +27,18 @@
 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.flicker.service.common.Utils
+import com.android.wm.shell.Utils
 import org.junit.After
 import org.junit.Assume
 import org.junit.Before
-import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
 
-
-@Ignore("Base Test Class")
-abstract class ResizeAppWithCornerResize
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class ResizeAppWithCornerResize
 @JvmOverloads
 constructor(val rotation: Rotation = Rotation.ROTATION_0,
     val horizontalChange: Int = 50,
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/SwitchToOverviewFromDesktop.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SwitchToOverviewFromDesktop.kt
similarity index 88%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/SwitchToOverviewFromDesktop.kt
rename to libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SwitchToOverviewFromDesktop.kt
index b4cadf4..53e36e23 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/SwitchToOverviewFromDesktop.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SwitchToOverviewFromDesktop.kt
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.desktopmode.scenarios
+package com.android.wm.shell.scenarios
 
+import android.platform.test.annotations.Postsubmit
 import android.app.Instrumentation
 import android.tools.NavBar
 import android.tools.Rotation
@@ -26,21 +27,23 @@
 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.flicker.service.common.Utils
+import com.android.wm.shell.Utils
 import org.junit.After
 import org.junit.Assume
 import org.junit.Before
-import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
 
 /**
 * Base test for opening recent apps overview from desktop mode.
 *
 * Navigation mode can be passed as a constructor parameter, by default it is set to gesture navigation.
 */
-@Ignore("Base Test Class")
-abstract class SwitchToOverviewFromDesktop
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class SwitchToOverviewFromDesktop
 @JvmOverloads
 constructor(val navigationMode: NavBar = NavBar.MODE_GESTURAL) {
 
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/OWNERS b/libs/WindowManager/Shell/tests/e2e/splitscreen/OWNERS
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/splitscreen/OWNERS
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/OWNERS
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/Android.bp b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/Android.bp
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/splitscreen/Android.bp
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/Android.bp
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidManifest.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidManifest.xml
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidManifest.xml
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidManifest.xml
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidTestTemplate.xml
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/OWNERS b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/OWNERS
similarity index 100%
copy from libs/WindowManager/Shell/tests/flicker/splitscreen/OWNERS
copy to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/OWNERS
diff --git a/libs/WindowManager/Shell/tests/flicker/service/res/xml/network_security_config.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/res/xml/network_security_config.xml
similarity index 100%
copy from libs/WindowManager/Shell/tests/flicker/service/res/xml/network_security_config.xml
copy to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/res/xml/network_security_config.xml
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/MultipleShowImeRequestsInSplitScreen.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/MultipleShowImeRequestsInSplitScreen.kt
similarity index 73%
rename from libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/MultipleShowImeRequestsInSplitScreen.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/MultipleShowImeRequestsInSplitScreen.kt
index dad5db9..a9dba4a 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/MultipleShowImeRequestsInSplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/MultipleShowImeRequestsInSplitScreen.kt
@@ -17,11 +17,13 @@
 package com.android.wm.shell.flicker.splitscreen
 
 import android.platform.test.annotations.Presubmit
+import android.tools.NavBar
 import android.tools.Rotation
+import android.tools.ScenarioBuilder
 import android.tools.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.flicker.legacy.FlickerBuilder
 import android.tools.flicker.legacy.LegacyFlickerTest
-import android.tools.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.traces.SERVICE_TRACE_CONFIG
 import android.tools.traces.component.ComponentNameMatcher
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.splitscreen.benchmark.MultipleShowImeRequestsInSplitScreenBenchmark
@@ -35,7 +37,7 @@
 /**
  * Test quick switch between two split pairs.
  *
- * To run this test: `atest WMShellFlickerTestsSplitScreenGroup2:MultipleShowImeRequestsInSplitScreen`
+ * To run this test: `atest WMShellFlickerTestsSplitScreenGroupOther:MultipleShowImeRequestsInSplitScreen`
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
@@ -58,10 +60,22 @@
             }
 
     companion object {
+        private fun createFlickerTest(
+            navBarMode: NavBar
+        ) = LegacyFlickerTest(ScenarioBuilder()
+            .withStartRotation(Rotation.ROTATION_0)
+            .withEndRotation(Rotation.ROTATION_0)
+            .withNavBarMode(navBarMode), resultReaderProvider = { scenario ->
+            android.tools.flicker.datastore.CachedResultReader(
+                scenario, SERVICE_TRACE_CONFIG
+            )
+        })
+
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams() = LegacyFlickerTestFactory.nonRotationTests(
-                supportedRotations = listOf(Rotation.ROTATION_0)
+        fun getParams() = listOf(
+            createFlickerTest(NavBar.MODE_GESTURAL),
+            createFlickerTest(NavBar.MODE_3BUTTON)
         )
     }
-}
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt
similarity index 98%
rename from libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt
index d349988..3018b56 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
-import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.tools.NavBar
 import android.tools.flicker.junit.FlickerParametersRunnerFactory
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/MultipleShowImeRequestsInSplitScreenBenchmark.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/MultipleShowImeRequestsInSplitScreenBenchmark.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/MultipleShowImeRequestsInSplitScreenBenchmark.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/MultipleShowImeRequestsInSplitScreenBenchmark.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SplitScreenBase.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/SplitScreenBase.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SplitScreenBase.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/SplitScreenBase.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/trace_config/trace_config.textproto b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/trace_config/trace_config.textproto
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/splitscreen/trace_config/trace_config.textproto
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/trace_config/trace_config.textproto
diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/Android.bp b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/Android.bp
new file mode 100644
index 0000000..dd0018a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/Android.bp
@@ -0,0 +1,41 @@
+//
+// 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 {
+    // 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 {
+    name: "WMShellFlickerServiceTests",
+    defaults: ["WMShellFlickerTestsDefault"],
+    manifest: "AndroidManifest.xml",
+    test_config_template: "AndroidTestTemplate.xml",
+    srcs: ["src/**/*.kt"],
+    static_libs: [
+        "WMShellFlickerTestsBase",
+        "WMShellScenariosSplitScreen",
+        "WMShellTestUtils",
+    ],
+    data: [
+        ":FlickerTestApp",
+        "trace_config/*",
+    ],
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/AndroidManifest.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidManifest.xml
similarity index 97%
rename from libs/WindowManager/Shell/tests/flicker/service/AndroidManifest.xml
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidManifest.xml
index d54b694..662e7f3 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidManifest.xml
@@ -16,7 +16,7 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           xmlns:tools="http://schemas.android.com/tools"
-          package="com.android.wm.shell.flicker.service">
+          package="com.android.wm.shell">
 
     <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/>
     <!-- Read and write traces from external storage -->
@@ -71,7 +71,7 @@
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.wm.shell.flicker.service"
+                     android:targetPackage="com.android.wm.shell"
                      android:label="WindowManager Flicker Service Tests">
     </instrumentation>
 </manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/service/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml
similarity index 74%
copy from libs/WindowManager/Shell/tests/flicker/service/AndroidTestTemplate.xml
copy to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml
index a66dfb4..6c903a2 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml
@@ -14,7 +14,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<configuration description="Runs WindowManager Shell Flicker Tests {MODULE}">
+<configuration description="WMShell Platinum Tests {MODULE}">
     <option name="test-tag" value="FlickerTests"/>
     <!-- Needed for storing the perfetto trace files in the sdcard/test_results-->
     <option name="isolated-storage" value="false"/>
@@ -59,37 +59,15 @@
         <option name="test-file-name" value="{MODULE}.apk"/>
         <option name="test-file-name" value="FlickerTestApp.apk"/>
     </target_preparer>
-    <!-- Enable mocking GPS location by the test app -->
-    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
-        <option name="run-command"
-                value="appops set com.android.wm.shell.flicker.pip.apps android:mock_location allow"/>
-        <option name="teardown-command"
-                value="appops set com.android.wm.shell.flicker.pip.apps android:mock_location deny"/>
-    </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.textproto"
-                value="/data/misc/perfetto-traces/trace_config.textproto"
-        />
-        <!--Install the content provider automatically when we push some file in sdcard folder.-->
-        <!--Needed to avoid the installation during the test suite.-->
-        <option name="push-file" key="trace_config.textproto" value="/sdcard/sample.textproto"/>
-    </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest">
         <option name="package" value="{PACKAGE}"/>
         <option name="shell-timeout" value="6600s"/>
         <option name="test-timeout" value="6000s"/>
         <option name="hidden-api-checks" value="false"/>
-        <option name="device-listeners" value="android.device.collectors.PerfettoListener"/>
-        <!-- 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"
-        />
+        <option name="device-listeners" value="android.tools.collectors.DefaultUITraceListener"/>
+        <!-- DefaultUITraceListener args -->
         <option name="instrumentation-arg" key="per_run" value="true"/>
         <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/>
     </test>
@@ -97,7 +75,7 @@
     <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
         <option name="pull-pattern-keys" value="perfetto_file_path"/>
         <option name="directory-keys"
-                value="/data/user/0/com.android.wm.shell.flicker.service/files"/>
+                value="/data/user/0/com.android.wm.shell/files"/>
         <option name="collect-on-run-ended-only" value="true"/>
         <option name="clean-up" value="true"/>
     </metrics_collector>
diff --git a/libs/WindowManager/Shell/tests/flicker/service/res/xml/network_security_config.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/res/xml/network_security_config.xml
similarity index 100%
copy from libs/WindowManager/Shell/tests/flicker/service/res/xml/network_security_config.xml
copy to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/res/xml/network_security_config.xml
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/CopyContentInSplitGesturalNavLandscape.kt
similarity index 90%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/CopyContentInSplitGesturalNavLandscape.kt
index 1684a26..3cb9cf2 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/CopyContentInSplitGesturalNavLandscape.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.flicker
+package com.android.wm.shell.flicker
 
 import android.tools.Rotation
 import android.tools.flicker.FlickerConfig
@@ -23,7 +23,7 @@
 import android.tools.flicker.config.FlickerConfig
 import android.tools.flicker.config.FlickerServiceConfig
 import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
+import com.android.wm.shell.scenarios.CopyContentInSplit
 import org.junit.Test
 import org.junit.runner.RunWith
 
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/CopyContentInSplitGesturalNavPortrait.kt
similarity index 90%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/CopyContentInSplitGesturalNavPortrait.kt
index 3b5fad6..b27a8ae 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/CopyContentInSplitGesturalNavPortrait.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.flicker
+package com.android.wm.shell.flicker
 
 import android.tools.Rotation
 import android.tools.flicker.FlickerConfig
@@ -23,7 +23,7 @@
 import android.tools.flicker.config.FlickerConfig
 import android.tools.flicker.config.FlickerServiceConfig
 import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
+import com.android.wm.shell.scenarios.CopyContentInSplit
 import org.junit.Test
 import org.junit.runner.RunWith
 
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt
similarity index 90%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt
index 2b8a903..9388114 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.flicker
+package com.android.wm.shell.flicker
 
 import android.tools.Rotation
 import android.tools.flicker.FlickerConfig
@@ -23,7 +23,7 @@
 import android.tools.flicker.config.FlickerConfig
 import android.tools.flicker.config.FlickerServiceConfig
 import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
+import com.android.wm.shell.scenarios.DismissSplitScreenByDivider
 import org.junit.Test
 import org.junit.runner.RunWith
 
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt
similarity index 90%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt
index b284fe1..30ef492 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.flicker
+package com.android.wm.shell.flicker
 
 import android.tools.Rotation
 import android.tools.flicker.FlickerConfig
@@ -23,7 +23,7 @@
 import android.tools.flicker.config.FlickerConfig
 import android.tools.flicker.config.FlickerServiceConfig
 import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
+import com.android.wm.shell.scenarios.DismissSplitScreenByDivider
 import org.junit.Test
 import org.junit.runner.RunWith
 
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
similarity index 90%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
index a400ee4..059f967 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.flicker
+package com.android.wm.shell.flicker
 
 import android.tools.Rotation
 import android.tools.flicker.FlickerConfig
@@ -23,7 +23,7 @@
 import android.tools.flicker.config.FlickerConfig
 import android.tools.flicker.config.FlickerServiceConfig
 import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
+import com.android.wm.shell.scenarios.DismissSplitScreenByGoHome
 import org.junit.Test
 import org.junit.runner.RunWith
 
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
similarity index 90%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
index 7f5ee4c..0c6d546 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.flicker
+package com.android.wm.shell.flicker
 
 import android.tools.Rotation
 import android.tools.flicker.FlickerConfig
@@ -23,7 +23,7 @@
 import android.tools.flicker.config.FlickerConfig
 import android.tools.flicker.config.FlickerServiceConfig
 import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
+import com.android.wm.shell.scenarios.DismissSplitScreenByGoHome
 import org.junit.Test
 import org.junit.runner.RunWith
 
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/DragDividerToResizeGesturalNavLandscape.kt
similarity index 90%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/DragDividerToResizeGesturalNavLandscape.kt
index 1b075c4..14fb72b 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/DragDividerToResizeGesturalNavLandscape.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.flicker
+package com.android.wm.shell.flicker
 
 import android.tools.Rotation
 import android.tools.flicker.FlickerConfig
@@ -23,7 +23,7 @@
 import android.tools.flicker.config.FlickerConfig
 import android.tools.flicker.config.FlickerServiceConfig
 import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
+import com.android.wm.shell.scenarios.DragDividerToResize
 import org.junit.Test
 import org.junit.runner.RunWith
 
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/DragDividerToResizeGesturalNavPortrait.kt
similarity index 90%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/DragDividerToResizeGesturalNavPortrait.kt
index 6ca3737..9be61a5 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/DragDividerToResizeGesturalNavPortrait.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.flicker
+package com.android.wm.shell.flicker
 
 import android.tools.Rotation
 import android.tools.flicker.FlickerConfig
@@ -23,7 +23,7 @@
 import android.tools.flicker.config.FlickerConfig
 import android.tools.flicker.config.FlickerServiceConfig
 import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
+import com.android.wm.shell.scenarios.DragDividerToResize
 import org.junit.Test
 import org.junit.runner.RunWith
 
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
similarity index 90%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
index f7d231f0..c12d199 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.flicker
+package com.android.wm.shell.flicker
 
 import android.tools.Rotation
 import android.tools.flicker.FlickerConfig
@@ -23,7 +23,7 @@
 import android.tools.flicker.config.FlickerConfig
 import android.tools.flicker.config.FlickerServiceConfig
 import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
+import com.android.wm.shell.scenarios.EnterSplitScreenByDragFromAllApps
 import org.junit.Test
 import org.junit.runner.RunWith
 
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
similarity index 90%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
index ab819fa..11cd38a 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.flicker
+package com.android.wm.shell.flicker
 
 import android.tools.Rotation
 import android.tools.flicker.FlickerConfig
@@ -23,7 +23,7 @@
 import android.tools.flicker.config.FlickerConfig
 import android.tools.flicker.config.FlickerServiceConfig
 import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
+import com.android.wm.shell.scenarios.EnterSplitScreenByDragFromAllApps
 import org.junit.Test
 import org.junit.runner.RunWith
 
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
similarity index 90%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
index a6b732c..66d4bfa 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.flicker
+package com.android.wm.shell.flicker
 
 import android.tools.Rotation
 import android.tools.flicker.FlickerConfig
@@ -23,7 +23,7 @@
 import android.tools.flicker.config.FlickerConfig
 import android.tools.flicker.config.FlickerServiceConfig
 import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
+import com.android.wm.shell.scenarios.EnterSplitScreenByDragFromNotification
 import org.junit.Test
 import org.junit.runner.RunWith
 
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
similarity index 90%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
index 07e5f4b..f3a11eb 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.flicker
+package com.android.wm.shell.flicker
 
 import android.tools.Rotation
 import android.tools.flicker.FlickerConfig
@@ -23,7 +23,7 @@
 import android.tools.flicker.config.FlickerConfig
 import android.tools.flicker.config.FlickerServiceConfig
 import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
+import com.android.wm.shell.scenarios.EnterSplitScreenByDragFromNotification
 import org.junit.Test
 import org.junit.runner.RunWith
 
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
similarity index 90%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
index 2725694..327ecc3 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.flicker
+package com.android.wm.shell.flicker
 
 import android.tools.Rotation
 import android.tools.flicker.FlickerConfig
@@ -23,7 +23,7 @@
 import android.tools.flicker.config.FlickerConfig
 import android.tools.flicker.config.FlickerServiceConfig
 import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
+import com.android.wm.shell.scenarios.EnterSplitScreenByDragFromShortcut
 import org.junit.Test
 import org.junit.runner.RunWith
 
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
similarity index 90%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
index 58cc4d7..dd5a395 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.flicker
+package com.android.wm.shell.flicker
 
 import android.tools.Rotation
 import android.tools.flicker.FlickerConfig
@@ -23,7 +23,7 @@
 import android.tools.flicker.config.FlickerConfig
 import android.tools.flicker.config.FlickerServiceConfig
 import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
+import com.android.wm.shell.scenarios.EnterSplitScreenByDragFromShortcut
 import org.junit.Test
 import org.junit.runner.RunWith
 
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
similarity index 90%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
index 85897a1..8e7cf30 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.flicker
+package com.android.wm.shell.flicker
 
 import android.tools.Rotation
 import android.tools.flicker.FlickerConfig
@@ -23,7 +23,7 @@
 import android.tools.flicker.config.FlickerConfig
 import android.tools.flicker.config.FlickerServiceConfig
 import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
+import com.android.wm.shell.scenarios.EnterSplitScreenByDragFromTaskbar
 import org.junit.Test
 import org.junit.runner.RunWith
 
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
similarity index 90%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
index 891b6df..0324dac 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.flicker
+package com.android.wm.shell.flicker
 
 import android.tools.Rotation
 import android.tools.flicker.FlickerConfig
@@ -23,7 +23,7 @@
 import android.tools.flicker.config.FlickerConfig
 import android.tools.flicker.config.FlickerServiceConfig
 import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
+import com.android.wm.shell.scenarios.EnterSplitScreenByDragFromTaskbar
 import org.junit.Test
 import org.junit.runner.RunWith
 
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
similarity index 90%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
index 7983652..2fa141e 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.flicker
+package com.android.wm.shell.flicker
 
 import android.tools.Rotation
 import android.tools.flicker.FlickerConfig
@@ -23,7 +23,7 @@
 import android.tools.flicker.config.FlickerConfig
 import android.tools.flicker.config.FlickerServiceConfig
 import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
+import com.android.wm.shell.scenarios.EnterSplitScreenFromOverview
 import org.junit.Test
 import org.junit.runner.RunWith
 
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
similarity index 90%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
index 1bdea66..0176913 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.flicker
+package com.android.wm.shell.flicker
 
 import android.tools.Rotation
 import android.tools.flicker.FlickerConfig
@@ -23,7 +23,7 @@
 import android.tools.flicker.config.FlickerConfig
 import android.tools.flicker.config.FlickerServiceConfig
 import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
+import com.android.wm.shell.scenarios.EnterSplitScreenFromOverview
 import org.junit.Test
 import org.junit.runner.RunWith
 
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
similarity index 90%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
index bab0c0a..1db28dc 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.flicker
+package com.android.wm.shell.flicker
 
 import android.tools.Rotation
 import android.tools.flicker.FlickerConfig
@@ -23,7 +23,7 @@
 import android.tools.flicker.config.FlickerConfig
 import android.tools.flicker.config.FlickerServiceConfig
 import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
+import com.android.wm.shell.scenarios.SwitchAppByDoubleTapDivider
 import org.junit.Test
 import org.junit.runner.RunWith
 
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
similarity index 90%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
index 17a59ab..c69167b 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.flicker
+package com.android.wm.shell.flicker
 
 import android.tools.Rotation
 import android.tools.flicker.FlickerConfig
@@ -23,7 +23,7 @@
 import android.tools.flicker.config.FlickerConfig
 import android.tools.flicker.config.FlickerServiceConfig
 import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
+import com.android.wm.shell.scenarios.SwitchAppByDoubleTapDivider
 import org.junit.Test
 import org.junit.runner.RunWith
 
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
similarity index 90%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
index 2c36d64..602283a 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.flicker
+package com.android.wm.shell.flicker
 
 import android.tools.Rotation
 import android.tools.flicker.FlickerConfig
@@ -23,7 +23,7 @@
 import android.tools.flicker.config.FlickerConfig
 import android.tools.flicker.config.FlickerServiceConfig
 import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
+import com.android.wm.shell.scenarios.SwitchBackToSplitFromAnotherApp
 import org.junit.Test
 import org.junit.runner.RunWith
 
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
similarity index 90%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
index 6e91d04..7cc14e0 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.flicker
+package com.android.wm.shell.flicker
 
 import android.tools.Rotation
 import android.tools.flicker.FlickerConfig
@@ -23,7 +23,7 @@
 import android.tools.flicker.config.FlickerConfig
 import android.tools.flicker.config.FlickerServiceConfig
 import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
+import com.android.wm.shell.scenarios.SwitchBackToSplitFromAnotherApp
 import org.junit.Test
 import org.junit.runner.RunWith
 
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
similarity index 90%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
index a921b46..daf6547 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.flicker
+package com.android.wm.shell.flicker
 
 import android.tools.Rotation
 import android.tools.flicker.FlickerConfig
@@ -23,7 +23,7 @@
 import android.tools.flicker.config.FlickerConfig
 import android.tools.flicker.config.FlickerServiceConfig
 import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
+import com.android.wm.shell.scenarios.SwitchBackToSplitFromHome
 import org.junit.Test
 import org.junit.runner.RunWith
 
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
similarity index 90%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
index 05f8912..b0f5e65 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.flicker
+package com.android.wm.shell.flicker
 
 import android.tools.Rotation
 import android.tools.flicker.FlickerConfig
@@ -23,7 +23,7 @@
 import android.tools.flicker.config.FlickerConfig
 import android.tools.flicker.config.FlickerServiceConfig
 import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
+import com.android.wm.shell.scenarios.SwitchBackToSplitFromHome
 import org.junit.Test
 import org.junit.runner.RunWith
 
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
similarity index 90%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
index 1ae1f53..88fa783 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.flicker
+package com.android.wm.shell.flicker
 
 import android.tools.Rotation
 import android.tools.flicker.FlickerConfig
@@ -23,7 +23,7 @@
 import android.tools.flicker.config.FlickerConfig
 import android.tools.flicker.config.FlickerServiceConfig
 import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
+import com.android.wm.shell.scenarios.SwitchBackToSplitFromRecent
 import org.junit.Test
 import org.junit.runner.RunWith
 
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
similarity index 90%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
index e14ca55..aa36f44 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.flicker
+package com.android.wm.shell.flicker
 
 import android.tools.Rotation
 import android.tools.flicker.FlickerConfig
@@ -23,7 +23,7 @@
 import android.tools.flicker.config.FlickerConfig
 import android.tools.flicker.config.FlickerServiceConfig
 import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
+import com.android.wm.shell.scenarios.SwitchBackToSplitFromRecent
 import org.junit.Test
 import org.junit.runner.RunWith
 
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt
similarity index 90%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt
index ce0c4c4..292f413 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.flicker
+package com.android.wm.shell.flicker
 
 import android.tools.Rotation
 import android.tools.flicker.FlickerConfig
@@ -23,7 +23,7 @@
 import android.tools.flicker.config.FlickerConfig
 import android.tools.flicker.config.FlickerServiceConfig
 import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
+import com.android.wm.shell.scenarios.SwitchBetweenSplitPairs
 import org.junit.Test
 import org.junit.runner.RunWith
 
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt
similarity index 90%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt
index 5a8d2d5..865958f 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.flicker
+package com.android.wm.shell.flicker
 
 import android.tools.Rotation
 import android.tools.flicker.FlickerConfig
@@ -23,7 +23,7 @@
 import android.tools.flicker.config.FlickerConfig
 import android.tools.flicker.config.FlickerServiceConfig
 import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
+import com.android.wm.shell.scenarios.SwitchBetweenSplitPairs
 import org.junit.Test
 import org.junit.runner.RunWith
 
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
similarity index 90%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
index d442615..6c36e84 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.flicker
+package com.android.wm.shell.flicker
 
 import android.tools.flicker.FlickerConfig
 import android.tools.flicker.annotation.ExpectedScenarios
@@ -22,7 +22,7 @@
 import android.tools.flicker.config.FlickerConfig
 import android.tools.flicker.config.FlickerServiceConfig
 import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
+import com.android.wm.shell.scenarios.UnlockKeyguardToSplitScreen
 import org.junit.Test
 import org.junit.runner.RunWith
 
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
similarity index 90%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
index ddc8a06..61ccd36 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.flicker
+package com.android.wm.shell.flicker
 
 import android.tools.flicker.FlickerConfig
 import android.tools.flicker.annotation.ExpectedScenarios
@@ -22,7 +22,7 @@
 import android.tools.flicker.config.FlickerConfig
 import android.tools.flicker.config.FlickerServiceConfig
 import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
+import com.android.wm.shell.scenarios.UnlockKeyguardToSplitScreen
 import org.junit.Test
 import org.junit.runner.RunWith
 
diff --git a/libs/WindowManager/Shell/tests/flicker/service/Android.bp b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/Android.bp
similarity index 67%
rename from libs/WindowManager/Shell/tests/flicker/service/Android.bp
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/Android.bp
index a5bc261..90210b1 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/Android.bp
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/Android.bp
@@ -26,9 +26,7 @@
 filegroup {
     name: "WMShellFlickerServicePlatinumTests-src",
     srcs: [
-        "src/**/platinum/*.kt",
-        "src/**/scenarios/*.kt",
-        "src/**/common/*.kt",
+        "src/**/*.kt",
     ],
 }
 
@@ -43,33 +41,42 @@
     ],
     static_libs: [
         "wm-shell-flicker-utils",
+        "WMShellScenariosSplitScreen",
     ],
 }
 
 android_test {
-    name: "WMShellFlickerServiceTests",
-    defaults: ["WMShellFlickerTestsDefault"],
-    manifest: "AndroidManifest.xml",
-    package_name: "com.android.wm.shell.flicker.service",
-    instrumentation_target_package: "com.android.wm.shell.flicker.service",
-    test_config_template: "AndroidTestTemplate.xml",
-    srcs: ["src/**/*.kt"],
-    static_libs: ["WMShellFlickerTestsBase"],
-    data: ["trace_config/*"],
-}
-
-android_test {
     name: "WMShellFlickerServicePlatinumTests",
-    defaults: ["WMShellFlickerTestsDefault"],
+    platform_apis: true,
+    certificate: "platform",
+    optimize: {
+        enabled: false,
+    },
     manifest: "AndroidManifest.xml",
-    package_name: "com.android.wm.shell.flicker.service",
-    instrumentation_target_package: "com.android.wm.shell.flicker.service",
     test_config_template: "AndroidTestTemplate.xml",
     test_suites: [
         "device-tests",
         "device-platinum-tests",
     ],
     srcs: [":WMShellFlickerServicePlatinumTests-src"],
-    static_libs: ["WMShellFlickerTestsBase"],
-    data: ["trace_config/*"],
+    static_libs: [
+        "WMShellFlickerTestsBase",
+        "WMShellScenariosSplitScreen",
+        "WMShellTestUtils",
+        "ui-trace-collector",
+        "collector-device-lib",
+        "wm-shell-flicker-utils",
+        "androidx.test.ext.junit",
+        "flickertestapplib",
+        "flickerlib-helpers",
+        "flickerlib-trace_processor_shell",
+        "platform-test-annotations",
+        "wm-flicker-common-app-helpers",
+        "launcher-helper-lib",
+        "launcher-aosp-tapl",
+    ],
+    data: [
+        ":FlickerTestApp",
+        "trace_config/*",
+    ],
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/service/AndroidManifest.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidManifest.xml
similarity index 97%
copy from libs/WindowManager/Shell/tests/flicker/service/AndroidManifest.xml
copy to libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidManifest.xml
index d54b694..662e7f3 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidManifest.xml
@@ -16,7 +16,7 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           xmlns:tools="http://schemas.android.com/tools"
-          package="com.android.wm.shell.flicker.service">
+          package="com.android.wm.shell">
 
     <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/>
     <!-- Read and write traces from external storage -->
@@ -71,7 +71,7 @@
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.wm.shell.flicker.service"
+                     android:targetPackage="com.android.wm.shell"
                      android:label="WindowManager Flicker Service Tests">
     </instrumentation>
 </manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/service/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml
similarity index 74%
copy from libs/WindowManager/Shell/tests/flicker/service/AndroidTestTemplate.xml
copy to libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml
index a66dfb4..6c903a2 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml
@@ -14,7 +14,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<configuration description="Runs WindowManager Shell Flicker Tests {MODULE}">
+<configuration description="WMShell Platinum Tests {MODULE}">
     <option name="test-tag" value="FlickerTests"/>
     <!-- Needed for storing the perfetto trace files in the sdcard/test_results-->
     <option name="isolated-storage" value="false"/>
@@ -59,37 +59,15 @@
         <option name="test-file-name" value="{MODULE}.apk"/>
         <option name="test-file-name" value="FlickerTestApp.apk"/>
     </target_preparer>
-    <!-- Enable mocking GPS location by the test app -->
-    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
-        <option name="run-command"
-                value="appops set com.android.wm.shell.flicker.pip.apps android:mock_location allow"/>
-        <option name="teardown-command"
-                value="appops set com.android.wm.shell.flicker.pip.apps android:mock_location deny"/>
-    </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.textproto"
-                value="/data/misc/perfetto-traces/trace_config.textproto"
-        />
-        <!--Install the content provider automatically when we push some file in sdcard folder.-->
-        <!--Needed to avoid the installation during the test suite.-->
-        <option name="push-file" key="trace_config.textproto" value="/sdcard/sample.textproto"/>
-    </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest">
         <option name="package" value="{PACKAGE}"/>
         <option name="shell-timeout" value="6600s"/>
         <option name="test-timeout" value="6000s"/>
         <option name="hidden-api-checks" value="false"/>
-        <option name="device-listeners" value="android.device.collectors.PerfettoListener"/>
-        <!-- 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"
-        />
+        <option name="device-listeners" value="android.tools.collectors.DefaultUITraceListener"/>
+        <!-- DefaultUITraceListener args -->
         <option name="instrumentation-arg" key="per_run" value="true"/>
         <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/>
     </test>
@@ -97,7 +75,7 @@
     <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
         <option name="pull-pattern-keys" value="perfetto_file_path"/>
         <option name="directory-keys"
-                value="/data/user/0/com.android.wm.shell.flicker.service/files"/>
+                value="/data/user/0/com.android.wm.shell/files"/>
         <option name="collect-on-run-ended-only" value="true"/>
         <option name="clean-up" value="true"/>
     </metrics_collector>
diff --git a/libs/WindowManager/Shell/tests/flicker/service/res/xml/network_security_config.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/res/xml/network_security_config.xml
similarity index 100%
copy from libs/WindowManager/Shell/tests/flicker/service/res/xml/network_security_config.xml
copy to libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/res/xml/network_security_config.xml
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/CopyContentInSplitGesturalNavLandscape.kt
similarity index 86%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/CopyContentInSplitGesturalNavLandscape.kt
index 64293b2..4c2ca67 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/CopyContentInSplitGesturalNavLandscape.kt
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.platinum
+package com.android.wm.shell
 
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.Rotation
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
+import com.android.wm.shell.scenarios.CopyContentInSplit
 import org.junit.Test
 
 open class CopyContentInSplitGesturalNavLandscape : CopyContentInSplit(Rotation.ROTATION_90) {
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/CopyContentInSplitGesturalNavPortrait.kt
similarity index 86%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/CopyContentInSplitGesturalNavPortrait.kt
index 517ba2d..0cca310 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/CopyContentInSplitGesturalNavPortrait.kt
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.platinum
+package com.android.wm.shell
 
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.Rotation
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
+import com.android.wm.shell.scenarios.CopyContentInSplit
 import org.junit.Test
 
 open class CopyContentInSplitGesturalNavPortrait : CopyContentInSplit(Rotation.ROTATION_0) {
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/DismissSplitScreenByDividerGesturalNavLandscape.kt
similarity index 86%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/DismissSplitScreenByDividerGesturalNavLandscape.kt
index 1bafe3b..7aa62cf 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/DismissSplitScreenByDividerGesturalNavLandscape.kt
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.platinum
+package com.android.wm.shell
 
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.Rotation
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
+import com.android.wm.shell.scenarios.DismissSplitScreenByDivider
 import org.junit.Test
 
 open class DismissSplitScreenByDividerGesturalNavLandscape :
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/DismissSplitScreenByDividerGesturalNavPortrait.kt
similarity index 86%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/DismissSplitScreenByDividerGesturalNavPortrait.kt
index fd0100f..de11fc6 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/DismissSplitScreenByDividerGesturalNavPortrait.kt
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.platinum
+package com.android.wm.shell
 
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.Rotation
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
+import com.android.wm.shell.scenarios.DismissSplitScreenByDivider
 import org.junit.Test
 
 open class DismissSplitScreenByDividerGesturalNavPortrait :
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
similarity index 86%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
index 850b3d8..daa6aac 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.platinum
+package com.android.wm.shell
 
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.Rotation
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
+import com.android.wm.shell.scenarios.DismissSplitScreenByGoHome
 import org.junit.Test
 
 open class DismissSplitScreenByGoHomeGesturalNavLandscape :
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
similarity index 86%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
index 0b752bf..ff57d00 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.platinum
+package com.android.wm.shell
 
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.Rotation
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
+import com.android.wm.shell.scenarios.DismissSplitScreenByGoHome
 import org.junit.Test
 
 open class DismissSplitScreenByGoHomeGesturalNavPortrait :
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/DragDividerToResizeGesturalNavLandscape.kt
similarity index 86%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/DragDividerToResizeGesturalNavLandscape.kt
index 3c52aa7..0ac19c8 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/DragDividerToResizeGesturalNavLandscape.kt
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.platinum
+package com.android.wm.shell
 
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.Rotation
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
+import com.android.wm.shell.scenarios.DragDividerToResize
 import org.junit.Test
 
 open class DragDividerToResizeGesturalNavLandscape : DragDividerToResize(Rotation.ROTATION_90) {
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/DragDividerToResizeGesturalNavPortrait.kt
similarity index 86%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/DragDividerToResizeGesturalNavPortrait.kt
index c2e21b8..5713602e 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/DragDividerToResizeGesturalNavPortrait.kt
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.platinum
+package com.android.wm.shell
 
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.Rotation
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
+import com.android.wm.shell.scenarios.DragDividerToResize
 import org.junit.Test
 
 open class DragDividerToResizeGesturalNavPortrait : DragDividerToResize(Rotation.ROTATION_0) {
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
similarity index 86%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
index bf85ab4..d7333f1a 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.platinum
+package com.android.wm.shell
 
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.Rotation
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
+import com.android.wm.shell.scenarios.EnterSplitScreenByDragFromAllApps
 import org.junit.Test
 
 open class EnterSplitScreenByDragFromAllAppsGesturalNavLandscape :
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
similarity index 86%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
index 0ac4ca2..e29a140 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.platinum
+package com.android.wm.shell
 
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.Rotation
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
+import com.android.wm.shell.scenarios.EnterSplitScreenByDragFromAllApps
 import org.junit.Test
 
 open class EnterSplitScreenByDragFromAllAppsGesturalNavPortrait :
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
similarity index 86%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
index 80bd088..9ccccb1 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.platinum
+package com.android.wm.shell
 
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.Rotation
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
+import com.android.wm.shell.scenarios.EnterSplitScreenByDragFromNotification
 import org.junit.Test
 
 open class EnterSplitScreenByDragFromNotificationGesturalNavLandscape :
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
similarity index 86%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
index 0dffb4a..87a4d08 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.platinum
+package com.android.wm.shell
 
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.Rotation
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
+import com.android.wm.shell.scenarios.EnterSplitScreenByDragFromNotification
 import org.junit.Test
 
 open class EnterSplitScreenByDragFromNotificationGesturalNavPortrait :
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
similarity index 86%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
index b721f2f..559652c 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.platinum
+package com.android.wm.shell
 
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.Rotation
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
+import com.android.wm.shell.scenarios.EnterSplitScreenByDragFromShortcut
 import org.junit.Test
 
 open class EnterSplitScreenByDragFromShortcutGesturalNavLandscape :
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
similarity index 86%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
index 22cbc77..bcb8e0c 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.platinum
+package com.android.wm.shell
 
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.Rotation
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
+import com.android.wm.shell.scenarios.EnterSplitScreenByDragFromShortcut
 import org.junit.Test
 
 open class EnterSplitScreenByDragFromShortcutGesturalNavPortrait :
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
similarity index 86%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
index ac0f9e2..39e0fed 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.platinum
+package com.android.wm.shell
 
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.Rotation
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
+import com.android.wm.shell.scenarios.EnterSplitScreenByDragFromTaskbar
 import org.junit.Test
 
 open class EnterSplitScreenByDragFromTaskbarGesturalNavLandscape :
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
similarity index 86%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
index f7a229d..6431629 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.platinum
+package com.android.wm.shell
 
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.Rotation
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
+import com.android.wm.shell.scenarios.EnterSplitScreenByDragFromTaskbar
 import org.junit.Test
 
 open class EnterSplitScreenByDragFromTaskbarGesturalNavPortrait :
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
similarity index 86%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
index 6dbbcb0..2093424 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.platinum
+package com.android.wm.shell
 
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.Rotation
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
+import com.android.wm.shell.scenarios.EnterSplitScreenFromOverview
 import org.junit.Test
 
 open class EnterSplitScreenFromOverviewGesturalNavLandscape :
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
similarity index 86%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
index bd69ea9..f89259d 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.platinum
+package com.android.wm.shell
 
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.Rotation
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
+import com.android.wm.shell.scenarios.EnterSplitScreenFromOverview
 import org.junit.Test
 
 open class EnterSplitScreenFromOverviewGesturalNavPortrait :
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
similarity index 86%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
index 404b96f..e5aff0c 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.platinum
+package com.android.wm.shell
 
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.Rotation
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
+import com.android.wm.shell.scenarios.SwitchAppByDoubleTapDivider
 import org.junit.Test
 
 open class SwitchAppByDoubleTapDividerGesturalNavLandscape :
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
similarity index 86%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
index a79687d..defade9 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.platinum
+package com.android.wm.shell
 
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.Rotation
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
+import com.android.wm.shell.scenarios.SwitchAppByDoubleTapDivider
 import org.junit.Test
 
 open class SwitchAppByDoubleTapDividerGesturalNavPortrait :
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
similarity index 86%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
index b52eb4c..e28deca 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.platinum
+package com.android.wm.shell
 
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.Rotation
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
+import com.android.wm.shell.scenarios.SwitchBackToSplitFromAnotherApp
 import org.junit.Test
 
 open class SwitchBackToSplitFromAnotherAppGesturalNavLandscape :
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
similarity index 86%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
index d79620c..99fb06c 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.platinum
+package com.android.wm.shell
 
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.Rotation
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
+import com.android.wm.shell.scenarios.SwitchBackToSplitFromAnotherApp
 import org.junit.Test
 
 open class SwitchBackToSplitFromAnotherAppGesturalNavPortrait :
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
similarity index 86%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
index d27bfa1..7045e66 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.platinum
+package com.android.wm.shell
 
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.Rotation
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
+import com.android.wm.shell.scenarios.SwitchBackToSplitFromHome
 import org.junit.Test
 
 open class SwitchBackToSplitFromHomeGesturalNavLandscape :
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
similarity index 86%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
index 3c7d4d4..b2da052 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.platinum
+package com.android.wm.shell
 
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.Rotation
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
+import com.android.wm.shell.scenarios.SwitchBackToSplitFromHome
 import org.junit.Test
 
 open class SwitchBackToSplitFromHomeGesturalNavPortrait :
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
similarity index 86%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
index 26a2034..04d7f62 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.platinum
+package com.android.wm.shell
 
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.Rotation
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
+import com.android.wm.shell.scenarios.SwitchBackToSplitFromRecent
 import org.junit.Test
 
 open class SwitchBackToSplitFromRecentGesturalNavLandscape :
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
similarity index 86%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
index 5154b35..bc36fb7 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.platinum
+package com.android.wm.shell
 
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.Rotation
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
+import com.android.wm.shell.scenarios.SwitchBackToSplitFromRecent
 import org.junit.Test
 
 open class SwitchBackToSplitFromRecentGesturalNavPortrait :
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchBetweenSplitPairsGesturalNavLandscape.kt
similarity index 86%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchBetweenSplitPairsGesturalNavLandscape.kt
index 86451c5..ceda4da 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchBetweenSplitPairsGesturalNavLandscape.kt
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.platinum
+package com.android.wm.shell
 
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.Rotation
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
+import com.android.wm.shell.scenarios.SwitchBetweenSplitPairs
 import org.junit.Test
 
 open class SwitchBetweenSplitPairsGesturalNavLandscape :
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchBetweenSplitPairsGesturalNavPortrait.kt
similarity index 86%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchBetweenSplitPairsGesturalNavPortrait.kt
index baf72b4..365c5cc 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchBetweenSplitPairsGesturalNavPortrait.kt
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.platinum
+package com.android.wm.shell
 
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.Rotation
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
+import com.android.wm.shell.scenarios.SwitchBetweenSplitPairs
 import org.junit.Test
 
 open class SwitchBetweenSplitPairsGesturalNavPortrait :
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
similarity index 87%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
index 9caab9b..a866297 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.platinum
+package com.android.wm.shell
 
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
+import com.android.wm.shell.scenarios.UnlockKeyguardToSplitScreen
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.BlockJUnit4ClassRunner
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
similarity index 87%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
index bf484e5..6d59001 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.platinum
+package com.android.wm.shell
 
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
+import com.android.wm.shell.scenarios.UnlockKeyguardToSplitScreen
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.BlockJUnit4ClassRunner
diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/Android.bp b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/Android.bp
new file mode 100644
index 0000000..60c7de7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/Android.bp
@@ -0,0 +1,47 @@
+//
+// 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 {
+    // 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"],
+}
+
+java_library {
+    name: "WMShellScenariosSplitScreen",
+    platform_apis: true,
+    optimize: {
+        enabled: false,
+    },
+    srcs: ["src/**/*.kt"],
+    static_libs: [
+        "WMShellFlickerTestsBase",
+        "WMShellTestUtils",
+        "wm-shell-flicker-utils",
+        "androidx.test.ext.junit",
+        "flickertestapplib",
+        "flickerlib-helpers",
+        "flickerlib-trace_processor_shell",
+        "platform-test-annotations",
+        "wm-flicker-common-app-helpers",
+        "wm-flicker-common-assertions",
+        "launcher-helper-lib",
+        "launcher-aosp-tapl",
+    ],
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/CopyContentInSplit.kt
similarity index 92%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/CopyContentInSplit.kt
index 6171074..ba46542 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/CopyContentInSplit.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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.scenarios
+package com.android.wm.shell.scenarios
 
 import android.app.Instrumentation
 import android.tools.NavBar
@@ -23,7 +23,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.common.Utils
+import com.android.wm.shell.Utils
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByDivider.kt
similarity index 93%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByDivider.kt
index c1a8ee7..d774a31 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByDivider.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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.scenarios
+package com.android.wm.shell.scenarios
 
 import android.app.Instrumentation
 import android.tools.NavBar
@@ -23,7 +23,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.common.Utils
+import com.android.wm.shell.Utils
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByGoHome.kt
similarity index 92%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByGoHome.kt
index 600855a..5aa1619 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByGoHome.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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.scenarios
+package com.android.wm.shell.scenarios
 
 import android.app.Instrumentation
 import android.tools.NavBar
@@ -23,7 +23,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.common.Utils
+import com.android.wm.shell.Utils
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DragDividerToResize.kt
similarity index 92%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DragDividerToResize.kt
index b5a6d83..668f367 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DragDividerToResize.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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.scenarios
+package com.android.wm.shell.scenarios
 
 import android.app.Instrumentation
 import android.tools.NavBar
@@ -23,7 +23,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.common.Utils
+import com.android.wm.shell.Utils
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/EnterSplitScreenByDragFromAllApps.kt
similarity index 93%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/EnterSplitScreenByDragFromAllApps.kt
index a189325..06c7b9b 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/EnterSplitScreenByDragFromAllApps.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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.scenarios
+package com.android.wm.shell.scenarios
 
 import android.app.Instrumentation
 import android.tools.NavBar
@@ -24,7 +24,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.common.Utils
+import com.android.wm.shell.Utils
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Assume
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/EnterSplitScreenByDragFromNotification.kt
similarity index 94%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/EnterSplitScreenByDragFromNotification.kt
index bcd0f12..96b22bf 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/EnterSplitScreenByDragFromNotification.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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.scenarios
+package com.android.wm.shell.scenarios
 
 import android.app.Instrumentation
 import android.tools.NavBar
@@ -25,7 +25,7 @@
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
 import com.android.server.wm.flicker.helpers.MultiWindowUtils
-import com.android.wm.shell.flicker.service.common.Utils
+import com.android.wm.shell.Utils
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Assume
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/EnterSplitScreenByDragFromShortcut.kt
similarity index 94%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/EnterSplitScreenByDragFromShortcut.kt
index 3f07be0..9e05b63 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/EnterSplitScreenByDragFromShortcut.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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.scenarios
+package com.android.wm.shell.scenarios
 
 import android.app.Instrumentation
 import android.tools.NavBar
@@ -24,7 +24,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.common.Utils
+import com.android.wm.shell.Utils
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Assume
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/EnterSplitScreenByDragFromTaskbar.kt
similarity index 93%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/EnterSplitScreenByDragFromTaskbar.kt
index 5328013..9090055 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/EnterSplitScreenByDragFromTaskbar.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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.scenarios
+package com.android.wm.shell.scenarios
 
 import android.app.Instrumentation
 import android.tools.NavBar
@@ -23,7 +23,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.common.Utils
+import com.android.wm.shell.Utils
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Assume
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/EnterSplitScreenFromOverview.kt
similarity index 92%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/EnterSplitScreenFromOverview.kt
index be4035d..d5cc92e 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/EnterSplitScreenFromOverview.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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.scenarios
+package com.android.wm.shell.scenarios
 
 import android.app.Instrumentation
 import android.tools.NavBar
@@ -23,7 +23,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.common.Utils
+import com.android.wm.shell.Utils
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchAppByDoubleTapDivider.kt
similarity index 96%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchAppByDoubleTapDivider.kt
index 2406bde..26203d4 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchAppByDoubleTapDivider.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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.scenarios
+package com.android.wm.shell.scenarios
 
 import android.app.Instrumentation
 import android.graphics.Point
@@ -25,7 +25,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.common.Utils
+import com.android.wm.shell.Utils
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBackToSplitFromAnotherApp.kt
similarity index 92%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBackToSplitFromAnotherApp.kt
index de26982..2ccffa85 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBackToSplitFromAnotherApp.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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.scenarios
+package com.android.wm.shell.scenarios
 
 import android.app.Instrumentation
 import android.tools.NavBar
@@ -23,7 +23,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.common.Utils
+import com.android.wm.shell.Utils
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBackToSplitFromHome.kt
similarity index 92%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBackToSplitFromHome.kt
index 873b019..8673c46 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBackToSplitFromHome.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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.scenarios
+package com.android.wm.shell.scenarios
 
 import android.app.Instrumentation
 import android.tools.NavBar
@@ -23,7 +23,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.common.Utils
+import com.android.wm.shell.Utils
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBackToSplitFromRecent.kt
similarity index 92%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBackToSplitFromRecent.kt
index 15934d0..c7cbc3e 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBackToSplitFromRecent.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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.scenarios
+package com.android.wm.shell.scenarios
 
 import android.app.Instrumentation
 import android.tools.NavBar
@@ -23,7 +23,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.common.Utils
+import com.android.wm.shell.Utils
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBetweenSplitPairs.kt
similarity index 93%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBetweenSplitPairs.kt
index 79e69ae..4ded148 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBetweenSplitPairs.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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.scenarios
+package com.android.wm.shell.scenarios
 
 import android.app.Instrumentation
 import android.tools.NavBar
@@ -23,7 +23,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.common.Utils
+import com.android.wm.shell.Utils
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/UnlockKeyguardToSplitScreen.kt
similarity index 92%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt
rename to libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/UnlockKeyguardToSplitScreen.kt
index 0f932d4..7b062fcc 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/UnlockKeyguardToSplitScreen.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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.splitscreen.scenarios
+package com.android.wm.shell.scenarios
 
 import android.app.Instrumentation
 import android.tools.NavBar
@@ -23,7 +23,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.common.Utils
+import com.android.wm.shell.Utils
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/e2e/utils/Android.bp b/libs/WindowManager/Shell/tests/e2e/utils/Android.bp
new file mode 100644
index 0000000..51d9c40
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/utils/Android.bp
@@ -0,0 +1,31 @@
+//
+// 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 {
+    // 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_team: "trendy_team_windowing_tools",
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+java_library {
+    name: "WMShellTestUtils",
+    srcs: ["src/**/*.kt"],
+    static_libs: ["WMShellFlickerTestsBase"],
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/common/Utils.kt b/libs/WindowManager/Shell/tests/e2e/utils/src/com/android/wm/shell/Utils.kt
similarity index 97%
rename from libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/common/Utils.kt
rename to libs/WindowManager/Shell/tests/e2e/utils/src/com/android/wm/shell/Utils.kt
index 4c6c6cc..dee67f3 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/common/Utils.kt
+++ b/libs/WindowManager/Shell/tests/e2e/utils/src/com/android/wm/shell/Utils.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service.common
+package com.android.wm.shell
 
 import android.app.Instrumentation
 import android.platform.test.rule.NavigationModeRule
diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
index 2ee53f4..d7ea9f3 100644
--- a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
@@ -100,14 +100,14 @@
     @Postsubmit
     @Test
     fun navBarLayerIsVisibleAtEnd() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
+        Assume.assumeFalse(usesTaskbar)
         flicker.navBarLayerIsVisibleAtEnd()
     }
 
     @Postsubmit
     @Test
     fun navBarLayerPositionAtEnd() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
+        Assume.assumeFalse(usesTaskbar)
         flicker.navBarLayerPositionAtEnd()
     }
 
@@ -154,7 +154,7 @@
     @Postsubmit
     @Test
     fun taskBarLayerIsVisibleAtEnd() {
-        Assume.assumeTrue(flicker.scenario.isTablet)
+        Assume.assumeTrue(usesTaskbar)
         flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.TASK_BAR) }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
index a5e0550..3ffc9d7 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
@@ -21,6 +21,7 @@
 import android.tools.flicker.legacy.FlickerBuilder
 import android.tools.flicker.legacy.LegacyFlickerTest
 import android.tools.traces.component.ComponentNameMatcher
+import com.android.wm.shell.Flags
 import com.android.wm.shell.flicker.pip.common.ClosePipTransition
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -60,7 +61,7 @@
             val pipCenterY = pipRegion.centerY()
             val displayCenterX = device.displayWidth / 2
             val barComponent =
-                if (flicker.scenario.isTablet) {
+                if (flicker.scenario.isTablet || Flags.enableTaskbarOnPhones()) {
                     ComponentNameMatcher.TASK_BAR
                 } else {
                     ComponentNameMatcher.NAV_BAR
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
index 3a0eeb6..68fa7c7 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
@@ -103,7 +103,7 @@
     @Postsubmit
     @Test
     override fun taskBarLayerIsVisibleAtStartAndEnd() {
-        Assume.assumeTrue(flicker.scenario.isTablet)
+        Assume.assumeTrue(usesTaskbar)
         // Netflix starts in immersive fullscreen mode, so taskbar bar is not visible at start
         flicker.assertLayersStart { this.isInvisible(ComponentNameMatcher.TASK_BAR) }
         flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.TASK_BAR) }
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt
index 5c539a5..72be3d8 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt
@@ -88,7 +88,7 @@
     @Postsubmit
     @Test
     override fun taskBarLayerIsVisibleAtStartAndEnd() {
-        Assume.assumeTrue(flicker.scenario.isTablet)
+        Assume.assumeTrue(usesTaskbar)
         // YouTube starts in immersive fullscreen mode, so taskbar bar is not visible at start
         flicker.assertLayersStart { this.isInvisible(ComponentNameMatcher.TASK_BAR) }
         flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.TASK_BAR) }
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/CloseAllAppsWithAppHeaderExitTest.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/CloseAllAppsWithAppHeaderExitTest.kt
deleted file mode 100644
index 9ba3a45..0000000
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/CloseAllAppsWithAppHeaderExitTest.kt
+++ /dev/null
@@ -1,34 +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.wm.shell.flicker.service.desktopmode.functional
-
-import android.platform.test.annotations.Postsubmit
-import com.android.wm.shell.flicker.service.desktopmode.scenarios.CloseAllAppsWithAppHeaderExit
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.BlockJUnit4ClassRunner
-
-/** Functional test for [CloseAllAppsWithAppHeaderExit]. */
-@RunWith(BlockJUnit4ClassRunner::class)
-@Postsubmit
-open class CloseAllAppsWithAppHeaderExitTest : CloseAllAppsWithAppHeaderExit() {
-
-    @Test
-    override fun closeAllAppsInDesktop() {
-        super.closeAllAppsInDesktop()
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/DragAppWindowMultiWindowTest.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/DragAppWindowMultiWindowTest.kt
deleted file mode 100644
index ed1d488..0000000
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/DragAppWindowMultiWindowTest.kt
+++ /dev/null
@@ -1,34 +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.wm.shell.flicker.service.desktopmode.functional
-
-import android.platform.test.annotations.Postsubmit
-import com.android.wm.shell.flicker.service.desktopmode.scenarios.DragAppWindowMultiWindow
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.BlockJUnit4ClassRunner
-
-/** Functional test for [DragAppWindowMultiWindow]. */
-@RunWith(BlockJUnit4ClassRunner::class)
-@Postsubmit
-open class DragAppWindowMultiWindowTest : DragAppWindowMultiWindow()
-{
-    @Test
-    override fun dragAppWindow() {
-        super.dragAppWindow()
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/DragAppWindowSingleWindowTest.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/DragAppWindowSingleWindowTest.kt
deleted file mode 100644
index d8b9348..0000000
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/DragAppWindowSingleWindowTest.kt
+++ /dev/null
@@ -1,34 +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.wm.shell.flicker.service.desktopmode.functional
-
-import android.platform.test.annotations.Postsubmit
-import com.android.wm.shell.flicker.service.desktopmode.scenarios.DragAppWindowSingleWindow
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.BlockJUnit4ClassRunner
-
-/** Functional test for [DragAppWindowSingleWindow]. */
-@RunWith(BlockJUnit4ClassRunner::class)
-@Postsubmit
-open class DragAppWindowSingleWindowTest : DragAppWindowSingleWindow()
-{
-    @Test
-    override fun dragAppWindow() {
-        super.dragAppWindow()
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/EnterDesktopWithAppHandleMenuTest.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/EnterDesktopWithAppHandleMenuTest.kt
deleted file mode 100644
index 546ce2d..0000000
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/EnterDesktopWithAppHandleMenuTest.kt
+++ /dev/null
@@ -1,33 +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.wm.shell.flicker.service.desktopmode.functional
-
-import android.platform.test.annotations.Postsubmit
-import com.android.wm.shell.flicker.service.desktopmode.scenarios.EnterDesktopWithAppHandleMenu
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.BlockJUnit4ClassRunner
-
-/** Functional test for [EnterDesktopWithAppHandleMenu]. */
-@RunWith(BlockJUnit4ClassRunner::class)
-@Postsubmit
-open class EnterDesktopWithAppHandleMenuTest : EnterDesktopWithAppHandleMenu() {
-    @Test
-    override fun enterDesktopWithAppHandleMenu() {
-        super.enterDesktopWithAppHandleMenu()
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/EnterDesktopWithDragTest.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/EnterDesktopWithDragTest.kt
deleted file mode 100644
index b5fdb16..0000000
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/EnterDesktopWithDragTest.kt
+++ /dev/null
@@ -1,35 +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.wm.shell.flicker.service.desktopmode.functional
-
-import android.platform.test.annotations.Postsubmit
-import android.tools.Rotation
-import com.android.wm.shell.flicker.service.desktopmode.scenarios.EnterDesktopWithDrag
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.BlockJUnit4ClassRunner
-
-/** Functional test for [EnterDesktopWithDrag]. */
-@RunWith(BlockJUnit4ClassRunner::class)
-@Postsubmit
-open class EnterDesktopWithDragTest : EnterDesktopWithDrag(Rotation.ROTATION_0) {
-
-    @Test
-    override fun enterDesktopWithDrag() {
-        super.enterDesktopWithDrag()
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/ExitDesktopWithDragToTopDragZoneTest.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/ExitDesktopWithDragToTopDragZoneTest.kt
deleted file mode 100644
index 8f802d2..0000000
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/ExitDesktopWithDragToTopDragZoneTest.kt
+++ /dev/null
@@ -1,35 +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.wm.shell.flicker.service.desktopmode.functional
-
-import android.platform.test.annotations.Postsubmit
-import android.tools.Rotation
-import com.android.wm.shell.flicker.service.desktopmode.scenarios.ExitDesktopWithDragToTopDragZone
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.BlockJUnit4ClassRunner
-
-/** Functional test for [ExitDesktopWithDragToTopDragZone]. */
-@RunWith(BlockJUnit4ClassRunner::class)
-@Postsubmit
-open class ExitDesktopWithDragToTopDragZoneTest :
-    ExitDesktopWithDragToTopDragZone(Rotation.ROTATION_0) {
-    @Test
-    override fun exitDesktopWithDragToTopDragZone() {
-        super.exitDesktopWithDragToTopDragZone()
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/MaximizeAppWindowTest.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/MaximizeAppWindowTest.kt
deleted file mode 100644
index f899082..0000000
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/MaximizeAppWindowTest.kt
+++ /dev/null
@@ -1,34 +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.wm.shell.flicker.service.desktopmode.functional
-
-import android.platform.test.annotations.Postsubmit
-import com.android.wm.shell.flicker.service.desktopmode.scenarios.MaximizeAppWindow
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.BlockJUnit4ClassRunner
-
-/** Functional test for [MaximizeAppWindow]. */
-@RunWith(BlockJUnit4ClassRunner::class)
-@Postsubmit
-open class MaximizeAppWindowTest : MaximizeAppWindow()
-{
-    @Test
-    override fun maximizeAppWindow() {
-        super.maximizeAppWindow()
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/MinimizeWindowOnAppOpenTest.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/MinimizeWindowOnAppOpenTest.kt
deleted file mode 100644
index 63c428a..0000000
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/MinimizeWindowOnAppOpenTest.kt
+++ /dev/null
@@ -1,36 +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.wm.shell.flicker.service.desktopmode.functional
-
-import android.platform.test.annotations.Postsubmit
-import com.android.wm.shell.flicker.service.desktopmode.scenarios.MinimizeWindowOnAppOpen
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.BlockJUnit4ClassRunner
-
-/** Functional test for [MinimizeWindowOnAppOpen]. */
-@RunWith(BlockJUnit4ClassRunner::class)
-@Postsubmit
-open class MinimizeWindowOnAppOpenTest : MinimizeWindowOnAppOpen()
-{
-    @Test
-    override fun openAppToMinimizeWindow() {
-        // Launch a new app while 4 apps are already open on desktop. This should result in the
-        // first app we opened to be minimized.
-        super.openAppToMinimizeWindow()
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/ResizeAppWithCornerResizeTest.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/ResizeAppWithCornerResizeTest.kt
deleted file mode 100644
index 4797aaf..0000000
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/ResizeAppWithCornerResizeTest.kt
+++ /dev/null
@@ -1,34 +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.wm.shell.flicker.service.desktopmode.functional
-
-import android.platform.test.annotations.Postsubmit
-import android.tools.Rotation
-import com.android.wm.shell.flicker.service.desktopmode.scenarios.ResizeAppWithCornerResize
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.BlockJUnit4ClassRunner
-
-/** Functional test for [ResizeAppWithCornerResize]. */
-@RunWith(BlockJUnit4ClassRunner::class)
-@Postsubmit
-open class ResizeAppWithCornerResizeTest : ResizeAppWithCornerResize(Rotation.ROTATION_0) {
-    @Test
-    override fun resizeAppWithCornerResize() {
-        super.resizeAppWithCornerResize()
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/SwitchToOverviewFromDesktopTest.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/SwitchToOverviewFromDesktopTest.kt
deleted file mode 100644
index 9a71361..0000000
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/SwitchToOverviewFromDesktopTest.kt
+++ /dev/null
@@ -1,33 +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.wm.shell.flicker.service.desktopmode.functional
-
-import android.platform.test.annotations.Postsubmit
-import com.android.wm.shell.flicker.service.desktopmode.scenarios.SwitchToOverviewFromDesktop
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.BlockJUnit4ClassRunner
-
-/** Functional test for [SwitchToOverviewFromDesktop]. */
-@RunWith(BlockJUnit4ClassRunner::class)
-@Postsubmit
-open class SwitchToOverviewFromDesktopTest : SwitchToOverviewFromDesktop() {
-    @Test
-    override fun switchToOverview() {
-        super.switchToOverview()
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/OWNERS b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/OWNERS
deleted file mode 100644
index 3ab6a1e..0000000
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-# Android > Android OS & Apps > Framework (Java + Native) > Window Manager > WM Shell > Split Screen
-# Bug component: 928697
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/ICommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/ICommonAssertions.kt
index 4465a16..acaf021 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/ICommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/ICommonAssertions.kt
@@ -28,12 +28,16 @@
 import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
 import com.android.server.wm.flicker.taskBarLayerIsVisibleAtStartAndEnd
 import com.android.server.wm.flicker.taskBarWindowIsAlwaysVisible
+import com.android.wm.shell.Flags
 import org.junit.Assume
 import org.junit.Test
 
 interface ICommonAssertions {
     val flicker: LegacyFlickerTest
 
+    val usesTaskbar: Boolean
+        get() = flicker.scenario.isTablet || Flags.enableTaskbarOnPhones()
+
     /** Checks that all parts of the screen are covered during the transition */
     @Presubmit @Test fun entireScreenCovered() = flicker.entireScreenCovered()
 
@@ -43,7 +47,7 @@
     @Presubmit
     @Test
     fun navBarLayerIsVisibleAtStartAndEnd() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
+        Assume.assumeFalse(usesTaskbar)
         flicker.navBarLayerIsVisibleAtStartAndEnd()
     }
 
@@ -54,7 +58,7 @@
     @Presubmit
     @Test
     fun navBarLayerPositionAtStartAndEnd() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
+        Assume.assumeFalse(usesTaskbar)
         flicker.navBarLayerPositionAtStartAndEnd()
     }
 
@@ -66,7 +70,7 @@
     @Presubmit
     @Test
     fun navBarWindowIsAlwaysVisible() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
+        Assume.assumeFalse(usesTaskbar)
         flicker.navBarWindowIsAlwaysVisible()
     }
 
@@ -76,7 +80,7 @@
     @Presubmit
     @Test
     fun taskBarLayerIsVisibleAtStartAndEnd() {
-        Assume.assumeTrue(flicker.scenario.isTablet)
+        Assume.assumeTrue(usesTaskbar)
         flicker.taskBarLayerIsVisibleAtStartAndEnd()
     }
 
@@ -88,7 +92,7 @@
     @Presubmit
     @Test
     fun taskBarWindowIsAlwaysVisible() {
-        Assume.assumeTrue(flicker.scenario.isTablet)
+        Assume.assumeTrue(usesTaskbar)
         flicker.taskBarWindowIsAlwaysVisible()
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp
index a040865..4d761e1 100644
--- a/libs/WindowManager/Shell/tests/unittest/Android.bp
+++ b/libs/WindowManager/Shell/tests/unittest/Android.bp
@@ -44,6 +44,8 @@
         "androidx.test.runner",
         "androidx.test.rules",
         "androidx.test.ext.junit",
+        "androidx.datastore_datastore",
+        "kotlinx_coroutines_test",
         "androidx.dynamicanimation_dynamicanimation",
         "dagger2",
         "frameworks-base-testutils",
diff --git a/packages/overlays/HsumConfigOverlay/AndroidManifest.xml b/libs/WindowManager/Shell/tests/unittest/res/layout/caption_layout.xml
similarity index 63%
copy from packages/overlays/HsumConfigOverlay/AndroidManifest.xml
copy to libs/WindowManager/Shell/tests/unittest/res/layout/caption_layout.xml
index cd7a879..079ee13 100644
--- a/packages/overlays/HsumConfigOverlay/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/tests/unittest/res/layout/caption_layout.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
   ~ Copyright (C) 2024 The Android Open Source Project
   ~
@@ -13,10 +14,10 @@
   ~ 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.internal.overlay.hsumconfig"
-    android:versionCode="1"
-    android:versionName="1.0">
-    <overlay android:targetPackage="android" android:priority="2" android:isStatic="true" />
-</manifest>
+<com.android.wm.shell.windowdecor.WindowDecorLinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/caption"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:gravity="end"
+    android:background="@drawable/caption_decor_title"/>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
index 669e433..9df9956 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
@@ -18,6 +18,7 @@
 
 import static android.view.Display.DEFAULT_DISPLAY;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
@@ -160,6 +161,19 @@
         assertTrue(secondListener.hideInsetsCount == 1);
     }
 
+    @Test
+    public void testGlobalListenerCallback() throws RemoteException {
+        TrackedListener globalListener = new TrackedListener();
+        addDisplay(SECOND_DISPLAY);
+        mController.addGlobalInsetsChangedListener(globalListener);
+
+        mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).insetsChanged(null);
+        mInsetsControllersByDisplayId.get(SECOND_DISPLAY).insetsChanged(null);
+        mExecutor.flushAll();
+
+        assertEquals(2, globalListener.insetsChangedCount);
+    }
+
     private void addDisplay(int displayId) throws RemoteException {
         mController.onDisplayAdded(displayId);
         verify(mWm, times(mInsetsControllersByDisplayId.size() + 1))
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 de1659b..b39cf19 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
@@ -43,6 +43,7 @@
 import android.view.InsetsState;
 import android.view.accessibility.AccessibilityManager;
 
+import androidx.annotation.NonNull;
 import androidx.test.filters.SmallTest;
 
 import com.android.window.flags.Flags;
@@ -128,6 +129,9 @@
     @Captor
     ArgumentCaptor<OnInsetsChangedListener> mOnInsetsChangedListenerCaptor;
 
+    @NonNull
+    private CompatUIStatusManager mCompatUIStatusManager;
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
@@ -147,11 +151,13 @@
         doReturn(true).when(mMockRestartDialogLayout).createLayout(anyBoolean());
         doReturn(true).when(mMockRestartDialogLayout).updateCompatInfo(any(), any(), anyBoolean());
 
+        mCompatUIStatusManager = new CompatUIStatusManager();
         mShellInit = spy(new ShellInit(mMockExecutor));
         mController = new CompatUIController(mContext, mShellInit, mMockShellController,
                 mMockDisplayController, mMockDisplayInsetsController, mMockImeController,
                 mMockSyncQueue, mMockExecutor, mMockTransitionsLazy, mDockStateReader,
-                mCompatUIConfiguration, mCompatUIShellCommandHandler, mAccessibilityManager) {
+                mCompatUIConfiguration, mCompatUIShellCommandHandler, mAccessibilityManager,
+                mCompatUIStatusManager) {
             @Override
             CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo,
                     ShellTaskOrganizer.TaskListener taskListener) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIStatusManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIStatusManagerTest.java
new file mode 100644
index 0000000..d6059a8
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIStatusManagerTest.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.wm.shell.compatui;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTestCase;
+
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.function.IntConsumer;
+import java.util.function.IntSupplier;
+
+/**
+ * Tests for {@link CompatUILayout}.
+ *
+ * Build/Install/Run:
+ *  atest WMShellUnitTests:CompatUIStatusManagerTest
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class CompatUIStatusManagerTest extends ShellTestCase {
+
+    private FakeCompatUIStatusManagerTest mTestState;
+    private CompatUIStatusManager mStatusManager;
+
+    @Before
+    public void setUp() {
+        mTestState = new FakeCompatUIStatusManagerTest();
+        mStatusManager = new CompatUIStatusManager(mTestState.mWriter, mTestState.mReader);
+    }
+
+    @Test
+    public void isEducationShown() {
+        assertFalse(mStatusManager.isEducationVisible());
+
+        mStatusManager.onEducationShown();
+        assertTrue(mStatusManager.isEducationVisible());
+
+        mStatusManager.onEducationHidden();
+        assertFalse(mStatusManager.isEducationVisible());
+    }
+
+    static class FakeCompatUIStatusManagerTest {
+
+        int mCurrentStatus = 0;
+
+        final IntSupplier mReader = () -> mCurrentStatus;
+
+        final IntConsumer mWriter = newStatus -> mCurrentStatus = newStatus;
+
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java
index 7617269..94dbd11 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java
@@ -20,9 +20,13 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK;
+import static com.android.wm.shell.compatui.CompatUIStatusManager.COMPAT_UI_EDUCATION_HIDDEN;
+import static com.android.wm.shell.compatui.CompatUIStatusManager.COMPAT_UI_EDUCATION_VISIBLE;
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static junit.framework.Assert.assertEquals;
+
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
@@ -38,6 +42,7 @@
 import android.app.TaskInfo;
 import android.graphics.Insets;
 import android.graphics.Rect;
+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;
@@ -54,6 +59,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.window.flags.Flags;
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
@@ -61,6 +67,7 @@
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.DockStateReader;
 import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.compatui.CompatUIStatusManagerTest.FakeCompatUIStatusManagerTest;
 import com.android.wm.shell.transition.Transitions;
 
 import org.junit.After;
@@ -120,6 +127,8 @@
 
     private CompatUIConfiguration mCompatUIConfiguration;
     private TestShellExecutor mExecutor;
+    private FakeCompatUIStatusManagerTest mCompatUIStatus;
+    private CompatUIStatusManager mCompatUIStatusManager;
 
     @Rule
     public final CheckFlagsRule mCheckFlagsRule =
@@ -129,6 +138,9 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mExecutor = new TestShellExecutor();
+        mCompatUIStatus = new FakeCompatUIStatusManagerTest();
+        mCompatUIStatusManager = new CompatUIStatusManager(mCompatUIStatus.mWriter,
+                mCompatUIStatus.mReader);
         mCompatUIConfiguration = new CompatUIConfiguration(mContext, mExecutor) {
 
             final Set<Integer> mHasSeenSet = new HashSet<>();
@@ -414,6 +426,21 @@
         assertFalse(windowManager.needsToBeRecreated(newTaskInfo, mTaskListener));
     }
 
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_COMPAT_UI_VISIBILITY_STATUS)
+    public void testCompatUIStatus_dialogIsShown() {
+        // We display the dialog
+        LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true,
+                USER_ID_1, /* isTaskbarEduShowing= */ false);
+        assertTrue(windowManager.createLayout(/* canShow= */ true));
+        assertNotNull(windowManager.mLayout);
+        assertEquals(/* expected= */ COMPAT_UI_EDUCATION_VISIBLE, mCompatUIStatus.mCurrentStatus);
+
+        // We dismiss
+        windowManager.release();
+        assertEquals(/* expected= */ COMPAT_UI_EDUCATION_HIDDEN, mCompatUIStatus.mCurrentStatus);
+    }
+
     private void verifyLayout(LetterboxEduDialogLayout layout, ViewGroup.LayoutParams params,
             int expectedWidth, int expectedHeight, int expectedExtraTopMargin,
             int expectedExtraBottomMargin) {
@@ -464,7 +491,7 @@
                 windowManager = new LetterboxEduWindowManager(mContext,
                 createTaskInfo(eligible, userId), mSyncTransactionQueue, mTaskListener,
                 createDisplayLayout(), mTransitions, mOnDismissCallback, mAnimationController,
-                mDockStateReader, mCompatUIConfiguration);
+                mDockStateReader, mCompatUIConfiguration, mCompatUIStatusManager);
         spyOn(windowManager);
         doReturn(mViewHost).when(windowManager).createSurfaceViewHost();
         doReturn(isTaskbarEduShowing).when(windowManager).isTaskbarEduShowing();
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 92f7050..7bb5449 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
@@ -731,6 +731,64 @@
 
   @Test
   @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
+  fun addMoveToDesktopChanges_lastWindowSnapLeft_positionResetsToCenter() {
+    setUpLandscapeDisplay()
+    val stableBounds = Rect()
+    displayLayout.getStableBoundsForDesktopMode(stableBounds)
+
+    // Add freeform task with half display size snap bounds at left side.
+    setUpFreeformTask(bounds = Rect(stableBounds.left, stableBounds.top, 500, stableBounds.bottom))
+
+    val task = setUpFullscreenTask()
+    val wct = WindowContainerTransaction()
+    controller.addMoveToDesktopChanges(wct, task)
+
+    val finalBounds = findBoundsChange(wct, task)
+    assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
+      .isEqualTo(DesktopTaskPosition.Center)
+  }
+
+  @Test
+  @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
+  fun addMoveToDesktopChanges_lastWindowSnapRight_positionResetsToCenter() {
+    setUpLandscapeDisplay()
+    val stableBounds = Rect()
+    displayLayout.getStableBoundsForDesktopMode(stableBounds)
+
+    // Add freeform task with half display size snap bounds at right side.
+    setUpFreeformTask(bounds = Rect(
+      stableBounds.right - 500, stableBounds.top, stableBounds.right, stableBounds.bottom))
+
+    val task = setUpFullscreenTask()
+    val wct = WindowContainerTransaction()
+    controller.addMoveToDesktopChanges(wct, task)
+
+    val finalBounds = findBoundsChange(wct, task)
+    assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
+      .isEqualTo(DesktopTaskPosition.Center)
+  }
+
+  @Test
+  @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
+  fun addMoveToDesktopChanges_lastWindowMaximised_positionResetsToCenter() {
+    setUpLandscapeDisplay()
+    val stableBounds = Rect()
+    displayLayout.getStableBoundsForDesktopMode(stableBounds)
+
+    // Add maximised freeform task.
+    setUpFreeformTask(bounds = Rect(stableBounds))
+
+    val task = setUpFullscreenTask()
+    val wct = WindowContainerTransaction()
+    controller.addMoveToDesktopChanges(wct, task)
+
+    val finalBounds = findBoundsChange(wct, task)
+    assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
+      .isEqualTo(DesktopTaskPosition.Center)
+  }
+
+  @Test
+  @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
   fun addMoveToDesktopChanges_defaultToCenterIfFree() {
     setUpLandscapeDisplay()
     val stableBounds = Rect()
@@ -751,6 +809,50 @@
   }
 
   @Test
+  @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+  fun addMoveToDesktopChanges_landscapeDevice_userFullscreenOverride_defaultPortraitBounds() {
+    setUpLandscapeDisplay()
+    val task = setUpFullscreenTask(enableUserFullscreenOverride = true)
+    val wct = WindowContainerTransaction()
+    controller.addMoveToDesktopChanges(wct, task)
+
+    assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
+  }
+
+  @Test
+  @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+  fun addMoveToDesktopChanges_landscapeDevice_systemFullscreenOverride_defaultPortraitBounds() {
+    setUpLandscapeDisplay()
+    val task = setUpFullscreenTask(enableSystemFullscreenOverride = true)
+    val wct = WindowContainerTransaction()
+    controller.addMoveToDesktopChanges(wct, task)
+
+    assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
+  }
+
+  @Test
+  @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+  fun addMoveToDesktopChanges_portraitDevice_userFullscreenOverride_defaultPortraitBounds() {
+    setUpPortraitDisplay()
+    val task = setUpFullscreenTask(enableUserFullscreenOverride = true)
+    val wct = WindowContainerTransaction()
+    controller.addMoveToDesktopChanges(wct, task)
+
+    assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
+  }
+
+  @Test
+  @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+  fun addMoveToDesktopChanges_portraitDevice_systemFullscreenOverride_defaultPortraitBounds() {
+    setUpPortraitDisplay()
+    val task = setUpFullscreenTask(enableSystemFullscreenOverride = true)
+    val wct = WindowContainerTransaction()
+    controller.addMoveToDesktopChanges(wct, task)
+
+    assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
+  }
+
+  @Test
   fun moveToDesktop_tdaFullscreen_windowingModeSetToFreeform() {
     val task = setUpFullscreenTask()
     val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
@@ -1305,13 +1407,36 @@
     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(2)
+    assertThat(wct!!.hierarchyOps.size).isEqualTo(3)
     wct.assertReorderAt(0, fullscreenTask, toTop = true)
-    wct.assertReorderAt(1, freeformTasks[0], toTop = false)
+    wct.assertReorderAt(1, homeTask, toTop = false)
+    wct.assertReorderAt(2, freeformTasks[0], toTop = false)
+  }
+
+  @Test
+  fun handleRequest_fullscreenTaskToFreeform_alreadyBeyondLimit_existingAndNewTasksAreMinimized() {
+    assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+    val minimizedTask = setUpFreeformTask()
+    taskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = minimizedTask.taskId)
+    val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
+    freeformTasks.forEach { markTaskVisible(it) }
+    val homeTask = setUpHomeTask()
+    val fullscreenTask = createFullscreenTask()
+
+    val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+    assertThat(wct!!.hierarchyOps.size).isEqualTo(4)
+    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)
   }
 
   @Test
@@ -2712,13 +2837,15 @@
   }
 
   private fun setUpFullscreenTask(
-      displayId: Int = DEFAULT_DISPLAY,
-      isResizable: Boolean = true,
-      windowingMode: Int = WINDOWING_MODE_FULLSCREEN,
-      deviceOrientation: Int = ORIENTATION_LANDSCAPE,
-      screenOrientation: Int = SCREEN_ORIENTATION_UNSPECIFIED,
-      shouldLetterbox: Boolean = false,
-      gravity: Int = Gravity.NO_GRAVITY
+    displayId: Int = DEFAULT_DISPLAY,
+    isResizable: Boolean = true,
+    windowingMode: Int = WINDOWING_MODE_FULLSCREEN,
+    deviceOrientation: Int = ORIENTATION_LANDSCAPE,
+    screenOrientation: Int = SCREEN_ORIENTATION_UNSPECIFIED,
+    shouldLetterbox: Boolean = false,
+    gravity: Int = Gravity.NO_GRAVITY,
+    enableUserFullscreenOverride: Boolean = false,
+    enableSystemFullscreenOverride: Boolean = false
   ): RunningTaskInfo {
     val task = createFullscreenTask(displayId)
     val activityInfo = ActivityInfo()
@@ -2729,6 +2856,8 @@
       isResizeable = isResizable
       configuration.orientation = deviceOrientation
       configuration.windowConfiguration.windowingMode = windowingMode
+      appCompatTaskInfo.isUserFullscreenOverrideEnabled = enableUserFullscreenOverride
+      appCompatTaskInfo.isSystemFullscreenOverrideEnabled = enableSystemFullscreenOverride
 
       if (shouldLetterbox) {
         if (deviceOrientation == ORIENTATION_LANDSCAPE &&
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 e4e2bd2..c97bcfb 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
@@ -11,6 +11,7 @@
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import android.view.SurfaceControl
+import android.view.WindowManager.TRANSIT_OPEN
 import android.window.TransitionInfo
 import android.window.TransitionInfo.FLAG_IS_WALLPAPER
 import android.window.WindowContainerTransaction
@@ -27,6 +28,7 @@
 import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP
 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.assertFalse
 import org.junit.Before
 import org.junit.Test
@@ -40,7 +42,6 @@
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.verifyZeroInteractions
 import org.mockito.kotlin.whenever
-import java.util.function.Supplier
 
 /** Tests of [DragToDesktopTransitionHandler]. */
 @SmallTest
@@ -52,17 +53,26 @@
     @Mock private lateinit var taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
     @Mock private lateinit var splitScreenController: SplitScreenController
     @Mock private lateinit var dragAnimator: MoveToDesktopAnimator
-    @Mock
-    private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
+    @Mock private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
+    @Mock private lateinit var draggedTaskLeash: SurfaceControl
+    @Mock private lateinit var homeTaskLeash: SurfaceControl
 
     private val transactionSupplier = Supplier { mock<SurfaceControl.Transaction>() }
 
-    private lateinit var handler: DragToDesktopTransitionHandler
+    private lateinit var defaultHandler: DragToDesktopTransitionHandler
+    private lateinit var springHandler: SpringDragToDesktopTransitionHandler
 
     @Before
     fun setUp() {
-        handler =
-            DragToDesktopTransitionHandler(
+        defaultHandler = DefaultDragToDesktopTransitionHandler(
+                    context,
+                    transitions,
+                    taskDisplayAreaOrganizer,
+                    mockInteractionJankMonitor,
+                    transactionSupplier,
+                )
+                .apply { setSplitScreenController(splitScreenController) }
+        springHandler = SpringDragToDesktopTransitionHandler(
                     context,
                     transitions,
                     taskDisplayAreaOrganizer,
@@ -76,10 +86,10 @@
     fun startDragToDesktop_animateDragWhenReady() {
         val task = createTask()
         // Simulate transition is started.
-        val transition = startDragToDesktopTransition(task, dragAnimator)
+        val transition = startDragToDesktopTransition(defaultHandler, task, dragAnimator)
 
         // Now it's ready to animate.
-        handler.startAnimation(
+        defaultHandler.startAnimation(
             transition = transition,
             info =
                 createTransitionInfo(
@@ -96,65 +106,70 @@
 
     @Test
     fun startDragToDesktop_cancelledBeforeReady_startCancelTransition() {
-        performEarlyCancel(DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL)
+        performEarlyCancel(
+            defaultHandler,
+            DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL
+        )
         verify(transitions)
-            .startTransition(eq(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP), any(), eq(handler))
+            .startTransition(
+                eq(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP),
+                any(),
+                eq(defaultHandler)
+            )
     }
 
     @Test
     fun startDragToDesktop_cancelledBeforeReady_verifySplitLeftCancel() {
-        performEarlyCancel(DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_LEFT)
-        verify(splitScreenController).requestEnterSplitSelect(
-            any(),
-            any(),
-            eq(SPLIT_POSITION_TOP_OR_LEFT),
-            any()
+        performEarlyCancel(
+            defaultHandler,
+            DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_LEFT
         )
+        verify(splitScreenController)
+            .requestEnterSplitSelect(any(), any(), eq(SPLIT_POSITION_TOP_OR_LEFT), any())
     }
 
     @Test
     fun startDragToDesktop_cancelledBeforeReady_verifySplitRightCancel() {
-        performEarlyCancel(DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_RIGHT)
-        verify(splitScreenController).requestEnterSplitSelect(
-            any(),
-            any(),
-            eq(SPLIT_POSITION_BOTTOM_OR_RIGHT),
-            any()
+        performEarlyCancel(
+            defaultHandler,
+            DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_RIGHT
         )
+        verify(splitScreenController)
+            .requestEnterSplitSelect(any(), any(), eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any())
     }
 
     @Test
     fun startDragToDesktop_aborted_finishDropped() {
         val task = createTask()
         // Simulate transition is started.
-        val transition = startDragToDesktopTransition(task, dragAnimator)
+        val transition = startDragToDesktopTransition(defaultHandler, task, dragAnimator)
         // But the transition was aborted.
-        handler.onTransitionConsumed(transition, aborted = true, mock())
+        defaultHandler.onTransitionConsumed(transition, aborted = true, mock())
 
         // Attempt to finish the failed drag start.
-        handler.finishDragToDesktopTransition(WindowContainerTransaction())
+        defaultHandler.finishDragToDesktopTransition(WindowContainerTransaction())
 
         // Should not be attempted and state should be reset.
         verify(transitions, never())
-                .startTransition(eq(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP), any(), any())
-        assertFalse(handler.inProgress)
+            .startTransition(eq(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP), any(), any())
+        assertFalse(defaultHandler.inProgress)
     }
 
     @Test
     fun startDragToDesktop_aborted_cancelDropped() {
         val task = createTask()
         // Simulate transition is started.
-        val transition = startDragToDesktopTransition(task, dragAnimator)
+        val transition = startDragToDesktopTransition(defaultHandler, task, dragAnimator)
         // But the transition was aborted.
-        handler.onTransitionConsumed(transition, aborted = true, mock())
+        defaultHandler.onTransitionConsumed(transition, aborted = true, mock())
 
         // Attempt to finish the failed drag start.
-        handler.cancelDragToDesktopTransition(
+        defaultHandler.cancelDragToDesktopTransition(
             DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL
         )
 
         // Should not be attempted and state should be reset.
-        assertFalse(handler.inProgress)
+        assertFalse(defaultHandler.inProgress)
     }
 
     @Test
@@ -162,23 +177,24 @@
         val task = createTask()
 
         // Simulate attempt to start two drag to desktop transitions.
-        startDragToDesktopTransition(task, dragAnimator)
-        startDragToDesktopTransition(task, dragAnimator)
+        startDragToDesktopTransition(defaultHandler, task, dragAnimator)
+        startDragToDesktopTransition(defaultHandler, task, dragAnimator)
 
         // Verify transition only started once.
-        verify(transitions, times(1)).startTransition(
+        verify(transitions, times(1))
+            .startTransition(
                 eq(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP),
                 any(),
-                eq(handler)
-        )
+                eq(defaultHandler)
+            )
     }
 
     @Test
     fun cancelDragToDesktop_startWasReady_cancel() {
-        startDrag()
+        startDrag(defaultHandler)
 
         // Then user cancelled after it had already started.
-        handler.cancelDragToDesktopTransition(
+        defaultHandler.cancelDragToDesktopTransition(
             DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL
         )
 
@@ -188,48 +204,40 @@
 
     @Test
     fun cancelDragToDesktop_splitLeftCancelType_splitRequested() {
-        startDrag()
+        startDrag(defaultHandler)
 
         // Then user cancelled it, requesting split.
-        handler.cancelDragToDesktopTransition(
+        defaultHandler.cancelDragToDesktopTransition(
             DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_LEFT
         )
 
         // Verify the request went through split controller.
-        verify(splitScreenController).requestEnterSplitSelect(
-            any(),
-            any(),
-            eq(SPLIT_POSITION_TOP_OR_LEFT),
-            any()
-        )
+        verify(splitScreenController)
+            .requestEnterSplitSelect(any(), any(), eq(SPLIT_POSITION_TOP_OR_LEFT), any())
     }
 
     @Test
     fun cancelDragToDesktop_splitRightCancelType_splitRequested() {
-        startDrag()
+        startDrag(defaultHandler)
 
         // Then user cancelled it, requesting split.
-        handler.cancelDragToDesktopTransition(
+        defaultHandler.cancelDragToDesktopTransition(
             DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_RIGHT
         )
 
         // Verify the request went through split controller.
-        verify(splitScreenController).requestEnterSplitSelect(
-            any(),
-            any(),
-            eq(SPLIT_POSITION_BOTTOM_OR_RIGHT),
-            any()
-        )
+        verify(splitScreenController)
+            .requestEnterSplitSelect(any(), any(), eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any())
     }
 
     @Test
     fun cancelDragToDesktop_startWasNotReady_animateCancel() {
         val task = createTask()
         // Simulate transition is started and is ready to animate.
-        startDragToDesktopTransition(task, dragAnimator)
+        startDragToDesktopTransition(defaultHandler, task, dragAnimator)
 
         // Then user cancelled before the transition was ready and animated.
-        handler.cancelDragToDesktopTransition(
+        defaultHandler.cancelDragToDesktopTransition(
             DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL
         )
 
@@ -240,50 +248,139 @@
     @Test
     fun cancelDragToDesktop_transitionNotInProgress_dropCancel() {
         // Then cancel is called before the transition was started.
-        handler.cancelDragToDesktopTransition(
+        defaultHandler.cancelDragToDesktopTransition(
             DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL
         )
 
         // Verify cancel is dropped.
-        verify(transitions, never()).startTransition(
+        verify(transitions, never())
+            .startTransition(
                 eq(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP),
                 any(),
-                eq(handler)
-        )
+                eq(defaultHandler)
+            )
     }
 
     @Test
     fun finishDragToDesktop_transitionNotInProgress_dropFinish() {
         // Then finish is called before the transition was started.
-        handler.finishDragToDesktopTransition(WindowContainerTransaction())
+        defaultHandler.finishDragToDesktopTransition(WindowContainerTransaction())
 
         // Verify finish is dropped.
-        verify(transitions, never()).startTransition(
+        verify(transitions, never())
+            .startTransition(
                 eq(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP),
                 any(),
-                eq(handler)
-        )
+                eq(defaultHandler)
+            )
     }
 
-    private fun startDrag() {
+    @Test
+    fun mergeAnimation_otherTransition_doesNotMerge() {
+        val transaction = mock<SurfaceControl.Transaction>()
+        val finishCallback = mock<Transitions.TransitionFinishCallback>()
         val task = createTask()
+
+        startDrag(defaultHandler, task)
+        defaultHandler.mergeAnimation(
+            transition = mock(),
+            info = createTransitionInfo(type = TRANSIT_OPEN, draggedTask = task),
+            t = transaction,
+            mergeTarget = mock(),
+            finishCallback = finishCallback
+        )
+
+        // Should NOT have any transaction changes
+        verifyZeroInteractions(transaction)
+        // Should NOT merge animation
+        verify(finishCallback, never()).onTransitionFinished(any())
+    }
+
+    @Test
+    fun mergeAnimation_endTransition_mergesAnimation() {
+        val playingFinishTransaction = mock<SurfaceControl.Transaction>()
+        val mergedStartTransaction = mock<SurfaceControl.Transaction>()
+        val finishCallback = mock<Transitions.TransitionFinishCallback>()
+        val task = createTask()
+        val startTransition =
+            startDrag(defaultHandler, task, finishTransaction = playingFinishTransaction)
+        defaultHandler.onTaskResizeAnimationListener = mock()
+
+        defaultHandler.mergeAnimation(
+            transition = mock(),
+            info =
+                createTransitionInfo(
+                    type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
+                    draggedTask = task
+                ),
+            t = mergedStartTransaction,
+            mergeTarget = startTransition,
+            finishCallback = finishCallback
+        )
+
+        // Should show dragged task layer in start and finish transaction
+        verify(mergedStartTransaction).show(draggedTaskLeash)
+        verify(playingFinishTransaction).show(draggedTaskLeash)
+        // Should merge animation
+        verify(finishCallback).onTransitionFinished(null)
+    }
+
+    @Test
+    fun mergeAnimation_endTransition_springHandler_hidesHome() {
+        whenever(dragAnimator.computeCurrentVelocity()).thenReturn(PointF())
+        val playingFinishTransaction = mock<SurfaceControl.Transaction>()
+        val mergedStartTransaction = mock<SurfaceControl.Transaction>()
+        val finishCallback = mock<Transitions.TransitionFinishCallback>()
+        val task = createTask()
+        val startTransition =
+            startDrag(springHandler, task, finishTransaction = playingFinishTransaction)
+        springHandler.onTaskResizeAnimationListener = mock()
+
+        springHandler.mergeAnimation(
+            transition = mock(),
+            info =
+                createTransitionInfo(
+                    type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
+                    draggedTask = task
+                ),
+            t = mergedStartTransaction,
+            mergeTarget = startTransition,
+            finishCallback = finishCallback
+        )
+
+        // Should show dragged task layer in start and finish transaction
+        verify(mergedStartTransaction).show(draggedTaskLeash)
+        verify(playingFinishTransaction).show(draggedTaskLeash)
+        // Should hide home task leash in finish transaction
+        verify(playingFinishTransaction).hide(homeTaskLeash)
+        // Should merge animation
+        verify(finishCallback).onTransitionFinished(null)
+    }
+
+    private fun startDrag(
+        handler: DragToDesktopTransitionHandler,
+        task: RunningTaskInfo = createTask(),
+        finishTransaction: SurfaceControl.Transaction = mock()
+    ): IBinder {
         whenever(dragAnimator.position).thenReturn(PointF())
         // Simulate transition is started and is ready to animate.
-        val transition = startDragToDesktopTransition(task, dragAnimator)
+        val transition = startDragToDesktopTransition(handler, task, dragAnimator)
         handler.startAnimation(
             transition = transition,
             info =
-            createTransitionInfo(
-                type = TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP,
-                draggedTask = task
-            ),
+                createTransitionInfo(
+                    type = TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP,
+                    draggedTask = task
+                ),
             startTransaction = mock(),
-            finishTransaction = mock(),
+            finishTransaction = finishTransaction,
             finishCallback = {}
         )
+        return transition
     }
 
     private fun startDragToDesktopTransition(
+        handler: DragToDesktopTransitionHandler,
         task: RunningTaskInfo,
         dragAnimator: MoveToDesktopAnimator
     ): IBinder {
@@ -300,20 +397,23 @@
         return token
     }
 
-    private fun performEarlyCancel(cancelState: DragToDesktopTransitionHandler.CancelState) {
+    private fun performEarlyCancel(
+        handler: DragToDesktopTransitionHandler,
+        cancelState: DragToDesktopTransitionHandler.CancelState
+    ) {
         val task = createTask()
         // Simulate transition is started and is ready to animate.
-        val transition = startDragToDesktopTransition(task, dragAnimator)
+        val transition = startDragToDesktopTransition(handler, task, dragAnimator)
 
         handler.cancelDragToDesktopTransition(cancelState)
 
         handler.startAnimation(
             transition = transition,
             info =
-            createTransitionInfo(
-                type = TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP,
-                draggedTask = task
-            ),
+                createTransitionInfo(
+                    type = TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP,
+                    draggedTask = task
+                ),
             startTransaction = mock(),
             finishTransaction = mock(),
             finishCallback = {}
@@ -340,7 +440,7 @@
     private fun createTransitionInfo(type: Int, draggedTask: RunningTaskInfo): TransitionInfo {
         return TransitionInfo(type, 0 /* flags */).apply {
             addChange( // Home.
-                TransitionInfo.Change(mock(), mock()).apply {
+                TransitionInfo.Change(mock(), homeTaskLeash).apply {
                     parent = null
                     taskInfo =
                         TestRunningTaskInfoBuilder().setActivityType(ACTIVITY_TYPE_HOME).build()
@@ -348,7 +448,7 @@
                 }
             )
             addChange( // Dragged Task.
-                TransitionInfo.Change(mock(), mock()).apply {
+                TransitionInfo.Change(mock(), draggedTaskLeash).apply {
                     parent = null
                     taskInfo = draggedTask
                 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationDatastoreRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationDatastoreRepositoryTest.kt
new file mode 100644
index 0000000..4d40738
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationDatastoreRepositoryTest.kt
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.content.Context
+import android.testing.AndroidTestingRunner
+import androidx.datastore.core.DataStore
+import androidx.datastore.core.DataStoreFactory
+import androidx.datastore.dataStoreFile
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.wm.shell.desktopmode.education.data.AppHandleEducationDatastoreRepository
+import com.android.wm.shell.desktopmode.education.data.WindowingEducationProto
+import com.google.common.truth.Truth.assertThat
+import java.io.File
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@ExperimentalCoroutinesApi
+class AppHandleEducationDatastoreRepositoryTest {
+  private val testContext: Context = InstrumentationRegistry.getInstrumentation().targetContext
+  private lateinit var testDatastore: DataStore<WindowingEducationProto>
+  private lateinit var datastoreRepository: AppHandleEducationDatastoreRepository
+  private lateinit var datastoreScope: CoroutineScope
+
+  @Before
+  fun setUp() {
+    Dispatchers.setMain(StandardTestDispatcher())
+    datastoreScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
+    testDatastore =
+        DataStoreFactory.create(
+            serializer =
+                AppHandleEducationDatastoreRepository.Companion.WindowingEducationProtoSerializer,
+            scope = datastoreScope) {
+              testContext.dataStoreFile(APP_HANDLE_EDUCATION_DATASTORE_TEST_FILE)
+            }
+    datastoreRepository = AppHandleEducationDatastoreRepository(testDatastore)
+  }
+
+  @After
+  fun tearDown() {
+    File(ApplicationProvider.getApplicationContext<Context>().filesDir, "datastore")
+        .deleteRecursively()
+
+    datastoreScope.cancel()
+  }
+
+  @Test
+  fun getWindowingEducationProto_returnsCorrectProto() =
+      runTest(StandardTestDispatcher()) {
+        val windowingEducationProto =
+            createWindowingEducationProto(
+                educationViewedTimestampMillis = 123L,
+                featureUsedTimestampMillis = 124L,
+                appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 2),
+                appUsageStatsLastUpdateTimestampMillis = 125L)
+        testDatastore.updateData { windowingEducationProto }
+
+        val resultProto = datastoreRepository.windowingEducationProto()
+
+        assertThat(resultProto).isEqualTo(windowingEducationProto)
+      }
+
+  private fun createWindowingEducationProto(
+      educationViewedTimestampMillis: Long? = null,
+      featureUsedTimestampMillis: Long? = null,
+      appUsageStats: Map<String, Int>? = null,
+      appUsageStatsLastUpdateTimestampMillis: Long? = null
+  ): WindowingEducationProto =
+      WindowingEducationProto.newBuilder()
+          .apply {
+            if (educationViewedTimestampMillis != null)
+                setEducationViewedTimestampMillis(educationViewedTimestampMillis)
+            if (featureUsedTimestampMillis != null)
+                setFeatureUsedTimestampMillis(featureUsedTimestampMillis)
+            setAppHandleEducation(
+                createAppHandleEducationProto(
+                    appUsageStats, appUsageStatsLastUpdateTimestampMillis))
+          }
+          .build()
+
+  private fun createAppHandleEducationProto(
+      appUsageStats: Map<String, Int>? = null,
+      appUsageStatsLastUpdateTimestampMillis: Long? = null
+  ): WindowingEducationProto.AppHandleEducation =
+      WindowingEducationProto.AppHandleEducation.newBuilder()
+          .apply {
+            if (appUsageStats != null) putAllAppUsageStats(appUsageStats)
+            if (appUsageStatsLastUpdateTimestampMillis != null)
+                setAppUsageStatsLastUpdateTimestampMillis(appUsageStatsLastUpdateTimestampMillis)
+          }
+          .build()
+
+  companion object {
+    private const val GMAIL_PACKAGE_NAME = "com.google.android.gm"
+    private const val APP_HANDLE_EDUCATION_DATASTORE_TEST_FILE = "app_handle_education_test.pb"
+  }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt
index dd19d76..571bdd4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt
@@ -33,7 +33,7 @@
 import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.ToggleOverride.OVERRIDE_UNSET
 import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.WALLPAPER_ACTIVITY
 import com.google.common.truth.Truth.assertThat
-import org.junit.Before
+import org.junit.After
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -49,9 +49,9 @@
 
   @JvmField @Rule val setFlagsRule = SetFlagsRule()
 
-  @Before
-  fun setUp() {
-    resetCache()
+  @After
+  fun tearDown() {
+    resetToggleOverrideCache()
   }
 
   // TODO(b/348193756): Add tests
@@ -338,7 +338,7 @@
     }
   }
 
-  private fun resetCache() {
+  private fun resetToggleOverrideCache() {
     val cachedToggleOverride =
       DesktopModeFlags::class.java.getDeclaredField("cachedToggleOverride")
     cachedToggleOverride.isAccessible = true
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
index ee9f886..af6c077 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
@@ -370,6 +370,6 @@
                 Surface.ROTATION_0, taskSize, contentInsets, new Rect() /* letterboxInsets */,
                 false, true /* isRealSnapshot */, WINDOWING_MODE_FULLSCREEN,
                 0 /* systemUiVisibility */, false /* isTranslucent */,
-                hasImeSurface /* hasImeSurface */);
+                hasImeSurface /* hasImeSurface */, 0 /* uiMode */);
     }
 }
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 68975ec..fa905e2 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
@@ -36,7 +36,6 @@
 import android.os.Handler
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
-import android.platform.test.annotations.RequiresFlagsEnabled
 import android.platform.test.flag.junit.CheckFlagsRule
 import android.platform.test.flag.junit.DeviceFlagsValueProvider
 import android.platform.test.flag.junit.SetFlagsRule
@@ -56,9 +55,9 @@
 import android.view.SurfaceControl
 import android.view.SurfaceView
 import android.view.View
-import android.view.WindowInsets.Type.navigationBars
 import android.view.WindowInsets.Type.statusBars
 import android.window.WindowContainerTransaction
+import android.window.WindowContainerTransaction.HierarchyOp
 import androidx.test.filters.SmallTest
 import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
 import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
@@ -85,11 +84,11 @@
 import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
 import com.android.wm.shell.splitscreen.SplitScreenController
-import com.android.wm.shell.sysui.KeyguardChangeListener
 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.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeKeyguardChangeListener
 import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener
 import java.util.Optional
 import java.util.function.Consumer
@@ -172,6 +171,7 @@
     private lateinit var shellInit: ShellInit
     private lateinit var desktopModeOnInsetsChangedListener: DesktopModeOnInsetsChangedListener
     private lateinit var displayChangingListener: DisplayChangeController.OnDisplayChangingListener
+    private lateinit var desktopModeOnKeyguardChangedListener: DesktopModeKeyguardChangeListener
     private lateinit var desktopModeWindowDecorViewModel: DesktopModeWindowDecorViewModel
 
     @Before
@@ -225,17 +225,20 @@
 
         shellInit.init()
 
-        val insetListenerCaptor =
-            argumentCaptor<DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener>()
-        verify(displayInsetsController)
-            .addInsetsChangedListener(anyInt(), insetListenerCaptor.capture())
-        desktopModeOnInsetsChangedListener = insetListenerCaptor.firstValue
-
         val displayChangingListenerCaptor =
             argumentCaptor<DisplayChangeController.OnDisplayChangingListener>()
         verify(mockDisplayController)
             .addDisplayChangingController(displayChangingListenerCaptor.capture())
         displayChangingListener = displayChangingListenerCaptor.firstValue
+        val insetsChangedCaptor =
+                argumentCaptor<DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener>()
+        verify(displayInsetsController)
+            .addGlobalInsetsChangedListener(insetsChangedCaptor.capture())
+        desktopModeOnInsetsChangedListener = insetsChangedCaptor.firstValue
+        val keyguardChangedCaptor =
+            argumentCaptor<DesktopModeKeyguardChangeListener>()
+        verify(mockShellController).addKeyguardChangeListener(keyguardChangedCaptor.capture())
+        desktopModeOnKeyguardChangedListener = keyguardChangedCaptor.firstValue
     }
 
     @After
@@ -354,23 +357,33 @@
     }
 
     @Test
-    fun testCaptionIsNotCreatedWhenKeyguardIsVisible() {
-        val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true)
-        val keyguardListenerCaptor = argumentCaptor<KeyguardChangeListener>()
-        verify(mockShellController).addKeyguardChangeListener(keyguardListenerCaptor.capture())
+    fun testCloseButtonInFreeform() {
+        val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
+        val windowDecor = setUpMockDecorationForTask(task)
 
-        keyguardListenerCaptor.firstValue.onKeyguardVisibilityChanged(
-                true /* visible */,
-                true /* occluded */,
-                false /* animatingDismiss */
-        )
         onTaskOpening(task)
+        val onClickListenerCaptor = argumentCaptor<View.OnClickListener>()
+        verify(windowDecor).setCaptionListeners(
+            onClickListenerCaptor.capture(), any(), any(), any())
 
-        task.setWindowingMode(WINDOWING_MODE_UNDEFINED)
-        task.setWindowingMode(ACTIVITY_TYPE_UNDEFINED)
-        onTaskChanging(task)
+        val onClickListener = onClickListenerCaptor.firstValue
+        val view = mock(View::class.java)
+        whenever(view.id).thenReturn(R.id.close_window)
 
-        assertFalse(windowDecorByTaskIdSpy.contains(task.taskId))
+        val freeformTaskTransitionStarter = mock(FreeformTaskTransitionStarter::class.java)
+        desktopModeWindowDecorViewModel
+            .setFreeformTaskTransitionStarter(freeformTaskTransitionStarter)
+
+        onClickListener.onClick(view)
+
+        val transactionCaptor = argumentCaptor<WindowContainerTransaction>()
+        verify(freeformTaskTransitionStarter).startRemoveTransition(transactionCaptor.capture())
+        val wct = transactionCaptor.firstValue
+
+        assertEquals(1, wct.getHierarchyOps().size)
+        assertEquals(HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_TASK,
+                     wct.getHierarchyOps().get(0).getType())
+        assertEquals(task.token.asBinder(), wct.getHierarchyOps().get(0).getContainer())
     }
 
     @Test
@@ -418,67 +431,50 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_IMMERSIVE_HANDLE_HIDING)
-    fun testRelayoutRunsWhenStatusBarsInsetsSourceVisibilityChanges() {
-        val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM, focused = true)
-        val decoration = setUpMockDecorationForTask(task)
-
-        onTaskOpening(task)
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_IMMERSIVE_HANDLE_HIDING)
+    fun testInsetsStateChanged_notifiesAllDecorsInDisplay() {
+        val task1 = createTask(windowingMode = WINDOWING_MODE_FREEFORM, displayId = 1)
+        val decoration1 = setUpMockDecorationForTask(task1)
+        onTaskOpening(task1)
+        val task2 = createTask(windowingMode = WINDOWING_MODE_FREEFORM, displayId = 2)
+        val decoration2 = setUpMockDecorationForTask(task2)
+        onTaskOpening(task2)
+        val task3 = createTask(windowingMode = WINDOWING_MODE_FREEFORM, displayId = 2)
+        val decoration3 = setUpMockDecorationForTask(task3)
+        onTaskOpening(task3)
 
         // Add status bar insets source
-        val insetsState = InsetsState()
-        val statusBarInsetsSourceId = 0
-        val statusBarInsetsSource = InsetsSource(statusBarInsetsSourceId, statusBars())
-        statusBarInsetsSource.isVisible = false
-        insetsState.addSource(statusBarInsetsSource)
+        val insetsState = InsetsState().apply {
+            addSource(InsetsSource(0 /* id */, statusBars()).apply {
+                isVisible = false
+            })
+        }
+        desktopModeOnInsetsChangedListener.insetsChanged(2 /* displayId */, insetsState)
 
-        desktopModeOnInsetsChangedListener.insetsChanged(insetsState)
-
-        // Verify relayout occurs when status bar inset visibility changes
-        verify(decoration, times(1)).relayout(task)
+        verify(decoration1, never()).onInsetsStateChanged(insetsState)
+        verify(decoration2).onInsetsStateChanged(insetsState)
+        verify(decoration3).onInsetsStateChanged(insetsState)
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_IMMERSIVE_HANDLE_HIDING)
-    fun testRelayoutDoesNotRunWhenNonStatusBarsInsetsSourceVisibilityChanges() {
-        val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM, focused = true)
-        val decoration = setUpMockDecorationForTask(task)
+    fun testKeyguardState_notifiesAllDecors() {
+        val task1 = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
+        val decoration1 = setUpMockDecorationForTask(task1)
+        onTaskOpening(task1)
+        val task2 = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
+        val decoration2 = setUpMockDecorationForTask(task2)
+        onTaskOpening(task2)
+        val task3 = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
+        val decoration3 = setUpMockDecorationForTask(task3)
+        onTaskOpening(task3)
 
-        onTaskOpening(task)
+        desktopModeOnKeyguardChangedListener
+            .onKeyguardVisibilityChanged(true /* visible */, true /* occluded */,
+                false /* animatingDismiss */)
 
-        // Add navigation bar insets source
-        val insetsState = InsetsState()
-        val navigationBarInsetsSourceId = 1
-        val navigationBarInsetsSource = InsetsSource(navigationBarInsetsSourceId, navigationBars())
-        navigationBarInsetsSource.isVisible = false
-        insetsState.addSource(navigationBarInsetsSource)
-
-        desktopModeOnInsetsChangedListener.insetsChanged(insetsState)
-
-        // Verify relayout does not occur when non-status bar inset changes visibility
-        verify(decoration, never()).relayout(task)
-    }
-
-    @Test
-    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_IMMERSIVE_HANDLE_HIDING)
-    fun testRelayoutDoesNotRunWhenNonStatusBarsInsetSourceVisibilityDoesNotChange() {
-        val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM, focused = true)
-        val decoration = setUpMockDecorationForTask(task)
-
-        onTaskOpening(task)
-
-        // Add status bar insets source
-        val insetsState = InsetsState()
-        val statusBarInsetsSourceId = 0
-        val statusBarInsetsSource = InsetsSource(statusBarInsetsSourceId, statusBars())
-        statusBarInsetsSource.isVisible = false
-        insetsState.addSource(statusBarInsetsSource)
-
-        desktopModeOnInsetsChangedListener.insetsChanged(insetsState)
-        desktopModeOnInsetsChangedListener.insetsChanged(insetsState)
-
-        // Verify relayout runs only once when status bar inset visibility changes.
-        verify(decoration, times(1)).relayout(task)
+        verify(decoration1).onKeyguardStateChanged(true /* visible */, true /* occluded */)
+        verify(decoration2).onKeyguardStateChanged(true /* visible */, true /* occluded */)
+        verify(decoration3).onKeyguardStateChanged(true /* visible */, true /* occluded */)
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
index 77337a0..d8f395d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
@@ -56,7 +56,7 @@
 public class DragResizeWindowGeometryTests extends ShellTestCase {
     private static final Size TASK_SIZE = new Size(500, 1000);
     private static final int TASK_CORNER_RADIUS = 10;
-    private static final int EDGE_RESIZE_THICKNESS = 15;
+    private static final int EDGE_RESIZE_THICKNESS = 12;
     private static final int FINE_CORNER_SIZE = EDGE_RESIZE_THICKNESS * 2 + 10;
     private static final int LARGE_CORNER_SIZE = FINE_CORNER_SIZE + 10;
     private static final DragResizeWindowGeometry GEOMETRY = new DragResizeWindowGeometry(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 2ec3ab5..6154391c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -31,6 +31,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
 import static org.junit.Assert.assertEquals;
@@ -63,6 +64,7 @@
 import android.util.DisplayMetrics;
 import android.view.AttachedSurfaceControl;
 import android.view.Display;
+import android.view.InsetsSource;
 import android.view.InsetsState;
 import android.view.SurfaceControl;
 import android.view.SurfaceControlViewHost;
@@ -158,6 +160,8 @@
         mRelayoutParams.mShadowRadiusId = R.dimen.test_window_decor_shadow_radius;
         mRelayoutParams.mCornerRadius = CORNER_RADIUS;
 
+        when(mMockDisplayController.getDisplay(Display.DEFAULT_DISPLAY))
+                .thenReturn(mock(Display.class));
         doReturn(mMockSurfaceControlViewHost).when(mMockSurfaceControlViewHostFactory)
                 .create(any(), any(), any());
         when(mMockSurfaceControlViewHost.getRootSurfaceControl())
@@ -629,15 +633,15 @@
                 .setVisible(true)
                 .setBounds(new Rect(0, 0, 1000, 1000))
                 .build();
+        taskInfo.isFocused = true;
+        // Caption visible at first.
+        when(mMockDisplayController.getInsetsState(taskInfo.displayId))
+                .thenReturn(createInsetsState(statusBars(), true /* visible */));
         final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
-
-        // Run it once so that insets are added.
-        mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, captionBar()).setVisible(true);
         windowDecor.relayout(taskInfo);
 
-        // Run it again so that insets are removed.
-        mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, captionBar()).setVisible(false);
-        windowDecor.relayout(taskInfo);
+        // Hide caption so insets are removed.
+        windowDecor.onInsetsStateChanged(createInsetsState(statusBars(), false /* visible */));
 
         verify(mMockWindowContainerTransaction).removeInsetsSource(eq(taskInfo.token), any(),
                 eq(0) /* index */, eq(captionBar()));
@@ -656,10 +660,10 @@
                 .setVisible(true)
                 .setBounds(new Rect(0, 0, 1000, 1000))
                 .build();
-        final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
-
         // Hidden from the beginning, so no insets were ever added.
-        mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, captionBar()).setVisible(false);
+        when(mMockDisplayController.getInsetsState(taskInfo.displayId))
+                .thenReturn(createInsetsState(statusBars(), false /* visible */));
+        final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
         windowDecor.relayout(taskInfo);
 
         // Never added.
@@ -896,6 +900,78 @@
         windowDecor.updateViewHost(mRelayoutParams, null /* onDrawTransaction */, mRelayoutResult);
     }
 
+    @Test
+    public void onStatusBarVisibilityChange_shownToHidden_hidesCaption() {
+        final ActivityManager.RunningTaskInfo task = createTaskInfo();
+        when(mMockDisplayController.getInsetsState(task.displayId))
+                .thenReturn(createInsetsState(statusBars(), true /* visible */));
+        final TestWindowDecoration decor = createWindowDecoration(task);
+        decor.relayout(task);
+        assertTrue(decor.mIsCaptionVisible);
+
+        decor.onInsetsStateChanged(createInsetsState(statusBars(), false /* visible */));
+
+        assertFalse(decor.mIsCaptionVisible);
+    }
+
+    @Test
+    public void onStatusBarVisibilityChange_hiddenToShown_showsCaption() {
+        final ActivityManager.RunningTaskInfo task = createTaskInfo();
+        when(mMockDisplayController.getInsetsState(task.displayId))
+                .thenReturn(createInsetsState(statusBars(), false /* visible */));
+        final TestWindowDecoration decor = createWindowDecoration(task);
+        decor.relayout(task);
+        assertFalse(decor.mIsCaptionVisible);
+
+        decor.onInsetsStateChanged(createInsetsState(statusBars(), true /* visible */));
+
+        assertTrue(decor.mIsCaptionVisible);
+    }
+
+    @Test
+    public void onKeyguardStateChange_hiddenToShownAndOccluding_hidesCaption() {
+        final ActivityManager.RunningTaskInfo task = createTaskInfo();
+        when(mMockDisplayController.getInsetsState(task.displayId))
+                .thenReturn(createInsetsState(statusBars(), true /* visible */));
+        final TestWindowDecoration decor = createWindowDecoration(task);
+        decor.relayout(task);
+        assertTrue(decor.mIsCaptionVisible);
+
+        decor.onKeyguardStateChanged(true /* visible */, true /* occluding */);
+
+        assertFalse(decor.mIsCaptionVisible);
+    }
+
+    @Test
+    public void onKeyguardStateChange_showingAndOccludingToHidden_showsCaption() {
+        final ActivityManager.RunningTaskInfo task = createTaskInfo();
+        when(mMockDisplayController.getInsetsState(task.displayId))
+                .thenReturn(createInsetsState(statusBars(), true /* visible */));
+        final TestWindowDecoration decor = createWindowDecoration(task);
+        decor.onKeyguardStateChanged(true /* visible */, true /* occluding */);
+        assertFalse(decor.mIsCaptionVisible);
+
+        decor.onKeyguardStateChanged(false /* visible */, false /* occluding */);
+
+        assertTrue(decor.mIsCaptionVisible);
+    }
+
+    private ActivityManager.RunningTaskInfo createTaskInfo() {
+        final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+                .setVisible(true)
+                .build();
+        taskInfo.isFocused = true;
+        return taskInfo;
+    }
+
+    private InsetsState createInsetsState(@WindowInsets.Type.InsetsType int type, boolean visible) {
+        final InsetsState state = new InsetsState();
+        final InsetsSource source = new InsetsSource(0, type);
+        source.setVisible(visible);
+        state.addSource(source);
+        return state;
+    }
+
     private TestWindowDecoration createWindowDecoration(ActivityManager.RunningTaskInfo taskInfo) {
         return new TestWindowDecoration(mContext, mContext, mMockDisplayController,
                 mMockShellTaskOrganizer, taskInfo, mMockTaskSurface,
@@ -961,10 +1037,24 @@
             return null;
         }
 
+        @Override
+        int getCaptionViewId() {
+            return R.id.caption;
+        }
+
+        @Override
+        TestView inflateLayout(Context context, int layoutResId) {
+            if (layoutResId == R.layout.caption_layout) {
+                return mMockView;
+            }
+            return super.inflateLayout(context, layoutResId);
+        }
+
         void relayout(ActivityManager.RunningTaskInfo taskInfo,
                 boolean applyStartTransactionOnDraw) {
             mRelayoutParams.mRunningTaskInfo = taskInfo;
             mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
+            mRelayoutParams.mLayoutResId = R.layout.caption_layout;
             relayout(mRelayoutParams, mMockSurfaceControlStartT, mMockSurfaceControlFinishT,
                     mMockWindowContainerTransaction, mMockView, mRelayoutResult);
         }
diff --git a/libs/androidfw/BigBuffer.cpp b/libs/androidfw/BigBuffer.cpp
index bedfc49..43b56c3 100644
--- a/libs/androidfw/BigBuffer.cpp
+++ b/libs/androidfw/BigBuffer.cpp
@@ -17,8 +17,8 @@
 #include <androidfw/BigBuffer.h>
 
 #include <algorithm>
+#include <iterator>
 #include <memory>
-#include <vector>
 
 #include "android-base/logging.h"
 
@@ -78,10 +78,27 @@
 
 std::string BigBuffer::to_string() const {
   std::string result;
+  result.reserve(size_);
   for (const Block& block : blocks_) {
     result.append(block.buffer.get(), block.buffer.get() + block.size);
   }
   return result;
 }
 
+void BigBuffer::AppendBuffer(BigBuffer&& buffer) {
+  std::move(buffer.blocks_.begin(), buffer.blocks_.end(), std::back_inserter(blocks_));
+  size_ += buffer.size_;
+  buffer.blocks_.clear();
+  buffer.size_ = 0;
+}
+
+void BigBuffer::BackUp(size_t count) {
+  Block& block = blocks_.back();
+  block.size -= count;
+  size_ -= count;
+  // BigBuffer is supposed to always give zeroed memory, but backing up usually means
+  // something has been already written into the block. Erase it.
+  std::fill_n(block.buffer.get() + block.size, count, 0);
+}
+
 }  // namespace android
diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp
index 9824190..f066e46 100644
--- a/libs/androidfw/Idmap.cpp
+++ b/libs/androidfw/Idmap.cpp
@@ -121,7 +121,7 @@
                                                uint8_t target_assigned_package_id)
     : data_header_(data_header),
       entries_(entries),
-      target_assigned_package_id_(target_assigned_package_id) { };
+      target_assigned_package_id_(target_assigned_package_id) {}
 
 status_t OverlayDynamicRefTable::lookupResourceId(uint32_t* resId) const {
   const Idmap_overlay_entry* first_entry = entries_;
diff --git a/libs/androidfw/include/androidfw/BigBuffer.h b/libs/androidfw/include/androidfw/BigBuffer.h
index b99a4ed..c4cd7c5 100644
--- a/libs/androidfw/include/androidfw/BigBuffer.h
+++ b/libs/androidfw/include/androidfw/BigBuffer.h
@@ -14,13 +14,12 @@
  * limitations under the License.
  */
 
-#ifndef _ANDROID_BIG_BUFFER_H
-#define _ANDROID_BIG_BUFFER_H
+#pragma once
 
-#include <cstring>
 #include <memory>
 #include <string>
 #include <type_traits>
+#include <utility>
 #include <vector>
 
 #include "android-base/logging.h"
@@ -150,24 +149,11 @@
 
 template <typename T>
 inline T* BigBuffer::NextBlock(size_t count) {
-  static_assert(std::is_standard_layout<T>::value, "T must be standard_layout type");
+  static_assert(std::is_standard_layout_v<T>, "T must be standard_layout type");
   CHECK(count != 0);
   return reinterpret_cast<T*>(NextBlockImpl(sizeof(T) * count));
 }
 
-inline void BigBuffer::BackUp(size_t count) {
-  Block& block = blocks_.back();
-  block.size -= count;
-  size_ -= count;
-}
-
-inline void BigBuffer::AppendBuffer(BigBuffer&& buffer) {
-  std::move(buffer.blocks_.begin(), buffer.blocks_.end(), std::back_inserter(blocks_));
-  size_ += buffer.size_;
-  buffer.blocks_.clear();
-  buffer.size_ = 0;
-}
-
 inline void BigBuffer::Pad(size_t bytes) {
   NextBlock<char>(bytes);
 }
@@ -188,5 +174,3 @@
 }
 
 }  // namespace android
-
-#endif  // _ANDROID_BIG_BUFFER_H
diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h
index c32a38e..64b1f0c 100644
--- a/libs/androidfw/include/androidfw/Idmap.h
+++ b/libs/androidfw/include/androidfw/Idmap.h
@@ -171,14 +171,14 @@
   }
 
   // Returns a mapping from target resource ids to overlay values.
-  const IdmapResMap GetTargetResourcesMap(uint8_t target_assigned_package_id,
-                                          const OverlayDynamicRefTable* overlay_ref_table) const {
+  IdmapResMap GetTargetResourcesMap(uint8_t target_assigned_package_id,
+                                    const OverlayDynamicRefTable* overlay_ref_table) const {
     return IdmapResMap(data_header_, target_entries_, target_inline_entries_, inline_entry_values_,
                        configurations_, target_assigned_package_id, overlay_ref_table);
   }
 
   // Returns a dynamic reference table for a loaded overlay package.
-  const OverlayDynamicRefTable GetOverlayDynamicRefTable(uint8_t target_assigned_package_id) const {
+  OverlayDynamicRefTable GetOverlayDynamicRefTable(uint8_t target_assigned_package_id) const {
     return OverlayDynamicRefTable(data_header_, overlay_entries_, target_assigned_package_id);
   }
 
diff --git a/libs/androidfw/tests/BigBuffer_test.cpp b/libs/androidfw/tests/BigBuffer_test.cpp
index 382d21e..7e38f17 100644
--- a/libs/androidfw/tests/BigBuffer_test.cpp
+++ b/libs/androidfw/tests/BigBuffer_test.cpp
@@ -98,4 +98,20 @@
   ASSERT_EQ(8u, buffer.size());
 }
 
+TEST(BigBufferTest, BackUpZeroed) {
+  BigBuffer buffer(16);
+
+  auto block = buffer.NextBlock<char>(2);
+  ASSERT_TRUE(block != nullptr);
+  ASSERT_EQ(2u, buffer.size());
+  block[0] = 0x01;
+  block[1] = 0x02;
+  buffer.BackUp(1);
+  ASSERT_EQ(1u, buffer.size());
+  auto new_block = buffer.NextBlock<char>(1);
+  ASSERT_TRUE(new_block != nullptr);
+  ASSERT_EQ(2u, buffer.size());
+  ASSERT_EQ(0, *new_block);
+}
+
 }  // namespace android
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index d71f3b6..23cd3ce 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -143,6 +143,7 @@
                 "aconfig_text_flags_c_lib",
                 "server_configurable_flags",
                 "libaconfig_storage_read_api_cc",
+                "libgraphicsenv",
             ],
             static_libs: [
                 "libEGL_blobCache",
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index d184f64..b6476c9 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -42,6 +42,14 @@
 constexpr bool initialize_gl_always() {
     return false;
 }
+
+constexpr bool skip_eglmanager_telemetry() {
+    return false;
+}
+
+constexpr bool resample_gainmap_regions() {
+    return false;
+}
 }  // namespace hwui_flags
 #endif
 
@@ -100,6 +108,8 @@
 
 bool Properties::clipSurfaceViews = false;
 bool Properties::hdr10bitPlus = false;
+bool Properties::skipTelemetry = false;
+bool Properties::resampleGainmapRegions = false;
 
 int Properties::timeoutMultiplier = 1;
 
@@ -175,8 +185,12 @@
     clipSurfaceViews =
             base::GetBoolProperty("debug.hwui.clip_surfaceviews", hwui_flags::clip_surfaceviews());
     hdr10bitPlus = hwui_flags::hdr_10bit_plus();
+    resampleGainmapRegions = base::GetBoolProperty("debug.hwui.resample_gainmap_regions",
+                                                   hwui_flags::resample_gainmap_regions());
 
     timeoutMultiplier = android::base::GetIntProperty("ro.hw_timeout_multiplier", 1);
+    skipTelemetry = base::GetBoolProperty(PROPERTY_SKIP_EGLMANAGER_TELEMETRY,
+                                          hwui_flags::skip_eglmanager_telemetry());
 
     return (prevDebugLayersUpdates != debugLayersUpdates) || (prevDebugOverdraw != debugOverdraw);
 }
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index e264642..db47152 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -234,6 +234,8 @@
  */
 #define PROPERTY_INITIALIZE_GL_ALWAYS "debug.hwui.initialize_gl_always"
 
+#define PROPERTY_SKIP_EGLMANAGER_TELEMETRY "debug.hwui.skip_eglmanager_telemetry"
+
 ///////////////////////////////////////////////////////////////////////////////
 // Misc
 ///////////////////////////////////////////////////////////////////////////////
@@ -342,6 +344,8 @@
 
     static bool clipSurfaceViews;
     static bool hdr10bitPlus;
+    static bool skipTelemetry;
+    static bool resampleGainmapRegions;
 
     static int timeoutMultiplier;
 
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
index cd3ae53..a1f5168 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -97,3 +97,20 @@
   description: "Initialize GL even when HWUI is set to use Vulkan. This improves app startup time for apps using GL."
   bug: "335172671"
 }
+
+flag {
+  name: "skip_eglmanager_telemetry"
+  namespace: "core_graphics"
+  description: "Skip telemetry in EglManager's calls to eglCreateContext to avoid polluting telemetry"
+  bug: "347911216"
+}
+
+flag {
+  name: "resample_gainmap_regions"
+  namespace: "core_graphics"
+  description: "Resample gainmaps when decoding regions, to improve visual quality"
+  bug: "352847821"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/libs/hwui/jni/BitmapRegionDecoder.cpp b/libs/hwui/jni/BitmapRegionDecoder.cpp
index ea5c144..6a65b82 100644
--- a/libs/hwui/jni/BitmapRegionDecoder.cpp
+++ b/libs/hwui/jni/BitmapRegionDecoder.cpp
@@ -87,8 +87,17 @@
                                            requireUnpremul, prefColorSpace);
     }
 
-    bool decodeGainmapRegion(sp<uirenderer::Gainmap>* outGainmap, int outWidth, int outHeight,
-                             const SkIRect& desiredSubset, int sampleSize, bool requireUnpremul) {
+    // Decodes the gainmap region. If decoding succeeded, returns true and
+    // populate outGainmap with the decoded gainmap. Otherwise, returns false.
+    //
+    // Note that the desiredSubset is the logical region within the source
+    // gainmap that we want to decode. This is used for scaling into the final
+    // bitmap, since we do not want to include portions of the gainmap outside
+    // of this region. desiredSubset is also _not_ guaranteed to be
+    // pixel-aligned, so it's not possible to simply resize the resulting
+    // bitmap to accomplish this.
+    bool decodeGainmapRegion(sp<uirenderer::Gainmap>* outGainmap, SkISize bitmapDimensions,
+                             const SkRect& desiredSubset, int sampleSize, bool requireUnpremul) {
         SkColorType decodeColorType = mGainmapBRD->computeOutputColorType(kN32_SkColorType);
         sk_sp<SkColorSpace> decodeColorSpace =
                 mGainmapBRD->computeOutputColorSpace(decodeColorType, nullptr);
@@ -107,13 +116,30 @@
         // allocation type. RecyclingClippingPixelAllocator will populate this with the
         // actual alpha type in either allocPixelRef() or copyIfNecessary()
         sk_sp<Bitmap> nativeBitmap = Bitmap::allocateHeapBitmap(SkImageInfo::Make(
-                outWidth, outHeight, decodeColorType, kPremul_SkAlphaType, decodeColorSpace));
+                bitmapDimensions, decodeColorType, kPremul_SkAlphaType, decodeColorSpace));
         if (!nativeBitmap) {
             ALOGE("OOM allocating Bitmap for Gainmap");
             return false;
         }
-        RecyclingClippingPixelAllocator allocator(nativeBitmap.get(), false);
-        if (!mGainmapBRD->decodeRegion(&bm, &allocator, desiredSubset, sampleSize, decodeColorType,
+
+        // Round out the subset so that we decode a slightly larger region, in
+        // case the subset has fractional components.
+        SkIRect roundedSubset = desiredSubset.roundOut();
+
+        // Map the desired subset to the space of the decoded gainmap. The
+        // subset is repositioned relative to the resulting bitmap, and then
+        // scaled to respect the sampleSize.
+        // This assumes that the subset will not be modified by the decoder, which is true
+        // for existing gainmap formats.
+        SkRect logicalSubset = desiredSubset.makeOffset(-std::floorf(desiredSubset.left()),
+                                                        -std::floorf(desiredSubset.top()));
+        logicalSubset.fLeft /= sampleSize;
+        logicalSubset.fTop /= sampleSize;
+        logicalSubset.fRight /= sampleSize;
+        logicalSubset.fBottom /= sampleSize;
+
+        RecyclingClippingPixelAllocator allocator(nativeBitmap.get(), false, logicalSubset);
+        if (!mGainmapBRD->decodeRegion(&bm, &allocator, roundedSubset, sampleSize, decodeColorType,
                                        requireUnpremul, decodeColorSpace)) {
             ALOGE("Error decoding Gainmap region");
             return false;
@@ -130,16 +156,31 @@
         return true;
     }
 
-    SkIRect calculateGainmapRegion(const SkIRect& mainImageRegion, int* inOutWidth,
-                                   int* inOutHeight) {
+    struct Projection {
+        SkRect srcRect;
+        SkISize destSize;
+    };
+    Projection calculateGainmapRegion(const SkIRect& mainImageRegion, SkISize dimensions) {
         const float scaleX = ((float)mGainmapBRD->width()) / mMainImageBRD->width();
         const float scaleY = ((float)mGainmapBRD->height()) / mMainImageBRD->height();
-        *inOutWidth *= scaleX;
-        *inOutHeight *= scaleY;
-        // TODO: Account for rounding error?
-        return SkIRect::MakeLTRB(mainImageRegion.left() * scaleX, mainImageRegion.top() * scaleY,
-                                 mainImageRegion.right() * scaleX,
-                                 mainImageRegion.bottom() * scaleY);
+
+        if (uirenderer::Properties::resampleGainmapRegions) {
+            const auto srcRect = SkRect::MakeLTRB(
+                    mainImageRegion.left() * scaleX, mainImageRegion.top() * scaleY,
+                    mainImageRegion.right() * scaleX, mainImageRegion.bottom() * scaleY);
+            // Request a slightly larger destination size so that the gainmap
+            // subset we want fits entirely in this size.
+            const auto destSize = SkISize::Make(std::ceil(dimensions.width() * scaleX),
+                                                std::ceil(dimensions.height() * scaleY));
+            return Projection{.srcRect = srcRect, .destSize = destSize};
+        } else {
+            const auto srcRect = SkRect::Make(SkIRect::MakeLTRB(
+                    mainImageRegion.left() * scaleX, mainImageRegion.top() * scaleY,
+                    mainImageRegion.right() * scaleX, mainImageRegion.bottom() * scaleY));
+            const auto destSize =
+                    SkISize::Make(dimensions.width() * scaleX, dimensions.height() * scaleY);
+            return Projection{.srcRect = srcRect, .destSize = destSize};
+        }
     }
 
     bool hasGainmap() { return mGainmapBRD != nullptr; }
@@ -327,16 +368,16 @@
     sp<uirenderer::Gainmap> gainmap;
     bool hasGainmap = brd->hasGainmap();
     if (hasGainmap) {
-        int gainmapWidth = bitmap.width();
-        int gainmapHeight = bitmap.height();
+        SkISize gainmapDims = SkISize::Make(bitmap.width(), bitmap.height());
         if (javaBitmap) {
             // If we are recycling we must match the inBitmap's relative dimensions
-            gainmapWidth = recycledBitmap->width();
-            gainmapHeight = recycledBitmap->height();
+            gainmapDims.fWidth = recycledBitmap->width();
+            gainmapDims.fHeight = recycledBitmap->height();
         }
-        SkIRect gainmapSubset = brd->calculateGainmapRegion(subset, &gainmapWidth, &gainmapHeight);
-        if (!brd->decodeGainmapRegion(&gainmap, gainmapWidth, gainmapHeight, gainmapSubset,
-                                      sampleSize, requireUnpremul)) {
+        BitmapRegionDecoderWrapper::Projection gainmapProjection =
+                brd->calculateGainmapRegion(subset, gainmapDims);
+        if (!brd->decodeGainmapRegion(&gainmap, gainmapProjection.destSize,
+                                      gainmapProjection.srcRect, sampleSize, requireUnpremul)) {
             // If there is an error decoding Gainmap - we don't fail. We just don't provide Gainmap
             hasGainmap = false;
         }
diff --git a/libs/hwui/jni/Graphics.cpp b/libs/hwui/jni/Graphics.cpp
index a88139d..258bf91 100644
--- a/libs/hwui/jni/Graphics.cpp
+++ b/libs/hwui/jni/Graphics.cpp
@@ -1,12 +1,14 @@
 #include <assert.h>
+#include <cutils/ashmem.h>
+#include <hwui/Canvas.h>
+#include <log/log.h>
+#include <nativehelper/JNIHelp.h>
 #include <unistd.h>
 
-#include "jni.h"
-#include <nativehelper/JNIHelp.h>
 #include "GraphicsJNI.h"
-
 #include "SkBitmap.h"
 #include "SkCanvas.h"
+#include "SkColor.h"
 #include "SkColorSpace.h"
 #include "SkFontMetrics.h"
 #include "SkImageInfo.h"
@@ -14,10 +16,9 @@
 #include "SkPoint.h"
 #include "SkRect.h"
 #include "SkRegion.h"
+#include "SkSamplingOptions.h"
 #include "SkTypes.h"
-#include <cutils/ashmem.h>
-#include <hwui/Canvas.h>
-#include <log/log.h>
+#include "jni.h"
 
 using namespace android;
 
@@ -630,13 +631,15 @@
 
 ////////////////////////////////////////////////////////////////////////////////
 
-RecyclingClippingPixelAllocator::RecyclingClippingPixelAllocator(android::Bitmap* recycledBitmap,
-                                                                 bool mustMatchColorType)
+RecyclingClippingPixelAllocator::RecyclingClippingPixelAllocator(
+        android::Bitmap* recycledBitmap, bool mustMatchColorType,
+        std::optional<SkRect> desiredSubset)
         : mRecycledBitmap(recycledBitmap)
         , mRecycledBytes(recycledBitmap ? recycledBitmap->getAllocationByteCount() : 0)
         , mSkiaBitmap(nullptr)
         , mNeedsCopy(false)
-        , mMustMatchColorType(mustMatchColorType) {}
+        , mMustMatchColorType(mustMatchColorType)
+        , mDesiredSubset(getSourceBoundsForUpsample(desiredSubset)) {}
 
 RecyclingClippingPixelAllocator::~RecyclingClippingPixelAllocator() {}
 
@@ -668,7 +671,8 @@
     const SkImageInfo maxInfo = bitmap->info().makeWH(maxWidth, maxHeight);
     const size_t rowBytes = maxInfo.minRowBytes();
     const size_t bytesNeeded = maxInfo.computeByteSize(rowBytes);
-    if (bytesNeeded <= mRecycledBytes) {
+
+    if (!mDesiredSubset && bytesNeeded <= mRecycledBytes) {
         // Here we take advantage of reconfigure() to reset the rowBytes
         // of mRecycledBitmap.  It is very important that we pass in
         // mRecycledBitmap->info() for the SkImageInfo.  According to the
@@ -712,20 +716,31 @@
     if (mNeedsCopy) {
         mRecycledBitmap->ref();
         android::Bitmap* recycledPixels = mRecycledBitmap;
-        void* dst = recycledPixels->pixels();
-        const size_t dstRowBytes = mRecycledBitmap->rowBytes();
-        const size_t bytesToCopy = std::min(mRecycledBitmap->info().minRowBytes(),
-                mSkiaBitmap->info().minRowBytes());
-        const int rowsToCopy = std::min(mRecycledBitmap->info().height(),
-                mSkiaBitmap->info().height());
-        for (int y = 0; y < rowsToCopy; y++) {
-            memcpy(dst, mSkiaBitmap->getAddr(0, y), bytesToCopy);
-            // Cast to bytes in order to apply the dstRowBytes offset correctly.
-            dst = reinterpret_cast<void*>(
-                    reinterpret_cast<uint8_t*>(dst) + dstRowBytes);
+        if (mDesiredSubset) {
+            recycledPixels->setAlphaType(mSkiaBitmap->alphaType());
+            recycledPixels->setColorSpace(mSkiaBitmap->refColorSpace());
+
+            auto canvas = SkCanvas(recycledPixels->getSkBitmap());
+            SkRect destination = SkRect::Make(recycledPixels->info().bounds());
+            destination.intersect(SkRect::Make(mSkiaBitmap->info().bounds()));
+            canvas.drawImageRect(mSkiaBitmap->asImage(), *mDesiredSubset, destination,
+                                 SkSamplingOptions(SkFilterMode::kLinear), nullptr,
+                                 SkCanvas::kFast_SrcRectConstraint);
+        } else {
+            void* dst = recycledPixels->pixels();
+            const size_t dstRowBytes = mRecycledBitmap->rowBytes();
+            const size_t bytesToCopy = std::min(mRecycledBitmap->info().minRowBytes(),
+                                                mSkiaBitmap->info().minRowBytes());
+            const int rowsToCopy =
+                    std::min(mRecycledBitmap->info().height(), mSkiaBitmap->info().height());
+            for (int y = 0; y < rowsToCopy; y++) {
+                memcpy(dst, mSkiaBitmap->getAddr(0, y), bytesToCopy);
+                // Cast to bytes in order to apply the dstRowBytes offset correctly.
+                dst = reinterpret_cast<void*>(reinterpret_cast<uint8_t*>(dst) + dstRowBytes);
+            }
+            recycledPixels->setAlphaType(mSkiaBitmap->alphaType());
+            recycledPixels->setColorSpace(mSkiaBitmap->refColorSpace());
         }
-        recycledPixels->setAlphaType(mSkiaBitmap->alphaType());
-        recycledPixels->setColorSpace(mSkiaBitmap->refColorSpace());
         recycledPixels->notifyPixelsChanged();
         recycledPixels->unref();
     }
@@ -733,6 +748,20 @@
     mSkiaBitmap = nullptr;
 }
 
+std::optional<SkRect> RecyclingClippingPixelAllocator::getSourceBoundsForUpsample(
+        std::optional<SkRect> subset) {
+    if (!uirenderer::Properties::resampleGainmapRegions || !subset || subset->isEmpty()) {
+        return std::nullopt;
+    }
+
+    if (subset->left() == floor(subset->left()) && subset->top() == floor(subset->top()) &&
+        subset->right() == floor(subset->right()) && subset->bottom() == floor(subset->bottom())) {
+        return std::nullopt;
+    }
+
+    return subset;
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 
 AshmemPixelAllocator::AshmemPixelAllocator(JNIEnv *env) {
diff --git a/libs/hwui/jni/GraphicsJNI.h b/libs/hwui/jni/GraphicsJNI.h
index b0a1074..4b08f8d 100644
--- a/libs/hwui/jni/GraphicsJNI.h
+++ b/libs/hwui/jni/GraphicsJNI.h
@@ -216,8 +216,8 @@
  */
 class RecyclingClippingPixelAllocator : public android::skia::BRDAllocator {
 public:
-    RecyclingClippingPixelAllocator(android::Bitmap* recycledBitmap,
-                                    bool mustMatchColorType = true);
+    RecyclingClippingPixelAllocator(android::Bitmap* recycledBitmap, bool mustMatchColorType = true,
+                                    std::optional<SkRect> desiredSubset = std::nullopt);
 
     ~RecyclingClippingPixelAllocator();
 
@@ -241,11 +241,24 @@
     SkCodec::ZeroInitialized zeroInit() const override { return SkCodec::kNo_ZeroInitialized; }
 
 private:
+    /**
+     *  Optionally returns a subset rectangle that we need to upsample from.
+     *  E.g., a gainmap subset may be decoded in a slightly larger rectangle
+     *  than is needed (in order to correctly preserve gainmap alignment when
+     *  rendering at display time), so we need to re-sample the "intended"
+     *  gainmap back up to the bitmap dimensions.
+     *
+     *  If we don't need to upsample from a subregion, then returns an empty
+     *  optional
+     */
+    static std::optional<SkRect> getSourceBoundsForUpsample(std::optional<SkRect> subset);
+
     android::Bitmap* mRecycledBitmap;
     const size_t     mRecycledBytes;
     SkBitmap*        mSkiaBitmap;
     bool             mNeedsCopy;
     const bool mMustMatchColorType;
+    const std::optional<SkRect> mDesiredSubset;
 };
 
 class AshmemPixelAllocator : public SkBitmap::Allocator {
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index 708b011..6010445 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -19,6 +19,7 @@
 #include <EGL/eglext.h>
 #include <GLES/gl.h>
 #include <cutils/properties.h>
+#include <graphicsenv/GpuStatsInfo.h>
 #include <log/log.h>
 #include <sync/sync.h>
 #include <utils/Trace.h>
@@ -366,6 +367,10 @@
         contextAttributes.push_back(EGL_CONTEXT_PRIORITY_LEVEL_IMG);
         contextAttributes.push_back(Properties::contextPriority);
     }
+    if (Properties::skipTelemetry) {
+        contextAttributes.push_back(EGL_TELEMETRY_HINT_ANDROID);
+        contextAttributes.push_back(android::GpuStatsInfo::SKIP_TELEMETRY);
+    }
     contextAttributes.push_back(EGL_NONE);
     mEglContext = eglCreateContext(
             mEglDisplay, EglExtensions.noConfigContext ? ((EGLConfig) nullptr) : mEglConfig,
diff --git a/media/jni/JetPlayer.h b/media/jni/JetPlayer.h
index bb569bc..4cc266d 100644
--- a/media/jni/JetPlayer.h
+++ b/media/jni/JetPlayer.h
@@ -40,7 +40,7 @@
     static const int JET_NUMQUEUEDSEGMENT_UPDATE = 3;
     static const int JET_PAUSE_UPDATE            = 4;
 
-    JetPlayer(void *javaJetPlayer,
+    explicit JetPlayer(void *javaJetPlayer,
             int maxTracks = 32,
             int trackBufferSize = 1200);
     ~JetPlayer();
@@ -69,7 +69,6 @@
     void                fireUpdateOnStatusChange();
     void                fireEventsFromJetQueue();
 
-    JetPlayer() {} // no default constructor
     void dump();
     void dumpJetStatus(S_JET_STATUS* pJetStatus);
 
@@ -96,7 +95,7 @@
 
     class JetPlayerThread : public Thread {
     public:
-        JetPlayerThread(JetPlayer *player) : mPlayer(player) {
+        explicit JetPlayerThread(JetPlayer *player) : mPlayer(player) {
         }
 
     protected:
@@ -106,8 +105,7 @@
         JetPlayer *mPlayer;
 
         bool threadLoop() {
-            int result;
-            result = mPlayer->render();
+            mPlayer->render();
             return false;
         }
 
diff --git a/native/android/surface_control_input_receiver.cpp b/native/android/surface_control_input_receiver.cpp
index a84ec73..7caa943 100644
--- a/native/android/surface_control_input_receiver.cpp
+++ b/native/android/surface_control_input_receiver.cpp
@@ -41,7 +41,7 @@
                   const sp<IBinder>& clientToken, const sp<InputTransferToken>& inputTransferToken,
                   AInputReceiverCallbacks* callbacks)
           : mCallbacks(callbacks),
-            mInputConsumer(inputChannel, looper, *this),
+            mInputConsumer(inputChannel, looper, *this, nullptr),
             mClientToken(clientToken),
             mInputTransferToken(inputTransferToken) {}
 
diff --git a/nfc/java/android/nfc/NfcActivityManager.java b/nfc/java/android/nfc/NfcActivityManager.java
index 0e40db6..0eb846d 100644
--- a/nfc/java/android/nfc/NfcActivityManager.java
+++ b/nfc/java/android/nfc/NfcActivityManager.java
@@ -236,11 +236,7 @@
 
     public void setReaderMode(Binder token, int flags, Bundle extras) {
         if (DBG) Log.d(TAG, "Setting reader mode");
-        try {
-            NfcAdapter.sService.setReaderMode(token, this, flags, extras);
-        } catch (RemoteException e) {
-            mAdapter.attemptDeadServiceRecovery(e);
-        }
+        NfcAdapter.callService(() -> NfcAdapter.sService.setReaderMode(token, this, flags, extras));
     }
 
     /**
@@ -248,19 +244,11 @@
      * Makes IPC call - do not hold lock.
      */
     void requestNfcServiceCallback() {
-        try {
-            NfcAdapter.sService.setAppCallback(this);
-        } catch (RemoteException e) {
-            mAdapter.attemptDeadServiceRecovery(e);
-        }
+        NfcAdapter.callService(() -> NfcAdapter.sService.setAppCallback(this));
     }
 
     void verifyNfcPermission() {
-        try {
-            NfcAdapter.sService.verifyNfcPermission();
-        } catch (RemoteException e) {
-            mAdapter.attemptDeadServiceRecovery(e);
-        }
+        NfcAdapter.callService(() -> NfcAdapter.sService.verifyNfcPermission());
     }
 
     @Override
@@ -406,11 +394,8 @@
     }
 
     private void changeDiscoveryTech(Binder token, int pollTech, int listenTech) {
-        try {
-            NfcAdapter.sService.updateDiscoveryTechnology(token, pollTech, listenTech);
-        } catch (RemoteException e) {
-            mAdapter.attemptDeadServiceRecovery(e);
-        }
+        NfcAdapter.callService(
+            () -> NfcAdapter.sService.updateDiscoveryTechnology(token, pollTech, listenTech));
     }
 
 }
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index 0ffab4b..b36b705 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -922,8 +922,8 @@
      * @hide
      */
     @UnsupportedAppUsage
-    public INfcAdapter getService() {
-        isEnabled();  // NOP call to recover sService if it is stale
+    public static INfcAdapter getService() {
+        isEnabledStatic();  // NOP call to recover sService if it is stale
         return sService;
     }
 
@@ -931,8 +931,8 @@
      * Returns the binder interface to the tag service.
      * @hide
      */
-    public INfcTag getTagService() {
-        isEnabled();  // NOP call to recover sTagService if it is stale
+    public static INfcTag getTagService() {
+        isEnabledStatic();  // NOP call to recover sTagService if it is stale
         return sTagService;
     }
 
@@ -940,8 +940,8 @@
      * Returns the binder interface to the card emulation service.
      * @hide
      */
-    public INfcCardEmulation getCardEmulationService() {
-        isEnabled();
+    public static INfcCardEmulation getCardEmulationService() {
+        isEnabledStatic();
         return sCardEmulationService;
     }
 
@@ -949,8 +949,8 @@
      * Returns the binder interface to the NFC-F card emulation service.
      * @hide
      */
-    public INfcFCardEmulation getNfcFCardEmulationService() {
-        isEnabled();
+    public static INfcFCardEmulation getNfcFCardEmulationService() {
+        isEnabledStatic();
         return sNfcFCardEmulationService;
     }
 
@@ -973,14 +973,14 @@
      * @hide
      */
     @UnsupportedAppUsage
-    public void attemptDeadServiceRecovery(Exception e) {
+    public static void attemptDeadServiceRecovery(RemoteException e) {
         Log.e(TAG, "NFC service dead - attempting to recover", e);
         INfcAdapter service = getServiceInterface();
         if (service == null) {
             Log.e(TAG, "could not retrieve NFC service during service recovery");
             // nothing more can be done now, sService is still stale, we'll hit
             // this recovery path again later
-            return;
+            e.rethrowAsRuntimeException();
         }
         // assigning to sService is not thread-safe, but this is best-effort code
         // and on a well-behaved system should never happen
@@ -993,7 +993,7 @@
                 Log.e(TAG, "could not retrieve NFC tag service during service recovery");
                 // nothing more can be done now, sService is still stale, we'll hit
                 // this recovery path again later
-                return;
+                ee.rethrowAsRuntimeException();
             }
         }
 
@@ -1014,24 +1014,27 @@
                         "could not retrieve NFC-F card emulation service during service recovery");
             }
         }
-
-        return;
     }
 
-    private boolean isCardEmulationEnabled() {
+    private static boolean isCardEmulationEnabled() {
         if (sHasCeFeature) {
             return (sCardEmulationService != null || sNfcFCardEmulationService != null);
         }
         return false;
     }
 
-    private boolean isTagReadingEnabled() {
+    private static boolean isTagReadingEnabled() {
         if (sHasNfcFeature) {
             return sTagService != null;
         }
         return false;
     }
 
+    private static boolean isEnabledStatic() {
+        boolean serviceState = callServiceReturn(() -> sService.getState() == STATE_ON, false);
+        return serviceState
+                && (isTagReadingEnabled() || isCardEmulationEnabled() || sHasNfcWlcFeature);
+    }
 
     /**
      * Return true if this NFC Adapter has any features enabled.
@@ -1046,24 +1049,7 @@
      * @return true if this NFC Adapter has any features enabled
      */
     public boolean isEnabled() {
-        boolean serviceState = false;
-        try {
-            serviceState = sService.getState() == STATE_ON;
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                serviceState = sService.getState() == STATE_ON;
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-        }
-        return serviceState
-                && (isTagReadingEnabled() || isCardEmulationEnabled() || sHasNfcWlcFeature);
+        return isEnabledStatic();
     }
 
     /**
@@ -1157,11 +1143,7 @@
      * @hide
      */
     public void pausePolling(int timeoutInMs) {
-        try {
-            sService.pausePolling(timeoutInMs);
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-        }
+        callService(() -> sService.pausePolling(timeoutInMs));
     }
 
 
@@ -1222,11 +1204,7 @@
      * @hide
      */
     public void resumePolling() {
-        try {
-            sService.resumePolling();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-        }
+        callService(() -> sService.resumePolling());
     }
 
     /**
@@ -1645,15 +1623,10 @@
         if (activity == null || intent == null) {
             throw new NullPointerException();
         }
-        try {
-            TechListParcel parcel = null;
-            if (techLists != null && techLists.length > 0) {
-                parcel = new TechListParcel(techLists);
-            }
-            sService.setForegroundDispatch(intent, filters, parcel);
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-        }
+        final TechListParcel parcel = (techLists != null && techLists.length > 0)
+            ? new TechListParcel(techLists)
+            : null;
+        callService(() -> sService.setForegroundDispatch(intent, filters, parcel));
     }
 
     /**
@@ -1677,11 +1650,7 @@
                 throw new UnsupportedOperationException();
             }
         }
-        try {
-            sService.setForegroundDispatch(null, null, null);
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-        }
+        callService(() -> sService.setForegroundDispatch(null, null, null));
     }
 
     /**
@@ -1762,11 +1731,7 @@
         }
         Binder token = new Binder();
         int flags = enable ? ENABLE_POLLING_FLAGS : DISABLE_POLLING_FLAGS;
-        try {
-            NfcAdapter.sService.setReaderMode(token, null, flags, null);
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-        }
+        callService(() -> sService.setReaderMode(token, null, flags, null));
     }
 
     /**
@@ -1838,12 +1803,8 @@
                 && ((pollTechnology & FLAG_SET_DEFAULT_TECH) == FLAG_SET_DEFAULT_TECH
                 || (listenTechnology & FLAG_SET_DEFAULT_TECH) == FLAG_SET_DEFAULT_TECH)) {
             Binder token = new Binder();
-            try {
-                NfcAdapter.sService.updateDiscoveryTechnology(token,
-                        pollTechnology, listenTechnology);
-            } catch (RemoteException e) {
-                attemptDeadServiceRecovery(e);
-            }
+            callService( () ->
+                sService.updateDiscoveryTechnology(token, pollTechnology, listenTechnology));
         } else {
             mNfcActivityManager.setDiscoveryTech(activity, pollTechnology, listenTechnology);
         }
@@ -2227,11 +2188,7 @@
         if (tag == null) {
             throw new NullPointerException("tag cannot be null");
         }
-        try {
-            sService.dispatch(tag);
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-        }
+        callService(() -> sService.dispatch(tag));
     }
 
     /**
@@ -2267,8 +2224,10 @@
             synchronized (mLock) {
                 if (mNfcUnlockHandlers.containsKey(unlockHandler)) {
                     // update the tag technologies
-                    sService.removeNfcUnlockHandler(mNfcUnlockHandlers.get(unlockHandler));
-                    mNfcUnlockHandlers.remove(unlockHandler);
+                    callService(() -> {
+                        sService.removeNfcUnlockHandler(mNfcUnlockHandlers.get(unlockHandler));
+                        mNfcUnlockHandlers.remove(unlockHandler);
+                    });
                 }
 
                 INfcUnlockHandler.Stub iHandler = new INfcUnlockHandler.Stub() {
@@ -2277,20 +2236,18 @@
                         return unlockHandler.onUnlockAttempted(tag);
                     }
                 };
-
-                sService.addNfcUnlockHandler(iHandler,
-                        Tag.getTechCodesFromStrings(tagTechnologies));
-                mNfcUnlockHandlers.put(unlockHandler, iHandler);
+                return callServiceReturn(() -> {
+                        sService.addNfcUnlockHandler(
+                            iHandler, Tag.getTechCodesFromStrings(tagTechnologies));
+                        mNfcUnlockHandlers.put(unlockHandler, iHandler);
+                        return true;
+                    }, false);
             }
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            return false;
         } catch (IllegalArgumentException e) {
             Log.e(TAG, "Unable to register LockscreenDispatch", e);
             return false;
         }
 
-        return true;
     }
 
     /**
@@ -2307,17 +2264,14 @@
                 throw new UnsupportedOperationException();
             }
         }
-        try {
-            synchronized (mLock) {
-                if (mNfcUnlockHandlers.containsKey(unlockHandler)) {
+        synchronized (mLock) {
+            if (mNfcUnlockHandlers.containsKey(unlockHandler)) {
+                return callServiceReturn(() -> {
                     sService.removeNfcUnlockHandler(mNfcUnlockHandlers.remove(unlockHandler));
-                }
-
-                return true;
+                    return true;
+                }, false);
             }
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            return false;
+            return true;
         }
     }
 
@@ -2525,26 +2479,8 @@
             Log.e(TAG, "TagIntentAppPreference is not supported");
             throw new UnsupportedOperationException();
         }
-        try {
-            Map<String, Boolean> result = (Map<String, Boolean>) sService
-                     .getTagIntentAppPreferenceForUser(userId);
-            return result;
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return Collections.emptyMap();
-            }
-            try {
-                Map<String, Boolean> result = (Map<String, Boolean>) sService
-                        .getTagIntentAppPreferenceForUser(userId);
-                return result;
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return Collections.emptyMap();
-        }
+        return callServiceReturn( () ->
+            sService.getTagIntentAppPreferenceForUser(userId), Collections.emptyMap());
     }
 
     /**
@@ -2590,50 +2526,44 @@
         callService(() ->  sService.notifyTestHceData(technology, data));
     }
 
+    /** @hide */
     interface ServiceCall {
         void call() throws RemoteException;
     }
-
-    void callService(ServiceCall call) {
+    /** @hide */
+    static void callService(ServiceCall call) {
         try {
             if (sService == null) {
-                attemptDeadServiceRecovery(null);
+                attemptDeadServiceRecovery(new RemoteException("NFC Service is null"));
             }
             call.call();
         } catch (RemoteException e) {
             attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return;
-            }
             try {
                 call.call();
             } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
+                ee.rethrowAsRuntimeException();
             }
         }
     }
+    /** @hide */
     interface ServiceCallReturn<T> {
         T call() throws RemoteException;
     }
-    <T> T callServiceReturn(ServiceCallReturn<T> call, T defaultReturn) {
+    /** @hide */
+    static <T> T callServiceReturn(ServiceCallReturn<T> call, T defaultReturn) {
         try {
             if (sService == null) {
-                attemptDeadServiceRecovery(null);
+                attemptDeadServiceRecovery(new RemoteException("NFC Service is null"));
             }
             return call.call();
         } catch (RemoteException e) {
             attemptDeadServiceRecovery(e);
             // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return defaultReturn;
-            }
             try {
                 return call.call();
             } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
+                ee.rethrowAsRuntimeException();
             }
         }
         return defaultReturn;
diff --git a/nfc/java/android/nfc/NfcOemExtension.java b/nfc/java/android/nfc/NfcOemExtension.java
index f6138a6..2ec819c 100644
--- a/nfc/java/android/nfc/NfcOemExtension.java
+++ b/nfc/java/android/nfc/NfcOemExtension.java
@@ -89,13 +89,11 @@
                         + "registering");
                 throw new IllegalArgumentException();
             }
-            try {
+            NfcAdapter.callService(() -> {
                 NfcAdapter.sService.registerOemExtensionCallback(mOemNfcExtensionCallback);
                 mCallback = callback;
                 mExecutor = executor;
-            } catch (RemoteException e) {
-                mAdapter.attemptDeadServiceRecovery(e);
-            }
+            });
         }
     }
 
@@ -117,13 +115,11 @@
                 Log.e(TAG, "Callback not registered");
                 throw new IllegalArgumentException();
             }
-            try {
+            NfcAdapter.callService(() -> {
                 NfcAdapter.sService.unregisterOemExtensionCallback(mOemNfcExtensionCallback);
                 mCallback = null;
                 mExecutor = null;
-            } catch (RemoteException e) {
-                mAdapter.attemptDeadServiceRecovery(e);
-            }
+            });
         }
     }
 
@@ -134,11 +130,7 @@
     @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
     @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
     public void clearPreference() {
-        try {
-            NfcAdapter.sService.clearPreference();
-        } catch (RemoteException e) {
-            mAdapter.attemptDeadServiceRecovery(e);
-        }
+        NfcAdapter.callService(() -> NfcAdapter.sService.clearPreference());
     }
 
     /**
@@ -147,11 +139,7 @@
     @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
     @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
     public void synchronizeScreenState() {
-        try {
-            NfcAdapter.sService.setScreenState();
-        } catch (RemoteException e) {
-            mAdapter.attemptDeadServiceRecovery(e);
-        }
+        NfcAdapter.callService(() -> NfcAdapter.sService.setScreenState());
     }
 
     /**
@@ -162,11 +150,7 @@
     @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
     @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
     public void maybeTriggerFirmwareUpdate() {
-        try {
-            NfcAdapter.sService.checkFirmware();
-        } catch (RemoteException e) {
-            mAdapter.attemptDeadServiceRecovery(e);
-        }
+        NfcAdapter.callService(() -> NfcAdapter.sService.checkFirmware());
     }
 
     private final class NfcOemExtensionCallback extends INfcOemExtensionCallback.Stub {
diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java
index 2fe2ce3..e0438ce 100644
--- a/nfc/java/android/nfc/cardemulation/CardEmulation.java
+++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java
@@ -219,24 +219,9 @@
      * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
      */
     public boolean isDefaultServiceForCategory(ComponentName service, String category) {
-        try {
-            return sService.isDefaultServiceForCategory(mContext.getUser().getIdentifier(),
-                    service, category);
-        } catch (RemoteException e) {
-            // Try one more time
-            recoverService();
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                return false;
-            }
-            try {
-                return sService.isDefaultServiceForCategory(mContext.getUser().getIdentifier(),
-                        service, category);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                return false;
-            }
-        }
+        return callServiceReturn(() ->
+            sService.isDefaultServiceForCategory(
+                mContext.getUser().getIdentifier(), service, category), false);
     }
 
     /**
@@ -251,24 +236,9 @@
      * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
      */
     public boolean isDefaultServiceForAid(ComponentName service, String aid) {
-        try {
-            return sService.isDefaultServiceForAid(mContext.getUser().getIdentifier(),
-                    service, aid);
-        } catch (RemoteException e) {
-            // Try one more time
-            recoverService();
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                return false;
-            }
-            try {
-                return sService.isDefaultServiceForAid(mContext.getUser().getIdentifier(),
-                        service, aid);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to reach CardEmulationService.");
-                return false;
-            }
-        }
+        return callServiceReturn(() ->
+            sService.isDefaultServiceForAid(
+                mContext.getUser().getIdentifier(), service, aid), false);
     }
 
     /**
@@ -331,22 +301,8 @@
      */
     public int getSelectionModeForCategory(String category) {
         if (CATEGORY_PAYMENT.equals(category)) {
-            boolean paymentRegistered = false;
-            try {
-                paymentRegistered = sService.isDefaultPaymentRegistered();
-            } catch (RemoteException e) {
-                recoverService();
-                if (sService == null) {
-                    Log.e(TAG, "Failed to recover CardEmulationService.");
-                    return SELECTION_MODE_ALWAYS_ASK;
-                }
-                try {
-                    paymentRegistered = sService.isDefaultPaymentRegistered();
-                } catch (RemoteException ee) {
-                    Log.e(TAG, "Failed to reach CardEmulationService.");
-                    return SELECTION_MODE_ALWAYS_ASK;
-                }
-            }
+            boolean paymentRegistered = callServiceReturn(() ->
+                    sService.isDefaultPaymentRegistered(), false);
             if (paymentRegistered) {
                 return SELECTION_MODE_PREFER_DEFAULT;
             } else {
@@ -369,13 +325,9 @@
     @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
     public boolean setShouldDefaultToObserveModeForService(@NonNull ComponentName service,
             boolean enable) {
-        try {
-            return sService.setShouldDefaultToObserveModeForService(
-                    mContext.getUser().getIdentifier(), service, enable);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to reach CardEmulationService.");
-        }
-        return  false;
+        return callServiceReturn(() ->
+            sService.setShouldDefaultToObserveModeForService(
+                mContext.getUser().getIdentifier(), service, enable), false);
     }
 
     /**
@@ -396,27 +348,11 @@
     @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
     public boolean registerPollingLoopFilterForService(@NonNull ComponentName service,
             @NonNull String pollingLoopFilter, boolean autoTransact) {
-        pollingLoopFilter = validatePollingLoopFilter(pollingLoopFilter);
-
-        try {
-            return sService.registerPollingLoopFilterForService(mContext.getUser().getIdentifier(),
-                    service, pollingLoopFilter, autoTransact);
-        } catch (RemoteException e) {
-            // Try one more time
-            recoverService();
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                return false;
-            }
-            try {
-                return sService.registerPollingLoopFilterForService(
-                        mContext.getUser().getIdentifier(), service,
-                        pollingLoopFilter, autoTransact);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to reach CardEmulationService.");
-                return false;
-            }
-        }
+        final String pollingLoopFilterV = validatePollingLoopFilter(pollingLoopFilter);
+        return callServiceReturn(() ->
+            sService.registerPollingLoopFilterForService(
+                mContext.getUser().getIdentifier(), service, pollingLoopFilterV, autoTransact),
+            false);
     }
 
     /**
@@ -431,27 +367,10 @@
     @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
     public boolean removePollingLoopFilterForService(@NonNull ComponentName service,
             @NonNull String pollingLoopFilter) {
-        pollingLoopFilter = validatePollingLoopFilter(pollingLoopFilter);
-
-        try {
-            return sService.removePollingLoopFilterForService(mContext.getUser().getIdentifier(),
-                    service, pollingLoopFilter);
-        } catch (RemoteException e) {
-            // Try one more time
-            recoverService();
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                return false;
-            }
-            try {
-                return sService.removePollingLoopFilterForService(
-                        mContext.getUser().getIdentifier(), service,
-                        pollingLoopFilter);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to reach CardEmulationService.");
-                return false;
-            }
-        }
+        final String pollingLoopFilterV = validatePollingLoopFilter(pollingLoopFilter);
+        return callServiceReturn(() ->
+            sService.removePollingLoopFilterForService(
+                mContext.getUser().getIdentifier(), service, pollingLoopFilterV), false);
     }
 
 
@@ -477,28 +396,13 @@
     @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
     public boolean registerPollingLoopPatternFilterForService(@NonNull ComponentName service,
             @NonNull String pollingLoopPatternFilter, boolean autoTransact) {
-        pollingLoopPatternFilter = validatePollingLoopPatternFilter(pollingLoopPatternFilter);
-
-        try {
-            return sService.registerPollingLoopPatternFilterForService(
-                    mContext.getUser().getIdentifier(),
-                    service, pollingLoopPatternFilter, autoTransact);
-        } catch (RemoteException e) {
-            // Try one more time
-            recoverService();
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                return false;
-            }
-            try {
-                return sService.registerPollingLoopPatternFilterForService(
-                        mContext.getUser().getIdentifier(), service,
-                        pollingLoopPatternFilter, autoTransact);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to reach CardEmulationService.");
-                return false;
-            }
-        }
+        final String pollingLoopPatternFilterV =
+            validatePollingLoopPatternFilter(pollingLoopPatternFilter);
+        return callServiceReturn(() ->
+            sService.registerPollingLoopPatternFilterForService(
+                mContext.getUser().getIdentifier(), service, pollingLoopPatternFilterV,
+                autoTransact),
+            false);
     }
 
     /**
@@ -518,27 +422,11 @@
     @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
     public boolean removePollingLoopPatternFilterForService(@NonNull ComponentName service,
             @NonNull String pollingLoopPatternFilter) {
-        pollingLoopPatternFilter = validatePollingLoopPatternFilter(pollingLoopPatternFilter);
-
-        try {
-            return sService.removePollingLoopPatternFilterForService(
-                    mContext.getUser().getIdentifier(), service, pollingLoopPatternFilter);
-        } catch (RemoteException e) {
-            // Try one more time
-            recoverService();
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                return false;
-            }
-            try {
-                return sService.removePollingLoopPatternFilterForService(
-                        mContext.getUser().getIdentifier(), service,
-                        pollingLoopPatternFilter);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to reach CardEmulationService.");
-                return false;
-            }
-        }
+        final String pollingLoopPatternFilterV =
+            validatePollingLoopPatternFilter(pollingLoopPatternFilter);
+        return callServiceReturn(() ->
+            sService.removePollingLoopPatternFilterForService(
+                mContext.getUser().getIdentifier(), service, pollingLoopPatternFilterV), false);
     }
 
     /**
@@ -563,25 +451,10 @@
      */
     public boolean registerAidsForService(ComponentName service, String category,
             List<String> aids) {
-        AidGroup aidGroup = new AidGroup(aids, category);
-        try {
-            return sService.registerAidGroupForService(mContext.getUser().getIdentifier(),
-                    service, aidGroup);
-        } catch (RemoteException e) {
-            // Try one more time
-            recoverService();
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                return false;
-            }
-            try {
-                return sService.registerAidGroupForService(mContext.getUser().getIdentifier(),
-                        service, aidGroup);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to reach CardEmulationService.");
-                return false;
-            }
-        }
+        final AidGroup aidGroup = new AidGroup(aids, category);
+        return callServiceReturn(() ->
+            sService.registerAidGroupForService(
+                mContext.getUser().getIdentifier(), service, aidGroup), false);
     }
 
     /**
@@ -603,27 +476,9 @@
     @RequiresPermission(android.Manifest.permission.NFC)
     @NonNull
     public boolean unsetOffHostForService(@NonNull ComponentName service) {
-        NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext);
-        if (adapter == null) {
-            return false;
-        }
-
-        try {
-            return sService.unsetOffHostForService(mContext.getUser().getIdentifier(), service);
-        } catch (RemoteException e) {
-            // Try one more time
-            recoverService();
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                return false;
-            }
-            try {
-                return sService.unsetOffHostForService(mContext.getUser().getIdentifier(), service);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to reach CardEmulationService.");
-                return false;
-            }
-        }
+        return callServiceReturn(() ->
+            sService.unsetOffHostForService(
+                mContext.getUser().getIdentifier(), service), false);
     }
 
     /**
@@ -662,8 +517,6 @@
     @NonNull
     public boolean setOffHostForService(@NonNull ComponentName service,
             @NonNull String offHostSecureElement) {
-        boolean validSecureElement = false;
-
         NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext);
         if (adapter == null || offHostSecureElement == null) {
             return false;
@@ -684,25 +537,10 @@
         } else if (offHostSecureElement.equals("SIM")) {
             offHostSecureElement = "SIM1";
         }
-
-        try {
-            return sService.setOffHostForService(mContext.getUser().getIdentifier(), service,
-                offHostSecureElement);
-        } catch (RemoteException e) {
-            // Try one more time
-            recoverService();
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                return false;
-            }
-            try {
-                return sService.setOffHostForService(mContext.getUser().getIdentifier(), service,
-                        offHostSecureElement);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to reach CardEmulationService.");
-                return false;
-            }
-        }
+        final String offHostSecureElementV = new String(offHostSecureElement);
+        return callServiceReturn(() ->
+            sService.setOffHostForService(
+                mContext.getUser().getIdentifier(), service, offHostSecureElementV), false);
     }
 
     /**
@@ -720,25 +558,10 @@
      * @return The list of AIDs registered for this category, or null if it couldn't be found.
      */
     public List<String> getAidsForService(ComponentName service, String category) {
-        try {
-            AidGroup group =  sService.getAidGroupForService(mContext.getUser().getIdentifier(),
-                    service, category);
-            return (group != null ? group.getAids() : null);
-        } catch (RemoteException e) {
-            recoverService();
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                return null;
-            }
-            try {
-                AidGroup group = sService.getAidGroupForService(mContext.getUser().getIdentifier(),
-                        service, category);
-                return (group != null ? group.getAids() : null);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                return null;
-            }
-        }
+        AidGroup group = callServiceReturn(() ->
+               sService.getAidGroupForService(
+                   mContext.getUser().getIdentifier(), service, category), null);
+        return (group != null ? group.getAids() : null);
     }
 
     /**
@@ -757,24 +580,9 @@
      * @return whether the group was successfully removed.
      */
     public boolean removeAidsForService(ComponentName service, String category) {
-        try {
-            return sService.removeAidGroupForService(mContext.getUser().getIdentifier(), service,
-                    category);
-        } catch (RemoteException e) {
-            // Try one more time
-            recoverService();
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                return false;
-            }
-            try {
-                return sService.removeAidGroupForService(mContext.getUser().getIdentifier(),
-                        service, category);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to reach CardEmulationService.");
-                return false;
-            }
-        }
+        return callServiceReturn(() ->
+            sService.removeAidGroupForService(
+                mContext.getUser().getIdentifier(), service, category), false);
     }
 
     /**
@@ -811,22 +619,7 @@
         if (activity == null || service == null) {
             throw new NullPointerException("activity or service or category is null");
         }
-        try {
-            return sService.setPreferredService(service);
-        } catch (RemoteException e) {
-            // Try one more time
-            recoverService();
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                return false;
-            }
-            try {
-                return sService.setPreferredService(service);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to reach CardEmulationService.");
-                return false;
-            }
-        }
+        return callServiceReturn(() -> sService.setPreferredService(service), false);
     }
 
     /**
@@ -843,22 +636,7 @@
         if (activity == null) {
             throw new NullPointerException("activity is null");
         }
-        try {
-            return sService.unsetPreferredService();
-        } catch (RemoteException e) {
-            // Try one more time
-            recoverService();
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                return false;
-            }
-            try {
-                return sService.unsetPreferredService();
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to reach CardEmulationService.");
-                return false;
-            }
-        }
+        return callServiceReturn(() -> sService.unsetPreferredService(), false);
     }
 
     /**
@@ -872,21 +650,7 @@
      * @return whether AID prefix registering is supported on this device.
      */
     public boolean supportsAidPrefixRegistration() {
-        try {
-            return sService.supportsAidPrefixRegistration();
-        } catch (RemoteException e) {
-            recoverService();
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                return false;
-            }
-            try {
-                return sService.supportsAidPrefixRegistration();
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to reach CardEmulationService.");
-                return false;
-            }
-        }
+        return callServiceReturn(() -> sService.supportsAidPrefixRegistration(), false);
     }
 
     /**
@@ -897,25 +661,9 @@
     @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO)
     @Nullable
     public List<String> getAidsForPreferredPaymentService() {
-        try {
-            ApduServiceInfo serviceInfo = sService.getPreferredPaymentService(
-                    mContext.getUser().getIdentifier());
-            return (serviceInfo != null ? serviceInfo.getAids() : null);
-        } catch (RemoteException e) {
-            recoverService();
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                throw e.rethrowFromSystemServer();
-            }
-            try {
-                ApduServiceInfo serviceInfo =
-                        sService.getPreferredPaymentService(mContext.getUser().getIdentifier());
-                return (serviceInfo != null ? serviceInfo.getAids() : null);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                throw e.rethrowFromSystemServer();
-            }
-        }
+        ApduServiceInfo serviceInfo = callServiceReturn(() ->
+                sService.getPreferredPaymentService(mContext.getUser().getIdentifier()), null);
+        return (serviceInfo != null ? serviceInfo.getAids() : null);
     }
 
     /**
@@ -944,40 +692,16 @@
     @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO)
     @Nullable
     public String getRouteDestinationForPreferredPaymentService() {
-        try {
-            ApduServiceInfo serviceInfo = sService.getPreferredPaymentService(
-                    mContext.getUser().getIdentifier());
-            if (serviceInfo != null) {
-                if (!serviceInfo.isOnHost()) {
-                    return serviceInfo.getOffHostSecureElement() == null ?
-                            "OffHost" : serviceInfo.getOffHostSecureElement();
-                }
-                return "Host";
+        ApduServiceInfo serviceInfo = callServiceReturn(() ->
+                sService.getPreferredPaymentService(mContext.getUser().getIdentifier()), null);
+        if (serviceInfo != null) {
+            if (!serviceInfo.isOnHost()) {
+                return serviceInfo.getOffHostSecureElement() == null ?
+                        "OffHost" : serviceInfo.getOffHostSecureElement();
             }
-            return null;
-        } catch (RemoteException e) {
-            recoverService();
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                throw e.rethrowFromSystemServer();
-            }
-            try {
-                ApduServiceInfo serviceInfo =
-                        sService.getPreferredPaymentService(mContext.getUser().getIdentifier());
-                if (serviceInfo != null) {
-                    if (!serviceInfo.isOnHost()) {
-                        return serviceInfo.getOffHostSecureElement() == null ?
-                                "Offhost" : serviceInfo.getOffHostSecureElement();
-                    }
-                    return "Host";
-                }
-                return null;
-
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                throw e.rethrowFromSystemServer();
-            }
+            return "Host";
         }
+        return null;
     }
 
     /**
@@ -995,115 +719,44 @@
     @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO)
     @Nullable
     public CharSequence getDescriptionForPreferredPaymentService() {
-        try {
-            ApduServiceInfo serviceInfo = sService.getPreferredPaymentService(
-                    mContext.getUser().getIdentifier());
-            return (serviceInfo != null ? serviceInfo.getDescription() : null);
-        } catch (RemoteException e) {
-            recoverService();
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                throw e.rethrowFromSystemServer();
-            }
-            try {
-                ApduServiceInfo serviceInfo =
-                        sService.getPreferredPaymentService(mContext.getUser().getIdentifier());
-                return (serviceInfo != null ? serviceInfo.getDescription() : null);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                throw e.rethrowFromSystemServer();
-            }
-        }
+        ApduServiceInfo serviceInfo = callServiceReturn(() ->
+                sService.getPreferredPaymentService(mContext.getUser().getIdentifier()), null);
+        return (serviceInfo != null ? serviceInfo.getDescription() : null);
     }
 
     /**
      * @hide
      */
     public boolean setDefaultServiceForCategory(ComponentName service, String category) {
-        try {
-            return sService.setDefaultServiceForCategory(mContext.getUser().getIdentifier(),
-                    service, category);
-        } catch (RemoteException e) {
-            // Try one more time
-            recoverService();
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                return false;
-            }
-            try {
-                return sService.setDefaultServiceForCategory(mContext.getUser().getIdentifier(),
-                        service, category);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to reach CardEmulationService.");
-                return false;
-            }
-        }
+        return callServiceReturn(() ->
+                sService.setDefaultServiceForCategory(
+                    mContext.getUser().getIdentifier(), service, category), false);
     }
 
     /**
      * @hide
      */
     public boolean setDefaultForNextTap(ComponentName service) {
-        try {
-            return sService.setDefaultForNextTap(mContext.getUser().getIdentifier(), service);
-        } catch (RemoteException e) {
-            // Try one more time
-            recoverService();
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                return false;
-            }
-            try {
-                return sService.setDefaultForNextTap(mContext.getUser().getIdentifier(), service);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to reach CardEmulationService.");
-                return false;
-            }
-        }
+        return callServiceReturn(() ->
+                sService.setDefaultForNextTap(
+                    mContext.getUser().getIdentifier(), service), false);
     }
 
     /**
      * @hide
      */
     public boolean setDefaultForNextTap(int userId, ComponentName service) {
-        try {
-            return sService.setDefaultForNextTap(userId, service);
-        } catch (RemoteException e) {
-            // Try one more time
-            recoverService();
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                return false;
-            }
-            try {
-                return sService.setDefaultForNextTap(userId, service);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to reach CardEmulationService.");
-                return false;
-            }
-        }
+        return callServiceReturn(() ->
+                sService.setDefaultForNextTap(userId, service), false);
     }
 
     /**
      * @hide
      */
     public List<ApduServiceInfo> getServices(String category) {
-        try {
-            return sService.getServices(mContext.getUser().getIdentifier(), category);
-        } catch (RemoteException e) {
-            // Try one more time
-            recoverService();
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                return null;
-            }
-            try {
-                return sService.getServices(mContext.getUser().getIdentifier(), category);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to reach CardEmulationService.");
-                return null;
-            }
-        }
+        return callServiceReturn(() ->
+                sService.getServices(
+                    mContext.getUser().getIdentifier(), category), null);
     }
 
     /**
@@ -1117,22 +770,8 @@
     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
     @NonNull
     public List<ApduServiceInfo> getServices(@NonNull String category, @UserIdInt int userId) {
-        try {
-            return sService.getServices(userId, category);
-        } catch (RemoteException e) {
-            // Try one more time
-            recoverService();
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                return null;
-            }
-            try {
-                return sService.getServices(userId, category);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to reach CardEmulationService.");
-                return null;
-            }
-        }
+        return callServiceReturn(() ->
+                sService.getServices(userId, category), null);
     }
 
     /**
@@ -1222,22 +861,8 @@
         if (service == null) {
             throw new NullPointerException("activity or service or category is null");
         }
-        try {
-            return sService.setServiceEnabledForCategoryOther(userId, service, status);
-        } catch (RemoteException e) {
-            // Try one more time
-            recoverService();
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                return false;
-            }
-            try {
-                return sService.setServiceEnabledForCategoryOther(userId, service, status);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to reach CardEmulationService.");
-                return false;
-            }
-        }
+        return callServiceReturn(() ->
+                sService.setServiceEnabledForCategoryOther(userId, service, status), false);
     }
 
      /**
@@ -1269,22 +894,9 @@
         if (!activity.isResumed()) {
             throw new IllegalArgumentException("Activity must be resumed.");
         }
-        try {
-            return sService.overrideRoutingTable(UserHandle.myUserId(), protocol, technology);
-        } catch (RemoteException e) {
-            // Try one more time
-            recoverService();
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                return false;
-            }
-            try {
-                return sService.overrideRoutingTable(UserHandle.myUserId(), protocol, technology);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to reach CardEmulationService.");
-                return false;
-            }
-        }
+        return callServiceReturn(() ->
+                sService.overrideRoutingTable(
+                    mContext.getUser().getIdentifier(), protocol, technology), false);
     }
 
     /**
@@ -1303,27 +915,9 @@
         if (!activity.isResumed()) {
             throw new IllegalArgumentException("Activity must be resumed.");
         }
-        try {
-            return sService.recoverRoutingTable(UserHandle.myUserId());
-        } catch (RemoteException e) {
-            // Try one more time
-            recoverService();
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                return false;
-            }
-            try {
-                return sService.recoverRoutingTable(UserHandle.myUserId());
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to reach CardEmulationService.");
-                return false;
-            }
-        }
-    }
-
-    void recoverService() {
-        NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext);
-        sService = adapter.getCardEmulationService();
+        return callServiceReturn(() ->
+                sService.recoverRoutingTable(
+                    mContext.getUser().getIdentifier()), false);
     }
 
     /**
@@ -1351,4 +945,53 @@
 
         return ComponentName.unflattenFromString(defaultPaymentComponent);
     }
+
+    /** @hide */
+    interface ServiceCall {
+        void call() throws RemoteException;
+    }
+    /** @hide */
+    public static void callService(ServiceCall call) {
+        try {
+            if (sService == null) {
+                NfcAdapter.attemptDeadServiceRecovery(
+                    new RemoteException("NFC CardEmulation Service is null"));
+                sService = NfcAdapter.getCardEmulationService();
+            }
+            call.call();
+        } catch (RemoteException e) {
+            NfcAdapter.attemptDeadServiceRecovery(e);
+            sService = NfcAdapter.getCardEmulationService();
+            try {
+                call.call();
+            } catch (RemoteException ee) {
+                ee.rethrowAsRuntimeException();
+            }
+        }
+    }
+    /** @hide */
+    interface ServiceCallReturn<T> {
+        T call() throws RemoteException;
+    }
+    /** @hide */
+    public static <T> T callServiceReturn(ServiceCallReturn<T> call, T defaultReturn) {
+        try {
+            if (sService == null) {
+                NfcAdapter.attemptDeadServiceRecovery(
+                    new RemoteException("NFC CardEmulation Service is null"));
+                sService = NfcAdapter.getCardEmulationService();
+            }
+            return call.call();
+        } catch (RemoteException e) {
+            NfcAdapter.attemptDeadServiceRecovery(e);
+            sService = NfcAdapter.getCardEmulationService();
+            // Try one more time
+            try {
+                return call.call();
+            } catch (RemoteException ee) {
+                ee.rethrowAsRuntimeException();
+            }
+        }
+        return defaultReturn;
+    }
 }
diff --git a/nfc/java/android/nfc/flags.aconfig b/nfc/java/android/nfc/flags.aconfig
index 95945d7..f16fa80 100644
--- a/nfc/java/android/nfc/flags.aconfig
+++ b/nfc/java/android/nfc/flags.aconfig
@@ -118,3 +118,10 @@
     bug: "321310044"
 }
 
+flag {
+    name: "nfc_action_manage_services_settings"
+    is_exported: true
+    namespace: "nfc"
+    description: "Add Settings.ACTION_MANAGE_OTHER_NFC_SERVICES_SETTINGS"
+    bug: "358129872"
+}
diff --git a/packages/PackageInstaller/TEST_MAPPING b/packages/PackageInstaller/TEST_MAPPING
index b3fb1e7..ff83610 100644
--- a/packages/PackageInstaller/TEST_MAPPING
+++ b/packages/PackageInstaller/TEST_MAPPING
@@ -28,6 +28,17 @@
     },
     {
       "name": "CtsIntentSignatureTestCases"
+    },
+    {
+      "name": "CtsPackageInstallerCUJTestCases",
+      "options":[
+          {
+              "exclude-annotation":"androidx.test.filters.FlakyTest"
+          },
+          {
+              "exclude-annotation":"org.junit.Ignore"
+          }
+      ]
     }
   ]
 }
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java
index 9ad3e3c..170cb45 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java
@@ -91,8 +91,7 @@
         // be stale, if e.g. the app was uninstalled while the activity was destroyed.
         super.onCreate(null);
 
-        // TODO(b/318521110) Enable PIA v2 for archive dialog.
-        if (usePiaV2() && !isTv() && !isArchiveDialog(getIntent())) {
+        if (usePiaV2() && !isTv()) {
             Log.i(TAG, "Using Pia V2");
 
             boolean returnResult = getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false);
@@ -225,11 +224,6 @@
         showConfirmationDialog();
     }
 
-    private boolean isArchiveDialog(Intent intent) {
-        return (intent.getIntExtra(PackageInstaller.EXTRA_DELETE_FLAGS, 0)
-                & PackageManager.DELETE_ARCHIVE) != 0;
-    }
-
     /**
      * Parses specific {@link android.content.pm.PackageManager.DeleteFlags} from {@link Intent}
      * to archive an app if requested.
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
index 186b69b..3b0faf0 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
@@ -736,7 +736,8 @@
             val appInfo = packageManager.getApplicationInfo(
                 pkgName, PackageManager.MATCH_UNINSTALLED_PACKAGES
             )
-            if (appInfo.flags and ApplicationInfo.FLAG_INSTALLED == 0) {
+            // If the package is archived, treat it as an update case.
+            if (!appInfo.isArchived && appInfo.flags and ApplicationInfo.FLAG_INSTALLED == 0) {
                 return false
             }
         } catch (e: PackageManager.NameNotFoundException) {
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt
index 0091a3e8..96525f6 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt
@@ -32,6 +32,8 @@
 import android.content.pm.ApplicationInfo
 import android.content.pm.PackageInstaller
 import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ApplicationInfoFlags
+import android.content.pm.PackageManager.PackageInfoFlags
 import android.content.pm.VersionedPackage
 import android.graphics.drawable.Icon
 import android.os.Build
@@ -51,6 +53,9 @@
 import com.android.packageinstaller.v2.model.PackageUtil.getPackageNameForUid
 import com.android.packageinstaller.v2.model.PackageUtil.isPermissionGranted
 import com.android.packageinstaller.v2.model.PackageUtil.isProfileOfOrSame
+import com.android.packageinstaller.v2.model.UninstallAborted.Companion.ABORT_REASON_UNINSTALL_DONE
+import android.content.pm.Flags as PmFlags
+import android.multiuser.Flags as MultiuserFlags
 
 class UninstallRepository(private val context: Context) {
 
@@ -71,6 +76,7 @@
     private var uninstallFromAllUsers = false
     private var isClonedApp = false
     private var uninstallId = 0
+    private var deleteFlags = 0
 
     fun performPreUninstallChecks(intent: Intent, callerInfo: CallerInfo): UninstallStage {
         this.intent = intent
@@ -155,7 +161,9 @@
         try {
             targetAppInfo = packageManager.getApplicationInfo(
                 targetPackageName!!,
-                PackageManager.ApplicationInfoFlags.of(PackageManager.MATCH_ANY_USER.toLong())
+                ApplicationInfoFlags.of(
+                    PackageManager.MATCH_ANY_USER.toLong() or PackageManager.MATCH_ARCHIVED_PACKAGES
+                )
             )
         } catch (e: PackageManager.NameNotFoundException) {
             Log.e(LOG_TAG, "Unable to get packageName")
@@ -180,9 +188,27 @@
             }
         }
 
+        parseDeleteFlags(intent)
+
         return UninstallReady()
     }
 
+    /**
+     * Parses specific {@link android.content.pm.PackageManager.DeleteFlags} from {@link Intent}
+     * to archive an app if requested.
+     *
+     * Do not parse other flags because developers might pass here any flags which might cause
+     * unintended behaviour.
+     * For more context {@link com.android.server.pm.PackageArchiver#requestArchive}.
+     */
+    private fun parseDeleteFlags(intent: Intent) {
+        val flags = intent.getIntExtra(PackageInstaller.EXTRA_DELETE_FLAGS, 0)
+        val archive = flags and PackageManager.DELETE_ARCHIVE
+        val keepData = flags and PackageManager.DELETE_KEEP_DATA
+
+        deleteFlags = archive or keepData
+    }
+
     fun generateUninstallDetails(): UninstallStage {
         val messageBuilder = StringBuilder()
 
@@ -201,6 +227,8 @@
         }
 
         val isUpdate = (targetAppInfo!!.flags and ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0
+        val isArchive =
+            PmFlags.archiving() && ((deleteFlags and PackageManager.DELETE_ARCHIVE) != 0)
         val myUserHandle = Process.myUserHandle()
         val isSingleUser = isSingleUser()
 
@@ -215,34 +243,54 @@
                 )
             )
         } else if (uninstallFromAllUsers && !isSingleUser) {
-            messageBuilder.append(context.getString(R.string.uninstall_application_text_all_users))
+            val messageString = if (isArchive) {
+                context.getString(R.string.archive_application_text_all_users)
+            } else {
+                context.getString(R.string.uninstall_application_text_all_users)
+            }
+            messageBuilder.append(messageString)
         } else if (uninstalledUser != myUserHandle) {
             // Uninstalling user is issuing uninstall for another user
             val customUserManager = context.createContextAsUser(uninstalledUser!!, 0)
                 .getSystemService(UserManager::class.java)
             val userName = customUserManager!!.userName
-            var messageString = context.getString(
-                    R.string.uninstall_application_text_user,
-                userName
-            )
+
+            var messageString = if (isArchive) {
+                context.getString(R.string.archive_application_text_user, userName)
+            } else {
+                context.getString(R.string.uninstall_application_text_user, userName)
+            }
+
             if (userManager!!.isSameProfileGroup(myUserHandle, uninstalledUser!!)) {
                 if (customUserManager.isManagedProfile) {
-                    messageString = context.getString(
+                    messageString = if (isArchive) {
+                        context.getString(
+                            R.string.archive_application_text_current_user_work_profile, userName
+                        )
+                    } else {
+                        context.getString(
                             R.string.uninstall_application_text_current_user_work_profile, userName
-                    )
+                        )
+                    }
                 } else if (customUserManager.isCloneProfile){
                     isClonedApp = true
                     messageString = context.getString(
                             R.string.uninstall_application_text_current_user_clone_profile
                     )
                 } else if (Flags.allowPrivateProfile()
-                        && android.multiuser.Flags.enablePrivateSpaceFeatures()
+                        && MultiuserFlags.enablePrivateSpaceFeatures()
                         && customUserManager.isPrivateProfile
                 ) {
                     // TODO(b/324244123): Get these Strings from a User Property API.
-                    messageString = context.getString(
+                    messageString = if (isArchive) {
+                        context.getString(
+                            R.string.archive_application_text_current_user_private_profile, userName
+                        )
+                    } else {
+                        context.getString(
                             R.string.uninstall_application_text_current_user_private_profile
-                    )
+                        )
+                    }
                 }
             }
             messageBuilder.append(messageString)
@@ -262,6 +310,8 @@
                     targetAppLabel
                 )
             )
+        } else if (isArchive) {
+            messageBuilder.append(context.getString(R.string.archive_application_text))
         } else {
             messageBuilder.append(context.getString(R.string.uninstall_application_text))
         }
@@ -270,15 +320,21 @@
 
         val title = if (isClonedApp) {
             context.getString(R.string.cloned_app_label, targetAppLabel)
+        } else if (isArchive) {
+            context.getString(R.string.archiving_app_label, targetAppLabel)
         } else {
             targetAppLabel.toString()
         }
 
         var suggestToKeepAppData = false
         try {
-            val pkgInfo = packageManager.getPackageInfo(targetPackageName!!, 0)
+            val pkgInfo = packageManager.getPackageInfo(
+                targetPackageName!!, PackageInfoFlags.of(PackageManager.MATCH_ARCHIVED_PACKAGES)
+            )
             suggestToKeepAppData =
-                pkgInfo.applicationInfo != null && pkgInfo.applicationInfo!!.hasFragileUserData()
+                pkgInfo.applicationInfo != null
+                    && pkgInfo.applicationInfo!!.hasFragileUserData()
+                    && !isArchive
         } catch (e: PackageManager.NameNotFoundException) {
             Log.e(LOG_TAG, "Cannot check hasFragileUserData for $targetPackageName", e)
         }
@@ -291,7 +347,7 @@
             )
         }
 
-        return UninstallUserActionRequired(title, message, appDataSize)
+        return UninstallUserActionRequired(title, message, appDataSize, isArchive)
     }
 
     /**
@@ -444,10 +500,11 @@
             callback!!.onUninstallComplete(targetPackageName!!, legacyStatus, message)
 
             // Since the caller already received the results, just finish the app at this point
-            uninstallResult.value = null
+            uninstallResult.value = UninstallAborted(ABORT_REASON_UNINSTALL_DONE)
             return
         }
         val returnResult = intent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)
+
         if (returnResult || callingActivity != null) {
             val intent = Intent()
             intent.putExtra(Intent.EXTRA_INSTALL_RESULT, legacyStatus)
@@ -717,6 +774,7 @@
     ): Boolean {
         var flags = if (uninstallFromAllUsers) PackageManager.DELETE_ALL_USERS else 0
         flags = flags or if (keepData) PackageManager.DELETE_KEEP_DATA else 0
+        flags = flags or deleteFlags
 
         return try {
             context.createContextAsUser(targetUser, 0)
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallStages.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallStages.kt
index f086209..316e8b7 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallStages.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallStages.kt
@@ -38,7 +38,8 @@
 data class UninstallUserActionRequired(
     val title: String? = null,
     val message: String? = null,
-    val appDataSize: Long = 0
+    val appDataSize: Long = 0,
+    val isArchive: Boolean = false
 ) : UninstallStage(STAGE_USER_ACTION_REQUIRED)
 
 data class UninstallUninstalling(val appLabel: CharSequence, val isCloneUser: Boolean) :
@@ -96,6 +97,11 @@
                 dialogTextResource = R.string.user_is_not_allowed_dlg_text
             }
 
+            ABORT_REASON_UNINSTALL_DONE -> {
+                dialogTitleResource = 0
+                dialogTextResource = 0
+            }
+
             else -> {
                 dialogTitleResource = 0
                 dialogTextResource = R.string.generic_error_dlg_text
@@ -107,6 +113,7 @@
         const val ABORT_REASON_GENERIC_ERROR = 0
         const val ABORT_REASON_APP_UNAVAILABLE = 1
         const val ABORT_REASON_USER_NOT_ALLOWED = 2
+        const val ABORT_REASON_UNINSTALL_DONE = 3
     }
 }
 
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt
index e2ab316..c61a2ac 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt
@@ -16,7 +16,9 @@
 
 package com.android.packageinstaller.v2.ui
 
-import android.app.Activity
+import android.app.Activity.RESULT_CANCELED
+import android.app.Activity.RESULT_FIRST_USER
+import android.app.Activity.RESULT_OK
 import android.app.AppOpsManager
 import android.content.ActivityNotFoundException
 import android.content.Intent
@@ -135,7 +137,7 @@
                     }
 
                     InstallAborted.ABORT_REASON_POLICY -> showPolicyRestrictionDialog(aborted)
-                    else -> setResult(Activity.RESULT_CANCELED, null, true)
+                    else -> setResult(RESULT_CANCELED, null, true)
                 }
             }
 
@@ -169,7 +171,7 @@
                 val success = installStage as InstallSuccess
                 if (success.shouldReturnResult) {
                     val successIntent = success.resultIntent
-                    setResult(Activity.RESULT_OK, successIntent, true)
+                    setResult(RESULT_OK, successIntent, true)
                 } else {
                     val successDialog = InstallSuccessFragment(success)
                     showDialogInner(successDialog)
@@ -180,7 +182,7 @@
                 val failed = installStage as InstallFailed
                 if (failed.shouldReturnResult) {
                     val failureIntent = failed.resultIntent
-                    setResult(Activity.RESULT_FIRST_USER, failureIntent, true)
+                    setResult(RESULT_FIRST_USER, failureIntent, true)
                 } else {
                     val failureDialog = InstallFailedFragment(failed)
                     showDialogInner(failureDialog)
@@ -219,7 +221,7 @@
             shouldFinish = blockedByPolicyDialog == null
             showDialogInner(blockedByPolicyDialog)
         }
-        setResult(Activity.RESULT_CANCELED, null, shouldFinish)
+        setResult(RESULT_CANCELED, null, shouldFinish)
     }
 
     /**
@@ -257,6 +259,10 @@
 
     fun setResult(resultCode: Int, data: Intent?, shouldFinish: Boolean) {
         super.setResult(resultCode, data)
+        if (resultCode != RESULT_OK) {
+            // Let callers know that the install was cancelled
+            installViewModel!!.cleanupInstall()
+        }
         if (shouldFinish) {
             finish()
         }
@@ -282,7 +288,7 @@
         if (stageCode == InstallStage.STAGE_USER_ACTION_REQUIRED) {
             installViewModel!!.cleanupInstall()
         }
-        setResult(Activity.RESULT_CANCELED, null, true)
+        setResult(RESULT_CANCELED, null, true)
     }
 
     override fun onNegativeResponse(resultCode: Int, data: Intent?) {
@@ -318,7 +324,7 @@
         if (localLogv) {
             Log.d(LOG_TAG, "Opening $intent")
         }
-        setResult(Activity.RESULT_OK, intent, true)
+        setResult(RESULT_OK, intent, true)
         if (intent != null && intent.hasCategory(Intent.CATEGORY_LAUNCHER)) {
             startActivity(intent)
         }
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallConfirmationFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallConfirmationFragment.java
index 87af1ae..524b4e6 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallConfirmationFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallConfirmationFragment.java
@@ -60,7 +60,7 @@
         Log.i(LOG_TAG, "Creating " + LOG_TAG + "\n" + mDialogData);
         AlertDialog.Builder builder = new AlertDialog.Builder(requireContext())
             .setTitle(mDialogData.getTitle())
-            .setPositiveButton(R.string.ok,
+            .setPositiveButton(mDialogData.isArchive() ? R.string.archive : R.string.ok,
                 (dialogInt, which) -> mUninstallActionListener.onPositiveResponse(
                     mKeepData != null && mKeepData.isChecked()))
             .setNegativeButton(R.string.cancel,
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/non_collapsing_toolbar_base_layout.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/non_collapsing_toolbar_base_layout.xml
new file mode 100644
index 0000000..1e48443
--- /dev/null
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/non_collapsing_toolbar_base_layout.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+<androidx.coordinatorlayout.widget.CoordinatorLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/content_parent"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fitsSystemWindows="true">
+
+    <include layout="@layout/non_collapsing_toolbar_content_layout"/>
+</androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/non_collapsing_toolbar_content_layout.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/non_collapsing_toolbar_content_layout.xml
new file mode 100644
index 0000000..33519cb
--- /dev/null
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/non_collapsing_toolbar_content_layout.xml
@@ -0,0 +1,45 @@
+<?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.
+-->
+<merge
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <com.google.android.material.appbar.AppBarLayout
+        android:id="@+id/app_bar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fitsSystemWindows="true"
+        android:outlineAmbientShadowColor="@android:color/transparent"
+        android:outlineSpotShadowColor="@android:color/transparent"
+        android:background="@android:color/transparent"
+        android:theme="@style/Theme.CollapsingToolbar.Settings">
+
+        <Toolbar
+            android:id="@+id/action_bar"
+            android:layout_width="match_parent"
+            android:layout_height="?attr/actionBarSize"
+            android:theme="?android:attr/actionBarTheme"
+            android:transitionName="shared_element_view"
+            app:layout_collapseMode="pin"/>
+    </com.google.android.material.appbar.AppBarLayout>
+
+    <FrameLayout
+        android:id="@+id/content_frame"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
+</merge>
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarAppCompatActivity.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarAppCompatActivity.java
index 4659051..f46f110 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarAppCompatActivity.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarAppCompatActivity.java
@@ -171,7 +171,7 @@
 
     private CollapsingToolbarDelegate getToolbarDelegate() {
         if (mToolbardelegate == null) {
-            mToolbardelegate = new CollapsingToolbarDelegate(new DelegateCallback());
+            mToolbardelegate = new CollapsingToolbarDelegate(new DelegateCallback(), true);
         }
         return mToolbardelegate;
     }
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
index 3965303..16ed5a8 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
@@ -169,7 +169,7 @@
 
     private CollapsingToolbarDelegate getToolbarDelegate() {
         if (mToolbardelegate == null) {
-            mToolbardelegate = new CollapsingToolbarDelegate(new DelegateCallback());
+            mToolbardelegate = new CollapsingToolbarDelegate(new DelegateCallback(), true);
         }
         return mToolbardelegate;
     }
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseFragment.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseFragment.java
index b605074..da97c30 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseFragment.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseFragment.java
@@ -57,7 +57,8 @@
     @Override
     public void onAttach(Context context) {
         super.onAttach(context);
-        mToolbardelegate = new CollapsingToolbarDelegate(new DelegateCallback());
+        mToolbardelegate =
+                new CollapsingToolbarDelegate(new DelegateCallback(), useCollapsingToolbar());
     }
 
     @Nullable
@@ -98,4 +99,8 @@
     public FrameLayout getContentFrameLayout() {
         return mToolbardelegate.getContentFrameLayout();
     }
+
+    protected boolean useCollapsingToolbar() {
+        return true;
+    }
 }
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
index b633337..2ab2abd 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
@@ -21,6 +21,8 @@
 import android.app.ActionBar;
 import android.app.Activity;
 import android.content.res.Configuration;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
 import android.graphics.text.LineBreakConfig;
 import android.os.Build;
 import android.util.Log;
@@ -80,8 +82,12 @@
     @NonNull
     private final HostCallback mHostCallback;
 
-    public CollapsingToolbarDelegate(@NonNull HostCallback hostCallback) {
+    private boolean mUseCollapsingToolbar;
+
+    public CollapsingToolbarDelegate(@NonNull HostCallback hostCallback,
+            boolean useCollapsingToolbar) {
         mHostCallback = hostCallback;
+        mUseCollapsingToolbar = useCollapsingToolbar;
     }
 
     /** Method to call that creates the root view of the collapsing toolbar. */
@@ -94,13 +100,32 @@
     @SuppressWarnings("RestrictTo")
     View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
             Activity activity) {
-        final View view =
-                inflater.inflate(R.layout.collapsing_toolbar_base_layout, container, false);
+        int layoutId;
+        boolean useCollapsingToolbar =
+                mUseCollapsingToolbar || Build.VERSION.SDK_INT < Build.VERSION_CODES.S;
+        if (useCollapsingToolbar) {
+            layoutId = R.layout.collapsing_toolbar_base_layout;
+        } else {
+            layoutId = R.layout.non_collapsing_toolbar_base_layout;
+        }
+        final View view = inflater.inflate(layoutId, container, false);
         if (view instanceof CoordinatorLayout) {
             mCoordinatorLayout = (CoordinatorLayout) view;
         }
         mCollapsingToolbarLayout = view.findViewById(R.id.collapsing_toolbar);
         mAppBarLayout = view.findViewById(R.id.app_bar);
+
+        if (!useCollapsingToolbar) {
+            // In the non-collapsing toolbar layout, we need to set the background of the app bar to
+            // the same as the activity background so that it covers the items extending above the
+            // bounds of the list for edge-to-edge.
+            TypedArray ta = container.getContext().obtainStyledAttributes(new int[] {
+                    android.R.attr.windowBackground});
+            Drawable background = ta.getDrawable(0);
+            ta.recycle();
+            mAppBarLayout.setBackground(background);
+        }
+
         if (mCollapsingToolbarLayout != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
             mCollapsingToolbarLayout.setLineSpacingMultiplier(TOOLBAR_LINE_SPACING_MULTIPLIER);
             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt
index d7147b5..0d73cb3 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt
@@ -22,8 +22,9 @@
 import androidx.compose.ui.test.assertIsDisplayed
 import androidx.compose.ui.test.assertIsNotDisplayed
 import androidx.compose.ui.test.assertIsNotEnabled
-import androidx.compose.ui.test.assertIsOff
-import androidx.compose.ui.test.assertIsOn
+import androidx.compose.ui.test.hasText
+import androidx.compose.ui.test.isOff
+import androidx.compose.ui.test.isOn
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithText
 import androidx.compose.ui.test.onRoot
@@ -31,6 +32,7 @@
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.settingslib.spa.testutils.FakeNavControllerWrapper
+import com.android.settingslib.spa.testutils.waitUntilExists
 import com.android.settingslib.spaprivileged.R
 import com.android.settingslib.spaprivileged.model.app.IPackageManagers
 import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
@@ -99,8 +101,8 @@
 
         setEntryItem(listModel)
 
-        composeTestRule.onNodeWithText(context.getString(R.string.app_permission_summary_allowed))
-            .assertIsDisplayed()
+        composeTestRule.waitUntilExists(
+            hasText(context.getString(R.string.app_permission_summary_allowed)))
     }
 
     @Test
@@ -141,8 +143,8 @@
 
         setTogglePermissionAppInfoPage(listModel)
 
-        composeTestRule.onNodeWithText(context.getString(listModel.switchTitleResId))
-            .assertIsOn()
+        composeTestRule.waitUntilExists(
+            hasText(context.getString(listModel.switchTitleResId)) and isOn())
     }
 
     @Test
@@ -151,8 +153,8 @@
 
         setTogglePermissionAppInfoPage(listModel)
 
-        composeTestRule.onNodeWithText(context.getString(listModel.switchTitleResId))
-            .assertIsOff()
+        composeTestRule.waitUntilExists(
+            hasText(context.getString(listModel.switchTitleResId)) and isOff())
     }
 
     @Test
@@ -160,11 +162,10 @@
         val listModel = TestTogglePermissionAppListModel(isAllowed = false, isChangeable = true)
 
         setTogglePermissionAppInfoPage(listModel)
-        composeTestRule.onNodeWithText(context.getString(listModel.switchTitleResId))
-            .performClick()
+        composeTestRule.onNodeWithText(context.getString(listModel.switchTitleResId)).performClick()
 
-        composeTestRule.onNodeWithText(context.getString(listModel.switchTitleResId))
-            .assertIsOn()
+        composeTestRule.waitUntilExists(
+            hasText(context.getString(listModel.switchTitleResId)) and isOn())
     }
 
     @Test
@@ -172,11 +173,10 @@
         val listModel = TestTogglePermissionAppListModel(isAllowed = false, isChangeable = false)
 
         setTogglePermissionAppInfoPage(listModel)
-        composeTestRule.onNodeWithText(context.getString(listModel.switchTitleResId))
-            .performClick()
+        composeTestRule.onNodeWithText(context.getString(listModel.switchTitleResId)).performClick()
 
-        composeTestRule.onNodeWithText(context.getString(listModel.switchTitleResId))
-            .assertIsOff()
+        composeTestRule.waitUntilExists(
+            hasText(context.getString(listModel.switchTitleResId)) and isOff())
     }
 
     @Test
diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
index 754d9423..0d124e8 100644
--- a/packages/SettingsLib/aconfig/settingslib.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -108,4 +108,14 @@
     namespace: "android_settings"
     description: "Settings catalyst project migration"
     bug: "323791114"
-}
\ No newline at end of file
+}
+
+flag {
+  name: "asha_profile_access_profile_enabled_true"
+  namespace: "accessibility"
+  description: "Changes the return value of HearingAidProfile.accessProfileEnabled() to true"
+  bug: "356530795"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 0fec61c..92da2be 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -1026,21 +1026,29 @@
         return mDevice.getBluetoothClass();
     }
 
+    /**
+     * Returns a list of {@link LocalBluetoothProfile} supported by the device.
+     */
     public List<LocalBluetoothProfile> getProfiles() {
         return new ArrayList<>(mProfiles);
     }
 
-    public List<LocalBluetoothProfile> getConnectableProfiles() {
-        List<LocalBluetoothProfile> connectableProfiles =
-                new ArrayList<LocalBluetoothProfile>();
+    /**
+     * Returns a list of {@link LocalBluetoothProfile} that are user-accessible from UI to
+     * initiate a connection.
+     *
+     * Note: Use {@link #getProfiles()} to retrieve all supported profiles on the device.
+     */
+    public List<LocalBluetoothProfile> getUiAccessibleProfiles() {
+        List<LocalBluetoothProfile> accessibleProfiles = new ArrayList<>();
         synchronized (mProfileLock) {
             for (LocalBluetoothProfile profile : mProfiles) {
                 if (profile.accessProfileEnabled()) {
-                    connectableProfiles.add(profile);
+                    accessibleProfiles.add(profile);
                 }
             }
         }
-        return connectableProfiles;
+        return accessibleProfiles;
     }
 
     public List<LocalBluetoothProfile> getRemovedProfiles() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
index a49314a..c6eb9fd 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
@@ -178,7 +178,7 @@
         }
         log("updateRelationshipOfGroupDevices: mCachedDevices list =" + mCachedDevices.toString());
 
-        // Get the preferred main device by getPreferredMainDeviceWithoutConectionState
+        // Get the preferred main device by getPreferredMainDeviceWithoutConnectionState
         List<CachedBluetoothDevice> groupDevicesList = getGroupDevicesFromAllOfDevicesList(groupId);
         CachedBluetoothDevice preferredMainDevice =
                 getPreferredMainDevice(groupId, groupDevicesList);
@@ -261,9 +261,9 @@
         }
 
         CachedBluetoothDevice dualModeDevice = groupDevicesList.stream()
-                .filter(cachedDevice -> cachedDevice.getConnectableProfiles().stream()
+                .filter(cachedDevice -> cachedDevice.getUiAccessibleProfiles().stream()
                         .anyMatch(profile -> profile instanceof LeAudioProfile))
-                .filter(cachedDevice -> cachedDevice.getConnectableProfiles().stream()
+                .filter(cachedDevice -> cachedDevice.getUiAccessibleProfiles().stream()
                         .anyMatch(profile -> profile instanceof A2dpProfile
                                 || profile instanceof HeadsetProfile))
                 .findFirst().orElse(null);
@@ -373,6 +373,7 @@
                 preferredMainDevice.addMemberDevice(deviceItem);
                 mCachedDevices.remove(deviceItem);
                 mBtManager.getEventManager().dispatchDeviceRemoved(deviceItem);
+                preferredMainDevice.refresh();
                 hasChanged = true;
             }
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
index f2450de..fb9b4df 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
@@ -34,6 +34,7 @@
 
 import com.android.settingslib.R;
 import com.android.settingslib.Utils;
+import com.android.settingslib.flags.Flags;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -141,7 +142,7 @@
     }
 
     public boolean accessProfileEnabled() {
-        return false;
+        return Flags.ashaProfileAccessProfileEnabledTrue();
     }
 
     public boolean isAutoConnectable() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index 27fcdbe..26905b1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -80,6 +80,7 @@
             "com.android.settings.action.BLUETOOTH_LE_AUDIO_SHARING_STATE_CHANGE";
     public static final String EXTRA_LE_AUDIO_SHARING_STATE = "BLUETOOTH_LE_AUDIO_SHARING_STATE";
     public static final String EXTRA_BLUETOOTH_DEVICE = "BLUETOOTH_DEVICE";
+    public static final String EXTRA_START_LE_AUDIO_SHARING = "START_LE_AUDIO_SHARING";
     public static final int BROADCAST_STATE_UNKNOWN = 0;
     public static final int BROADCAST_STATE_ON = 1;
     public static final int BROADCAST_STATE_OFF = 2;
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 9ff5c43..326bb31 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
@@ -19,37 +19,39 @@
 import android.bluetooth.BluetoothAdapter
 import android.content.Context
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.devicesettings.ActionSwitchPreference
 import com.android.settingslib.bluetooth.devicesettings.DeviceSetting
 import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId
-import com.android.settingslib.bluetooth.devicesettings.DeviceSettingPreferenceState
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingItem
 import com.android.settingslib.bluetooth.devicesettings.DeviceSettingsConfig
-import java.util.concurrent.ConcurrentHashMap
+import com.android.settingslib.bluetooth.devicesettings.MultiTogglePreference
+import com.android.settingslib.bluetooth.devicesettings.ToggleInfo
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingStateModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.ToggleModel
+import com.google.common.cache.CacheBuilder
+import com.google.common.cache.CacheLoader
+import com.google.common.cache.LoadingCache
 import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
 
 /** Provides functionality to control bluetooth device settings. */
 interface DeviceSettingRepository {
     /** Gets config for the bluetooth device, returns null if failed. */
-    suspend fun getDeviceSettingsConfig(cachedDevice: CachedBluetoothDevice): DeviceSettingsConfig?
-
-    /** Gets all device settings for the bluetooth device. */
-    fun getDeviceSettingList(
-        cachedDevice: CachedBluetoothDevice,
-    ): Flow<List<DeviceSetting>?>
+    suspend fun getDeviceSettingsConfig(
+        cachedDevice: CachedBluetoothDevice
+    ): DeviceSettingConfigModel?
 
     /** Gets device setting for the bluetooth device. */
     fun getDeviceSetting(
         cachedDevice: CachedBluetoothDevice,
         @DeviceSettingId settingId: Int
-    ): Flow<DeviceSetting?>
-
-    /** Updates device setting for the bluetooth device. */
-    suspend fun updateDeviceSettingState(
-        cachedDevice: CachedBluetoothDevice,
-        @DeviceSettingId deviceSettingId: Int,
-        deviceSettingPreferenceState: DeviceSettingPreferenceState,
-    )
+    ): Flow<DeviceSettingModel?>
 }
 
 class DeviceSettingRepositoryImpl(
@@ -58,40 +60,94 @@
     private val coroutineScope: CoroutineScope,
     private val backgroundCoroutineContext: CoroutineContext,
 ) : DeviceSettingRepository {
-    private val deviceSettings =
-        ConcurrentHashMap<CachedBluetoothDevice, DeviceSettingServiceConnection>()
+    private val connectionCache:
+        LoadingCache<CachedBluetoothDevice, DeviceSettingServiceConnection> =
+        CacheBuilder.newBuilder()
+            .weakValues()
+            .build(
+                object : CacheLoader<CachedBluetoothDevice, DeviceSettingServiceConnection>() {
+                    override fun load(
+                        cachedDevice: CachedBluetoothDevice
+                    ): DeviceSettingServiceConnection =
+                        DeviceSettingServiceConnection(
+                            cachedDevice,
+                            context,
+                            bluetoothAdaptor,
+                            coroutineScope,
+                            backgroundCoroutineContext,
+                        )
+                }
+            )
 
     override suspend fun getDeviceSettingsConfig(
         cachedDevice: CachedBluetoothDevice
-    ): DeviceSettingsConfig? = createConnectionIfAbsent(cachedDevice).getDeviceSettingsConfig()
-
-    override fun getDeviceSettingList(
-        cachedDevice: CachedBluetoothDevice
-    ): Flow<List<DeviceSetting>?> = createConnectionIfAbsent(cachedDevice).getDeviceSettingList()
+    ): DeviceSettingConfigModel? =
+        connectionCache.get(cachedDevice).getDeviceSettingsConfig()?.toModel()
 
     override fun getDeviceSetting(
         cachedDevice: CachedBluetoothDevice,
         settingId: Int
-    ): Flow<DeviceSetting?> = createConnectionIfAbsent(cachedDevice).getDeviceSetting(settingId)
-
-    override suspend fun updateDeviceSettingState(
-        cachedDevice: CachedBluetoothDevice,
-        @DeviceSettingId deviceSettingId: Int,
-        deviceSettingPreferenceState: DeviceSettingPreferenceState,
-    ) =
-        createConnectionIfAbsent(cachedDevice)
-            .updateDeviceSettings(deviceSettingId, deviceSettingPreferenceState)
-
-    private fun createConnectionIfAbsent(
-        cachedDevice: CachedBluetoothDevice
-    ): DeviceSettingServiceConnection =
-        deviceSettings.computeIfAbsent(cachedDevice) {
-            DeviceSettingServiceConnection(
-                cachedDevice,
-                context,
-                bluetoothAdaptor,
-                coroutineScope,
-                backgroundCoroutineContext,
-            )
+    ): Flow<DeviceSettingModel?> =
+        connectionCache.get(cachedDevice).let { connection ->
+            connection.getDeviceSetting(settingId).map { it?.toModel(cachedDevice, connection) }
         }
+
+    private fun DeviceSettingsConfig.toModel(): DeviceSettingConfigModel =
+        DeviceSettingConfigModel(
+            mainItems = mainContentItems.map { it.toModel() },
+            moreSettingsItems = moreSettingsItems.map { it.toModel() },
+            moreSettingsPageFooter = moreSettingsFooter
+        )
+
+    private fun DeviceSettingItem.toModel(): DeviceSettingConfigItemModel =
+        DeviceSettingConfigItemModel(settingId)
+
+    private fun DeviceSetting.toModel(
+        cachedDevice: CachedBluetoothDevice,
+        connection: DeviceSettingServiceConnection
+    ): DeviceSettingModel =
+        when (val pref = preference) {
+            is ActionSwitchPreference ->
+                DeviceSettingModel.ActionSwitchPreference(
+                    cachedDevice = cachedDevice,
+                    id = settingId,
+                    title = pref.title,
+                    summary = pref.summary,
+                    icon = pref.icon,
+                    isAllowedChangingState = pref.isAllowedChangingState,
+                    intent = pref.intent,
+                    switchState =
+                        if (pref.hasSwitch()) {
+                            DeviceSettingStateModel.ActionSwitchPreferenceState(pref.checked)
+                        } else {
+                            null
+                        },
+                    updateState = { newState ->
+                        coroutineScope.launch(backgroundCoroutineContext) {
+                            connection.updateDeviceSettings(
+                                settingId,
+                                newState.toParcelable(),
+                            )
+                        }
+                    },
+                )
+            is MultiTogglePreference ->
+                DeviceSettingModel.MultiTogglePreference(
+                    cachedDevice = cachedDevice,
+                    id = settingId,
+                    title = pref.title,
+                    toggles = pref.toggleInfos.map { it.toModel() },
+                    isAllowedChangingState = pref.isAllowedChangingState,
+                    isActive = true,
+                    state = DeviceSettingStateModel.MultiTogglePreferenceState(pref.state),
+                    updateState = { newState ->
+                        coroutineScope.launch(backgroundCoroutineContext) {
+                            connection.updateDeviceSettings(settingId, newState.toParcelable())
+                        }
+                    },
+                )
+            else -> DeviceSettingModel.Unknown(cachedDevice, settingId)
+        }
+
+    private fun ToggleInfo.toModel(): ToggleModel = ToggleModel(label, icon)
 }
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
new file mode 100644
index 0000000..cd597ee
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt
@@ -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.
+ */
+
+package com.android.settingslib.bluetooth.devicesettings.shared.model
+
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId
+
+/** Models a device setting config. */
+data class DeviceSettingConfigModel(
+    /** Items need to be shown in device details main page. */
+    val mainItems: List<DeviceSettingConfigItemModel>,
+    /** Items need to be shown in device details more settings page. */
+    val moreSettingsItems: List<DeviceSettingConfigItemModel>,
+    /** Footer text in more settings page. */
+    val moreSettingsPageFooter: String)
+
+/** Models a device setting item in config. */
+data class DeviceSettingConfigItemModel(
+    @DeviceSettingId val settingId: Int,
+)
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 72a60fb..fe6659d1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -634,7 +634,7 @@
         }
 
         private boolean isMediaDevice(CachedBluetoothDevice device) {
-            for (LocalBluetoothProfile profile : device.getConnectableProfiles()) {
+            for (LocalBluetoothProfile profile : device.getUiAccessibleProfiles()) {
                 if (profile instanceof A2dpProfile || profile instanceof HearingAidProfile ||
                         profile instanceof LeAudioProfile) {
                     return true;
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 a06f084..f7492cf 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
@@ -16,8 +16,8 @@
 
 package com.android.settingslib.notification.modes;
 
-import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_UNKNOWN;
-import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER;
+import static android.service.notification.ZenModeConfig.ORIGIN_UNKNOWN;
+import static android.service.notification.ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI;
 
 import android.app.AutomaticZenRule;
 import android.app.NotificationManager;
@@ -40,13 +40,17 @@
     private ZenModeConfig.ZenRule mConfigZenRule;
 
     public static final ZenMode EXAMPLE = new TestModeBuilder().build();
-    public static final ZenMode MANUAL_DND = ZenMode.manualDndMode(
-            new AutomaticZenRule.Builder("Manual DND", Uri.parse("rule://dnd"))
-                    .setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY)
-                    .setZenPolicy(new ZenPolicy.Builder().disallowAllSounds().build())
-                    .build(),
-            true /* isActive */
-    );
+    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) {
+        return ZenMode.manualDndMode(
+                new AutomaticZenRule.Builder("Do Not Disturb", conditionId)
+                        .setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY)
+                        .setZenPolicy(new ZenPolicy.Builder().disallowAllSounds().build())
+                        .build(),
+                isActive);
+    }
 
     public TestModeBuilder() {
         // Reasonable defaults
@@ -154,7 +158,7 @@
         mRule.setEnabled(enabled);
         mConfigZenRule.enabled = enabled;
         if (!enabled) {
-            mConfigZenRule.disabledOrigin = byUser ? UPDATE_ORIGIN_USER : UPDATE_ORIGIN_UNKNOWN;
+            mConfigZenRule.disabledOrigin = byUser ? ORIGIN_USER_IN_SYSTEMUI : ORIGIN_UNKNOWN;
         }
         return this;
     }
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 88af7ee..2f4b2ef 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
@@ -22,6 +22,7 @@
 import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
 import static android.service.notification.SystemZenRules.getTriggerDescriptionForScheduleEvent;
 import static android.service.notification.SystemZenRules.getTriggerDescriptionForScheduleTime;
+import static android.service.notification.ZenModeConfig.tryParseCountdownConditionId;
 import static android.service.notification.ZenModeConfig.tryParseEventConditionId;
 import static android.service.notification.ZenModeConfig.tryParseScheduleConditionId;
 
@@ -66,7 +67,7 @@
 
     private static final String TAG = "ZenMode";
 
-    static final String MANUAL_DND_MODE_ID = "manual_dnd";
+    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.
@@ -119,7 +120,7 @@
                 return Status.ENABLED;
             }
         } else {
-            if (zenRuleExtraData.disabledOrigin == ZenModeConfig.UPDATE_ORIGIN_USER) {
+            if (zenRuleExtraData.disabledOrigin == ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI) {
                 return Status.DISABLED_BY_USER;
             } else {
                 return Status.DISABLED_BY_OTHER; // by APP, SYSTEM, UNKNOWN.
@@ -188,11 +189,37 @@
         return mRule.getType();
     }
 
+    /** Returns the trigger description of the mode. */
     @Nullable
     public String getTriggerDescription() {
         return mRule.getTriggerDescription();
     }
 
+    /**
+     * Returns a "dynamic" trigger description. For some modes (such as manual Do Not Disturb)
+     * when activated, we know when (and if) the mode is expected to end on its own; this dynamic
+     * description reflects that. In other cases, returns {@link #getTriggerDescription}.
+     */
+    @Nullable
+    public String getDynamicDescription(Context context) {
+        if (isManualDnd() && isActive()) {
+            long countdownEndTime = tryParseCountdownConditionId(mRule.getConditionId());
+            if (countdownEndTime > 0) {
+                CharSequence formattedTime = ZenModeConfig.getFormattedTime(context,
+                        countdownEndTime, ZenModeConfig.isToday(countdownEndTime),
+                        context.getUserId());
+                return context.getString(com.android.internal.R.string.zen_mode_until,
+                        formattedTime);
+            }
+        }
+        // TODO: b/333527800 - For TYPE_SCHEDULE_TIME rules we could do the same; however
+        //   according to the snoozing discussions the mode may or may not end at the scheduled
+        //   time if manually activated. When we resolve that point, we could calculate end time
+        //   for these modes as well.
+
+        return getTriggerDescription();
+    }
+
     @NonNull
     public ListenableFuture<Drawable> getIcon(@NonNull Context context,
             @NonNull ZenIconLoader iconLoader) {
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 f533c95..64e503b32 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModesBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModesBackend.java
@@ -116,7 +116,6 @@
 
     private ZenMode getManualDndMode(ZenModeConfig config) {
         ZenModeConfig.ZenRule manualRule = config.manualRule;
-        // TODO: b/333682392 - Replace with final strings for name & trigger description
         AutomaticZenRule manualDndRule = new AutomaticZenRule.Builder(
                 mContext.getString(R.string.zen_mode_settings_title), manualRule.conditionId)
                 .setType(manualRule.type)
@@ -127,7 +126,7 @@
                 .setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY)
                 .build();
 
-        return ZenMode.manualDndMode(manualDndRule, config != null && config.isManualActive());
+        return ZenMode.manualDndMode(manualDndRule, config.isManualActive());
     }
 
     public void updateMode(ZenMode mode) {
@@ -175,7 +174,6 @@
             mNotificationManager.setZenMode(Settings.Global.ZEN_MODE_OFF, null, TAG,
                     /* fromUser= */ true);
         } else {
-            // TODO: b/333527800 - This should (potentially) snooze the rule if it was active.
             mNotificationManager.setAutomaticZenRuleState(mode.getId(),
                     new Condition(mode.getRule().getConditionId(), "", Condition.STATE_FALSE,
                             Condition.SOURCE_USER_ACTION));
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
index 69c7410..6198d80 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
@@ -181,14 +181,14 @@
      * admin status.
      */
     public Dialog createDialog(Activity activity,
-            ActivityStarter activityStarter, boolean isMultipleAdminEnabled,
+            ActivityStarter activityStarter, boolean canCreateAdminUser,
             NewUserData successCallback, Runnable cancelCallback) {
         mActivity = activity;
         mCustomDialogHelper = new CustomDialogHelper(activity);
         mSuccessCallback = successCallback;
         mCancelCallback = cancelCallback;
         mActivityStarter = activityStarter;
-        addCustomViews(isMultipleAdminEnabled);
+        addCustomViews(canCreateAdminUser);
         mUserCreationDialog = mCustomDialogHelper.getDialog();
         updateLayout();
         mUserCreationDialog.setOnDismissListener(view -> finish());
@@ -197,19 +197,19 @@
         return mUserCreationDialog;
     }
 
-    private void addCustomViews(boolean isMultipleAdminEnabled) {
+    private void addCustomViews(boolean canCreateAdminUser) {
         addGrantAdminView();
         addUserInfoEditView();
         mCustomDialogHelper.setPositiveButton(R.string.next, view -> {
             mCurrentState++;
-            if (mCurrentState == GRANT_ADMIN_DIALOG && !isMultipleAdminEnabled) {
+            if (mCurrentState == GRANT_ADMIN_DIALOG && !canCreateAdminUser) {
                 mCurrentState++;
             }
             updateLayout();
         });
         mCustomDialogHelper.setNegativeButton(R.string.back, view -> {
             mCurrentState--;
-            if (mCurrentState == GRANT_ADMIN_DIALOG && !isMultipleAdminEnabled) {
+            if (mCurrentState == GRANT_ADMIN_DIALOG && !canCreateAdminUser) {
                 mCurrentState--;
             }
             updateLayout();
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 c88c4c9..0e71116 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
@@ -25,6 +25,7 @@
 import android.provider.Settings
 import androidx.concurrent.futures.DirectExecutor
 import com.android.internal.util.ConcurrentUtils
+import com.android.settingslib.volume.shared.AudioLogger
 import com.android.settingslib.volume.shared.AudioManagerEventsReceiver
 import com.android.settingslib.volume.shared.model.AudioManagerEvent
 import com.android.settingslib.volume.shared.model.AudioStream
@@ -99,7 +100,7 @@
     private val contentResolver: ContentResolver,
     private val backgroundCoroutineContext: CoroutineContext,
     private val coroutineScope: CoroutineScope,
-    private val logger: Logger,
+    private val logger: AudioLogger,
 ) : AudioRepository {
 
     private val streamSettingNames: Map<AudioStream, String> =
@@ -117,10 +118,10 @@
 
     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)
@@ -140,14 +141,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) }
@@ -160,15 +161,15 @@
 
     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),
+        )
             .conflate()
             .map { getCurrentAudioStream(audioStream) }
             .onStart { emit(getCurrentAudioStream(audioStream)) }
@@ -251,11 +252,4 @@
             awaitClose { contentResolver.unregisterContentObserver(observer) }
         }
     }
-
-    interface Logger {
-
-        fun onSetVolumeRequested(audioStream: AudioStream, volume: Int)
-
-        fun onVolumeUpdateReceived(audioStream: AudioStream, model: AudioStreamModel)
-    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt
index 7a66335..ebba7f1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt
@@ -34,6 +34,7 @@
 import com.android.settingslib.bluetooth.onSourceConnectedOrRemoved
 import com.android.settingslib.volume.data.repository.AudioSharingRepository.Companion.AUDIO_SHARING_VOLUME_MAX
 import com.android.settingslib.volume.data.repository.AudioSharingRepository.Companion.AUDIO_SHARING_VOLUME_MIN
+import com.android.settingslib.volume.shared.AudioSharingLogger
 import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -50,6 +51,7 @@
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.runningFold
 import kotlinx.coroutines.flow.stateIn
@@ -90,6 +92,7 @@
     private val btManager: LocalBluetoothManager,
     private val coroutineScope: CoroutineScope,
     private val backgroundCoroutineContext: CoroutineContext,
+    private val logger: AudioSharingLogger
 ) : AudioSharingRepository {
     private val isAudioSharingProfilesReady: StateFlow<Boolean> =
         btManager.profileManager.onServiceStateChanged
@@ -104,6 +107,7 @@
                 btManager.profileManager.leAudioBroadcastProfile.onBroadcastStartedOrStopped
                     .map { isBroadcasting() }
                     .onStart { emit(isBroadcasting()) }
+                    .onEach { logger.onAudioSharingStateChanged(it) }
                     .flowOn(backgroundCoroutineContext)
             } else {
                 flowOf(false)
@@ -156,6 +160,7 @@
                 .map { getSecondaryGroupId() },
             primaryGroupId.map { getSecondaryGroupId() })
             .onStart { emit(getSecondaryGroupId()) }
+            .onEach { logger.onSecondaryGroupIdChanged(it) }
             .flowOn(backgroundCoroutineContext)
             .stateIn(
                 coroutineScope,
@@ -202,6 +207,7 @@
                             acc
                         }
                     }
+                    .onEach { logger.onVolumeMapChanged(it) }
                     .flowOn(backgroundCoroutineContext)
             } else {
                 emptyFlow()
@@ -220,6 +226,7 @@
                     BluetoothUtils.getSecondaryDeviceForBroadcast(contentResolver, btManager)
                 if (cachedDevice != null) {
                     it.setDeviceVolume(cachedDevice.device, volume, /* isGroupOp= */ true)
+                    logger.onSetDeviceVolumeRequested(volume)
                 }
             }
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioLogger.kt b/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioLogger.kt
new file mode 100644
index 0000000..84f7fcb
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioLogger.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.volume.shared
+
+import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.settingslib.volume.shared.model.AudioStreamModel
+
+/** A log interface for audio streams volume events. */
+interface AudioLogger {
+    fun onSetVolumeRequested(audioStream: AudioStream, volume: Int)
+
+    fun onVolumeUpdateReceived(audioStream: AudioStream, model: AudioStreamModel)
+}
\ No newline at end of file
diff --git a/telephony/java/android/telephony/satellite/stub/ProvisionSubscriberId.aidl b/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioSharingLogger.kt
similarity index 64%
copy from telephony/java/android/telephony/satellite/stub/ProvisionSubscriberId.aidl
copy to packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioSharingLogger.kt
index 460de8c..18a4c6d 100644
--- a/telephony/java/android/telephony/satellite/stub/ProvisionSubscriberId.aidl
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioSharingLogger.kt
@@ -14,18 +14,16 @@
  * limitations under the License.
  */
 
-package android.telephony.satellite.stub;
+package com.android.settingslib.volume.shared
 
-/**
- * {@hide}
- */
-parcelable ProvisionSubscriberId {
-    /** provision subscriberId */
-    String subscriberId;
+/** A log interface for audio sharing volume events. */
+interface AudioSharingLogger {
 
-    /** carrier id */
-    int mCarrierId;
+    fun onAudioSharingStateChanged(state: Boolean)
 
-    /** apn */
-    String mNiddApn;
-}
+    fun onSecondaryGroupIdChanged(groupId: Int)
+
+    fun onVolumeMapChanged(map: Map<Int, Int>)
+
+    fun onSetDeviceVolumeRequested(volume: Int)
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryTest.kt
index 078f0c8..8c5a085 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryTest.kt
@@ -48,6 +48,7 @@
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
+import org.junit.After
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -101,7 +102,7 @@
 
     @Captor
     private lateinit var assistantCallbackCaptor:
-        ArgumentCaptor<BluetoothLeBroadcastAssistant.Callback>
+            ArgumentCaptor<BluetoothLeBroadcastAssistant.Callback>
 
     @Captor private lateinit var btCallbackCaptor: ArgumentCaptor<BluetoothCallback>
 
@@ -110,6 +111,7 @@
     @Captor
     private lateinit var volumeCallbackCaptor: ArgumentCaptor<BluetoothVolumeControl.Callback>
 
+    private val logger = FakeAudioSharingRepositoryLogger()
     private val testScope = TestScope()
     private val context: Context = ApplicationProvider.getApplicationContext()
     @Spy private val contentResolver: ContentResolver = context.contentResolver
@@ -135,16 +137,23 @@
         Settings.Secure.putInt(
             contentResolver,
             BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
-            TEST_GROUP_ID_INVALID)
+            TEST_GROUP_ID_INVALID
+        )
         underTest =
             AudioSharingRepositoryImpl(
                 contentResolver,
                 btManager,
                 testScope.backgroundScope,
                 testScope.testScheduler,
+                logger
             )
     }
 
+    @After
+    fun tearDown() {
+        logger.reset()
+    }
+
     @Test
     fun audioSharingStateChange_profileReady_emitValues() {
         testScope.runTest {
@@ -160,6 +169,13 @@
             runCurrent()
 
             Truth.assertThat(states).containsExactly(false, true, false, true)
+            Truth.assertThat(logger.logs)
+                .containsAtLeastElementsIn(
+                    listOf(
+                        "onAudioSharingStateChanged state=true",
+                        "onAudioSharingStateChanged state=false",
+                    )
+                ).inOrder()
         }
     }
 
@@ -187,7 +203,8 @@
             Truth.assertThat(groupIds)
                 .containsExactly(
                     TEST_GROUP_ID_INVALID,
-                    TEST_GROUP_ID2)
+                    TEST_GROUP_ID2
+                )
         }
     }
 
@@ -219,13 +236,16 @@
             triggerSourceAdded()
             runCurrent()
             triggerProfileConnectionChange(
-                BluetoothAdapter.STATE_CONNECTING, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT)
+                BluetoothAdapter.STATE_CONNECTING, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT
+            )
             runCurrent()
             triggerProfileConnectionChange(
-                BluetoothAdapter.STATE_DISCONNECTED, BluetoothProfile.LE_AUDIO)
+                BluetoothAdapter.STATE_DISCONNECTED, BluetoothProfile.LE_AUDIO
+            )
             runCurrent()
             triggerProfileConnectionChange(
-                BluetoothAdapter.STATE_DISCONNECTED, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT)
+                BluetoothAdapter.STATE_DISCONNECTED, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT
+            )
             runCurrent()
 
             Truth.assertThat(groupIds)
@@ -235,7 +255,16 @@
                     TEST_GROUP_ID1,
                     TEST_GROUP_ID_INVALID,
                     TEST_GROUP_ID2,
-                    TEST_GROUP_ID_INVALID)
+                    TEST_GROUP_ID_INVALID
+                )
+            Truth.assertThat(logger.logs)
+                .containsAtLeastElementsIn(
+                    listOf(
+                        "onSecondaryGroupIdChanged groupId=$TEST_GROUP_ID_INVALID",
+                        "onSecondaryGroupIdChanged groupId=$TEST_GROUP_ID2",
+                        "onSecondaryGroupIdChanged groupId=$TEST_GROUP_ID1",
+                    )
+                ).inOrder()
         }
     }
 
@@ -257,11 +286,22 @@
             verify(volumeControl).unregisterCallback(any())
             runCurrent()
 
+            val expectedMap1 = mapOf(TEST_GROUP_ID1 to TEST_VOLUME1)
+            val expectedMap2 = mapOf(TEST_GROUP_ID1 to TEST_VOLUME2)
             Truth.assertThat(volumeMaps)
                 .containsExactly(
                     emptyMap<Int, Int>(),
-                    mapOf(TEST_GROUP_ID1 to TEST_VOLUME1),
-                    mapOf(TEST_GROUP_ID1 to TEST_VOLUME2))
+                    expectedMap1,
+                    expectedMap2
+                )
+            Truth.assertThat(logger.logs)
+                .containsAtLeastElementsIn(
+                    listOf(
+                        "onVolumeMapChanged map={}",
+                        "onVolumeMapChanged map=$expectedMap1",
+                        "onVolumeMapChanged map=$expectedMap2",
+                    )
+                ).inOrder()
         }
     }
 
@@ -281,12 +321,19 @@
             Settings.Secure.putInt(
                 contentResolver,
                 BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
-                TEST_GROUP_ID2)
+                TEST_GROUP_ID2
+            )
             `when`(assistant.allConnectedDevices).thenReturn(listOf(device1, device2))
             underTest.setSecondaryVolume(TEST_VOLUME1)
 
             runCurrent()
             verify(volumeControl).setDeviceVolume(device1, TEST_VOLUME1, true)
+            Truth.assertThat(logger.logs)
+                .isEqualTo(
+                    listOf(
+                        "onSetVolumeRequested volume=$TEST_VOLUME1",
+                    )
+                )
         }
     }
 
@@ -313,7 +360,8 @@
         Settings.Secure.putInt(
             contentResolver,
             BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
-            TEST_GROUP_ID1)
+            TEST_GROUP_ID1
+        )
         `when`(assistant.allConnectedDevices).thenReturn(listOf(device1, device2))
         assistantCallbackCaptor.value.sourceAdded(device1, receiveState)
     }
@@ -324,7 +372,8 @@
         Settings.Secure.putInt(
             contentResolver,
             BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
-            TEST_GROUP_ID1)
+            TEST_GROUP_ID1
+        )
         assistantCallbackCaptor.value.sourceRemoved(device2)
     }
 
@@ -334,7 +383,8 @@
         Settings.Secure.putInt(
             contentResolver,
             BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
-            TEST_GROUP_ID1)
+            TEST_GROUP_ID1
+        )
         btCallbackCaptor.value.onProfileConnectionStateChanged(cachedDevice2, state, profile)
     }
 
@@ -343,12 +393,14 @@
             .registerContentObserver(
                 eq(Settings.Secure.getUriFor(BluetoothUtils.getPrimaryGroupIdUriForBroadcast())),
                 eq(false),
-                contentObserverCaptor.capture())
+                contentObserverCaptor.capture()
+            )
         `when`(assistant.allConnectedDevices).thenReturn(listOf(device1, device2))
         Settings.Secure.putInt(
             contentResolver,
             BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
-            TEST_GROUP_ID2)
+            TEST_GROUP_ID2
+        )
         contentObserverCaptor.value.primaryChanged()
     }
 
@@ -380,8 +432,9 @@
             onBroadcastStopped(TEST_REASON, TEST_BROADCAST_ID)
         }
         val sourceAdded:
-            BluetoothLeBroadcastAssistant.Callback.(
-                sink: BluetoothDevice, state: BluetoothLeBroadcastReceiveState) -> Unit =
+                BluetoothLeBroadcastAssistant.Callback.(
+                    sink: BluetoothDevice, state: BluetoothLeBroadcastReceiveState
+                ) -> Unit =
             { sink, state ->
                 onReceiveStateChanged(sink, TEST_SOURCE_ID, state)
             }
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepositoryLogger.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepositoryLogger.kt
index 389bf53..bd573fb 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepositoryLogger.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepositoryLogger.kt
@@ -16,10 +16,11 @@
 
 package com.android.settingslib.volume.data.repository
 
+import com.android.settingslib.volume.shared.AudioLogger
 import com.android.settingslib.volume.shared.model.AudioStream
 import com.android.settingslib.volume.shared.model.AudioStreamModel
 
-class FakeAudioRepositoryLogger : AudioRepositoryImpl.Logger {
+class FakeAudioRepositoryLogger : AudioLogger {
 
     private val mutableLogs: MutableList<String> = mutableListOf()
     val logs: List<String>
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioSharingRepositoryLogger.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioSharingRepositoryLogger.kt
new file mode 100644
index 0000000..cc4cc8d
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioSharingRepositoryLogger.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.settingslib.volume.data.repository
+
+import com.android.settingslib.volume.shared.AudioSharingLogger
+import java.util.concurrent.CopyOnWriteArrayList
+
+class FakeAudioSharingRepositoryLogger : AudioSharingLogger {
+    private val mutableLogs = CopyOnWriteArrayList<String>()
+    val logs: List<String>
+        get() = mutableLogs.toList()
+
+    fun reset() {
+        mutableLogs.clear()
+    }
+
+    override fun onAudioSharingStateChanged(state: Boolean) {
+        mutableLogs.add("onAudioSharingStateChanged state=$state")
+    }
+
+    override fun onSecondaryGroupIdChanged(groupId: Int) {
+        mutableLogs.add("onSecondaryGroupIdChanged groupId=$groupId")
+    }
+
+    override fun onVolumeMapChanged(map: GroupIdToVolumes) {
+        mutableLogs.add("onVolumeMapChanged map=$map")
+    }
+
+    override fun onSetDeviceVolumeRequested(volume: Int) {
+        mutableLogs.add("onSetVolumeRequested volume=$volume")
+    }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
index 7e7c76e..8cc9974 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
@@ -35,6 +35,7 @@
 import android.os.ParcelUuid;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -545,6 +546,7 @@
      * Test to verify OnDeviceUnpaired() for csip device unpair.
      */
     @Test
+    @Ignore("b/359066481")
     public void onDeviceUnpaired_unpairCsipSubDevice() {
         when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
         when(mDevice2.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java
index 3f59da4..698eb81 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java
@@ -19,7 +19,9 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.bluetooth.BluetoothClass;
@@ -145,18 +147,18 @@
         profiles.add(mHfpProfile);
         profiles.add(mA2dpProfile);
         profiles.add(mLeAudioProfile);
-        when(mCachedDevice1.getConnectableProfiles()).thenReturn(profiles);
+        when(mCachedDevice1.getUiAccessibleProfiles()).thenReturn(profiles);
         when(mCachedDevice1.isConnected()).thenReturn(true);
 
         profiles.clear();
         profiles.add(mLeAudioProfile);
-        when(mCachedDevice2.getConnectableProfiles()).thenReturn(profiles);
+        when(mCachedDevice2.getUiAccessibleProfiles()).thenReturn(profiles);
         when(mCachedDevice2.isConnected()).thenReturn(true);
 
         profiles.clear();
         profiles.add(mHfpProfile);
         profiles.add(mA2dpProfile);
-        when(mCachedDevice3.getConnectableProfiles()).thenReturn(profiles);
+        when(mCachedDevice3.getUiAccessibleProfiles()).thenReturn(profiles);
         when(mCachedDevice3.isConnected()).thenReturn(true);
     }
 
@@ -253,7 +255,7 @@
         when(mDevice2.isConnected()).thenReturn(false);
         List<LocalBluetoothProfile> profiles = new ArrayList<LocalBluetoothProfile>();
         profiles.add(mLeAudioProfile);
-        when(mCachedDevice1.getConnectableProfiles()).thenReturn(profiles);
+        when(mCachedDevice1.getUiAccessibleProfiles()).thenReturn(profiles);
         CachedBluetoothDevice expectedDevice = mCachedDevice1;
 
         assertThat(
@@ -352,4 +354,34 @@
         assertThat(mCachedDevice1.getMemberDevice()).contains(mCachedDevice3);
         assertThat(mCachedDevice1.getDevice()).isEqualTo(expectedMainBluetoothDevice);
     }
+
+    @Test
+    public void onProfileConnectionStateChangedIfProcessed_addMemberDevice_refreshUI() {
+        mCachedDevice3.setGroupId(GROUP1);
+
+        mCsipDeviceManager.onProfileConnectionStateChangedIfProcessed(mCachedDevice3,
+                BluetoothProfile.STATE_CONNECTED);
+
+        verify(mCachedDevice1).refresh();
+    }
+
+    @Test
+    public void onProfileConnectionStateChangedIfProcessed_switchMainDevice_refreshUI() {
+        when(mDevice3.isConnected()).thenReturn(true);
+        when(mDevice2.isConnected()).thenReturn(false);
+        when(mDevice1.isConnected()).thenReturn(false);
+        mCachedDevice3.setGroupId(GROUP1);
+        mCsipDeviceManager.onProfileConnectionStateChangedIfProcessed(mCachedDevice3,
+                BluetoothProfile.STATE_CONNECTED);
+
+        when(mDevice3.isConnected()).thenReturn(false);
+        mCsipDeviceManager.onProfileConnectionStateChangedIfProcessed(mCachedDevice3,
+                BluetoothProfile.STATE_DISCONNECTED);
+        when(mDevice1.isConnected()).thenReturn(true);
+        mCsipDeviceManager.onProfileConnectionStateChangedIfProcessed(mCachedDevice1,
+                BluetoothProfile.STATE_CONNECTED);
+
+        verify(mCachedDevice3).switchMemberDeviceContent(mCachedDevice1);
+        verify(mCachedDevice3, atLeastOnce()).refresh();
+    }
 }
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 b5457c5..fee2394 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
@@ -22,6 +22,7 @@
 import android.content.Context
 import android.content.Intent
 import android.content.ServiceConnection
+import android.graphics.Bitmap
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
 import com.android.settingslib.bluetooth.devicesettings.ActionSwitchPreference
 import com.android.settingslib.bluetooth.devicesettings.ActionSwitchPreferenceState
@@ -34,6 +35,14 @@
 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.MultiTogglePreference
+import com.android.settingslib.bluetooth.devicesettings.MultiTogglePreferenceState
+import com.android.settingslib.bluetooth.devicesettings.ToggleInfo
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingStateModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.ToggleModel
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.delay
@@ -148,7 +157,7 @@
 
             val config = underTest.getDeviceSettingsConfig(cachedDevice)
 
-            assertThat(config).isSameInstanceAs(DEVICE_SETTING_CONFIG)
+            assertConfig(config!!, DEVICE_SETTING_CONFIG)
         }
     }
 
@@ -163,7 +172,7 @@
                 )
                 .thenReturn("".toByteArray())
 
-            var config: DeviceSettingsConfig? = null
+            var config: DeviceSettingConfigModel? = null
             val job = launch { config = underTest.getDeviceSettingsConfig(cachedDevice) }
             delay(1000)
             verify(bluetoothAdapter)
@@ -185,7 +194,7 @@
                 .thenReturn(BLUETOOTH_DEVICE_METADATA.toByteArray())
 
             job.join()
-            assertThat(config).isSameInstanceAs(DEVICE_SETTING_CONFIG)
+            assertConfig(config!!, DEVICE_SETTING_CONFIG)
         }
     }
 
@@ -202,7 +211,7 @@
     }
 
     @Test
-    fun getDeviceSettingList_success() {
+    fun getDeviceSetting_actionSwitchPreference_success() {
         testScope.runTest {
             `when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
             `when`(settingProviderService1.registerDeviceSettingsListener(any(), any())).then {
@@ -211,73 +220,7 @@
                     .getArgument<IDeviceSettingsListener>(1)
                     .onDeviceSettingsChanged(listOf(DEVICE_SETTING_1))
             }
-            `when`(settingProviderService2.registerDeviceSettingsListener(any(), any())).then {
-                input ->
-                input
-                    .getArgument<IDeviceSettingsListener>(1)
-                    .onDeviceSettingsChanged(listOf(DEVICE_SETTING_2))
-            }
-            var settings: List<DeviceSetting>? = null
-
-            underTest
-                .getDeviceSettingList(cachedDevice)
-                .onEach { settings = it }
-                .launchIn(backgroundScope)
-            runCurrent()
-
-            assertThat(settings?.map { it.settingId })
-                .containsExactly(
-                    DeviceSettingId.DEVICE_SETTING_ID_HEADER,
-                    DeviceSettingId.DEVICE_SETTING_ID_ANC
-                )
-            assertThat(settings?.map { (it.preference as ActionSwitchPreference).title })
-                .containsExactly(
-                    "title1",
-                    "title2",
-                )
-        }
-    }
-
-    @Test
-    fun getDeviceSetting_oneServiceFailed_returnPartialResult() {
-        testScope.runTest {
-            `when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
-            `when`(settingProviderService1.registerDeviceSettingsListener(any(), any())).then {
-                input ->
-                input
-                    .getArgument<IDeviceSettingsListener>(1)
-                    .onDeviceSettingsChanged(listOf(DEVICE_SETTING_1))
-            }
-            var settings: List<DeviceSetting>? = null
-
-            underTest
-                .getDeviceSettingList(cachedDevice)
-                .onEach { settings = it }
-                .launchIn(backgroundScope)
-            runCurrent()
-
-            assertThat(settings?.map { it.settingId })
-                .containsExactly(
-                    DeviceSettingId.DEVICE_SETTING_ID_HEADER,
-                )
-            assertThat(settings?.map { (it.preference as ActionSwitchPreference).title })
-                .containsExactly(
-                    "title1",
-                )
-        }
-    }
-
-    @Test
-    fun getDeviceSetting_success() {
-        testScope.runTest {
-            `when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
-            `when`(settingProviderService1.registerDeviceSettingsListener(any(), any())).then {
-                input ->
-                input
-                    .getArgument<IDeviceSettingsListener>(1)
-                    .onDeviceSettingsChanged(listOf(DEVICE_SETTING_1))
-            }
-            var setting: DeviceSetting? = null
+            var setting: DeviceSettingModel? = null
 
             underTest
                 .getDeviceSetting(cachedDevice, DeviceSettingId.DEVICE_SETTING_ID_HEADER)
@@ -285,13 +228,55 @@
                 .launchIn(backgroundScope)
             runCurrent()
 
-            assertThat(setting?.settingId).isEqualTo(DeviceSettingId.DEVICE_SETTING_ID_HEADER)
-            assertThat((setting?.preference as ActionSwitchPreference).title).isEqualTo("title1")
+            assertDeviceSetting(setting!!, DEVICE_SETTING_1)
         }
     }
 
     @Test
-    fun updateDeviceSetting_success() {
+    fun getDeviceSetting_multiTogglePreference_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_2))
+            }
+            var setting: DeviceSettingModel? = null
+
+            underTest
+                .getDeviceSetting(cachedDevice, DeviceSettingId.DEVICE_SETTING_ID_ANC)
+                .onEach { setting = it }
+                .launchIn(backgroundScope)
+            runCurrent()
+
+            assertDeviceSetting(setting!!, DEVICE_SETTING_2)
+        }
+    }
+
+    @Test
+    fun getDeviceSetting_noConfig_returnNull() {
+        testScope.runTest {
+            `when`(settingProviderService1.registerDeviceSettingsListener(any(), any())).then {
+                input ->
+                input
+                    .getArgument<IDeviceSettingsListener>(1)
+                    .onDeviceSettingsChanged(listOf(DEVICE_SETTING_1))
+            }
+            var setting: DeviceSettingModel? = null
+
+            underTest
+                .getDeviceSetting(cachedDevice, DeviceSettingId.DEVICE_SETTING_ID_HEADER)
+                .onEach { setting = it }
+                .launchIn(backgroundScope)
+            runCurrent()
+
+            assertThat(setting).isNull()
+        }
+    }
+
+    @Test
+    fun updateDeviceSettingState_switchState_success() {
         testScope.runTest {
             `when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
             `when`(settingProviderService1.registerDeviceSettingsListener(any(), any())).then {
@@ -300,12 +285,15 @@
                     .getArgument<IDeviceSettingsListener>(1)
                     .onDeviceSettingsChanged(listOf(DEVICE_SETTING_1))
             }
+            var setting: DeviceSettingModel? = null
 
-            underTest.updateDeviceSettingState(
-                cachedDevice,
-                DeviceSettingId.DEVICE_SETTING_ID_HEADER,
-                ActionSwitchPreferenceState.Builder().build()
-            )
+            underTest
+                .getDeviceSetting(cachedDevice, DeviceSettingId.DEVICE_SETTING_ID_HEADER)
+                .onEach { setting = it }
+                .launchIn(backgroundScope)
+            runCurrent()
+            val updateFunc = (setting as DeviceSettingModel.ActionSwitchPreference).updateState!!
+            updateFunc(DeviceSettingStateModel.ActionSwitchPreferenceState(false))
             runCurrent()
 
             verify(settingProviderService1)
@@ -313,12 +301,107 @@
                     DEVICE_INFO,
                     DeviceSettingState.Builder()
                         .setSettingId(DeviceSettingId.DEVICE_SETTING_ID_HEADER)
-                        .setPreferenceState(ActionSwitchPreferenceState.Builder().build())
+                        .setPreferenceState(
+                            ActionSwitchPreferenceState.Builder().setChecked(false).build()
+                        )
                         .build()
                 )
         }
     }
 
+    @Test
+    fun updateDeviceSettingState_multiToggleState_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_2))
+            }
+            var setting: DeviceSettingModel? = null
+
+            underTest
+                .getDeviceSetting(cachedDevice, DeviceSettingId.DEVICE_SETTING_ID_ANC)
+                .onEach { setting = it }
+                .launchIn(backgroundScope)
+            runCurrent()
+            val updateFunc = (setting as DeviceSettingModel.MultiTogglePreference).updateState
+            updateFunc(DeviceSettingStateModel.MultiTogglePreferenceState(2))
+            runCurrent()
+
+            verify(settingProviderService2)
+                .updateDeviceSettings(
+                    DEVICE_INFO,
+                    DeviceSettingState.Builder()
+                        .setSettingId(DeviceSettingId.DEVICE_SETTING_ID_ANC)
+                        .setPreferenceState(
+                            MultiTogglePreferenceState.Builder().setState(2).build()
+                        )
+                        .build()
+                )
+        }
+    }
+
+    private fun assertDeviceSetting(actual: DeviceSettingModel, serviceResponse: DeviceSetting) {
+        assertThat(actual.id).isEqualTo(serviceResponse.settingId)
+        when (actual) {
+            is DeviceSettingModel.ActionSwitchPreference -> {
+                assertThat(serviceResponse.preference)
+                    .isInstanceOf(ActionSwitchPreference::class.java)
+                val pref = serviceResponse.preference as ActionSwitchPreference
+                assertThat(actual.title).isEqualTo(pref.title)
+                assertThat(actual.summary).isEqualTo(pref.summary)
+                assertThat(actual.icon).isEqualTo(pref.icon)
+                assertThat(actual.isAllowedChangingState).isEqualTo(pref.isAllowedChangingState)
+                if (pref.hasSwitch()) {
+                    assertThat(actual.switchState!!.checked).isEqualTo(pref.checked)
+                } else {
+                    assertThat(actual.switchState).isNull()
+                }
+            }
+            is DeviceSettingModel.MultiTogglePreference -> {
+                assertThat(serviceResponse.preference)
+                    .isInstanceOf(MultiTogglePreference::class.java)
+                val pref = serviceResponse.preference as MultiTogglePreference
+                assertThat(actual.title).isEqualTo(pref.title)
+                assertThat(actual.isAllowedChangingState).isEqualTo(pref.isAllowedChangingState)
+                assertThat(actual.toggles.size).isEqualTo(pref.toggleInfos.size)
+                for (i in 0..<actual.toggles.size) {
+                    assertToggle(actual.toggles[i], pref.toggleInfos[i])
+                }
+            }
+            else -> {}
+        }
+    }
+
+    private fun assertToggle(actual: ToggleModel, serviceResponse: ToggleInfo) {
+        assertThat(actual.label).isEqualTo(serviceResponse.label)
+        assertThat(actual.icon).isEqualTo(serviceResponse.icon)
+    }
+
+    private fun assertConfig(
+        actual: DeviceSettingConfigModel,
+        serviceResponse: DeviceSettingsConfig
+    ) {
+        assertThat(actual.mainItems.size).isEqualTo(serviceResponse.mainContentItems.size)
+        for (i in 0..<actual.mainItems.size) {
+            assertConfigItem(actual.mainItems[i], serviceResponse.mainContentItems[i])
+        }
+        assertThat(actual.moreSettingsItems.size).isEqualTo(serviceResponse.moreSettingsItems.size)
+        for (i in 0..<actual.moreSettingsItems.size) {
+            assertConfigItem(actual.moreSettingsItems[i], serviceResponse.moreSettingsItems[i])
+        }
+        assertThat(actual.moreSettingsPageFooter).isEqualTo(serviceResponse.moreSettingsFooter)
+    }
+
+    private fun assertConfigItem(
+        actual: DeviceSettingConfigItemModel,
+        serviceResponse: DeviceSettingItem
+    ) {
+        assertThat(actual.settingId).isEqualTo(serviceResponse.settingId)
+    }
+
     private companion object {
         const val BLUETOOTH_ADDRESS = "12:34:56:78"
         const val CONFIG_SERVICE_PACKAGE_NAME = "com.android.fake.configservice"
@@ -377,10 +460,21 @@
             DeviceSetting.Builder()
                 .setSettingId(DeviceSettingId.DEVICE_SETTING_ID_ANC)
                 .setPreference(
-                    ActionSwitchPreference.Builder()
-                        .setTitle("title2")
-                        .setHasSwitch(true)
-                        .setAllowedChangingState(true)
+                    MultiTogglePreference.Builder()
+                        .setTitle("title1")
+                        .setAllowChangingState(true)
+                        .addToggleInfo(
+                            ToggleInfo.Builder()
+                                .setLabel("label1")
+                                .setIcon(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888))
+                                .build()
+                        )
+                        .addToggleInfo(
+                            ToggleInfo.Builder()
+                                .setLabel("label2")
+                                .setIcon(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888))
+                                .build()
+                        )
                         .build()
                 )
                 .build()
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java
index 194a0e2..4e54d8f 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java
@@ -40,6 +40,7 @@
 import com.android.settingslib.testutils.shadow.ShadowInteractionJankMonitor;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -58,6 +59,7 @@
 
 @RunWith(RobolectricTestRunner.class)
 @Config(shadows = {ShadowInteractionJankMonitor.class, SettingsJankMonitorTest.ShadowBuilder.class})
+@Ignore("b/359066481")
 public class SettingsJankMonitorTest {
     private static final String TEST_KEY = "key";
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java
index 0d318c3..325bb2c 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java
@@ -36,6 +36,7 @@
 import com.android.settingslib.testutils.shadow.ShadowSmsApplication;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -86,6 +87,7 @@
         mPowerAllowlistBackend = new PowerAllowlistBackend(mContext, mDeviceIdleService);
     }
 
+    @Ignore("b/359066481")
     @Test
     public void testIsAllowlisted() throws Exception {
         doReturn(new String[] {PACKAGE_ONE}).when(mDeviceIdleService).getFullPowerWhitelist();
@@ -160,6 +162,7 @@
         assertThat(mPowerAllowlistBackend.isDefaultActiveApp(PACKAGE_ONE, UID)).isTrue();
     }
 
+    @Ignore("b/359066481")
     @Test
     public void testIsSystemAllowlisted() throws Exception {
         doReturn(new String[] {PACKAGE_ONE}).when(mDeviceIdleService).getSystemPowerWhitelist();
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
index a30d6a7..3e8457b 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
@@ -470,7 +470,7 @@
         when(cachedManager.findDevice(bluetoothDevice)).thenReturn(cachedDevice);
         when(cachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
         when(cachedDevice.isConnected()).thenReturn(false);
-        when(cachedDevice.getConnectableProfiles()).thenReturn(profiles);
+        when(cachedDevice.getUiAccessibleProfiles()).thenReturn(profiles);
         when(cachedDevice.getDevice()).thenReturn(bluetoothDevice);
         when(cachedDevice.getAddress()).thenReturn(TEST_ADDRESS);
         when(mA2dpProfile.getActiveDevice()).thenReturn(bluetoothDevice);
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 651e57c..d9fdcc38 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
@@ -93,7 +93,7 @@
     public void constructor_disabledRuleByUser_statusDisabledByUser() {
         AutomaticZenRule azr = new AutomaticZenRule.Builder(ZEN_RULE).setEnabled(false).build();
         ZenModeConfig.ZenRule configZenRule = zenConfigRuleFor(azr, false);
-        configZenRule.disabledOrigin = ZenModeConfig.UPDATE_ORIGIN_USER;
+        configZenRule.disabledOrigin = ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI;
 
         ZenMode mode = new ZenMode("id", azr, configZenRule);
         assertThat(mode.getStatus()).isEqualTo(ZenMode.Status.DISABLED_BY_USER);
@@ -103,7 +103,7 @@
     public void constructor_disabledRuleByOther_statusDisabledByOther() {
         AutomaticZenRule azr = new AutomaticZenRule.Builder(ZEN_RULE).setEnabled(false).build();
         ZenModeConfig.ZenRule configZenRule = zenConfigRuleFor(azr, false);
-        configZenRule.disabledOrigin = ZenModeConfig.UPDATE_ORIGIN_APP;
+        configZenRule.disabledOrigin = ZenModeConfig.ORIGIN_APP;
 
         ZenMode mode = new ZenMode("id", azr, configZenRule);
         assertThat(mode.getStatus()).isEqualTo(ZenMode.Status.DISABLED_BY_OTHER);
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 00c7ae3..539519b 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
@@ -123,7 +123,6 @@
         zenRule.id = id;
         zenRule.pkg = "package";
         zenRule.enabled = azr.isEnabled();
-        zenRule.snoozing = false;
         zenRule.conditionId = azr.getConditionId();
         zenRule.condition = new Condition(azr.getConditionId(), "",
                 active ? Condition.STATE_TRUE : Condition.STATE_FALSE,
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BannerMessagePreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BannerMessagePreferenceTest.java
index f0f53d6..7f4bdae 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BannerMessagePreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BannerMessagePreferenceTest.java
@@ -44,6 +44,7 @@
 import com.android.settingslib.widget.preference.banner.R;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.robolectric.Robolectric;
@@ -53,6 +54,7 @@
 import org.robolectric.shadows.ShadowTouchDelegate;
 import org.robolectric.util.ReflectionHelpers;
 
+@Ignore("b/359066481")
 @RunWith(RobolectricTestRunner.class)
 public class BannerMessagePreferenceTest {
 
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index 03c2a83..65937ea 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -445,7 +445,6 @@
                                 String.valueOf(Global.Wearable.TETHERED_CONFIG_TETHERED),
                                 String.valueOf(Global.Wearable.TETHERED_CONFIG_RESTRICTED)
                         }));
-        VALIDATORS.put(Global.Wearable.PHONE_SWITCHING_SUPPORTED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.Wearable.WEAR_LAUNCHER_UI_MODE, ANY_INTEGER_VALIDATOR);
         VALIDATORS.put(Global.Wearable.WEAR_POWER_ANOMALY_SERVICE_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.Wearable.CONNECTIVITY_KEEP_DATA_ON, BOOLEAN_VALIDATOR);
@@ -457,5 +456,10 @@
         VALIDATORS.put(Global.ADD_USERS_WHEN_LOCKED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.REMOVE_GUEST_ON_EXIT, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.USER_SWITCHER_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Global.Wearable.PHONE_SWITCHING_REQUEST_SOURCE,
+            new InclusiveIntegerRangeValidator(
+                Global.Wearable.PHONE_SWITCHING_REQUEST_SOURCE_NONE,
+                Global.Wearable.PHONE_SWITCHING_REQUEST_SOURCE_COMPANION
+        ));
     }
 }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index 30c4ee5..9ab853f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -690,7 +690,7 @@
         Cursor cursor = getContentResolver().query(Settings.Global.CONTENT_URI, PROJECTION, null,
                 null, null);
         try {
-            return extractRelevantValues(cursor, GlobalSettings.SETTINGS_TO_BACKUP);
+            return extractRelevantValues(cursor, getGlobalSettingsToBackup());
         } finally {
             cursor.close();
         }
@@ -1011,7 +1011,7 @@
                     Settings.System.LEGACY_RESTORE_SETTINGS);
             validators = SystemSettingsValidators.VALIDATORS;
         } else if (contentUri.equals(Settings.Global.CONTENT_URI)) {
-            whitelist = ArrayUtils.concat(String.class, GlobalSettings.SETTINGS_TO_BACKUP,
+            whitelist = ArrayUtils.concat(String.class, getGlobalSettingsToBackup(),
                     Settings.Global.LEGACY_RESTORE_SETTINGS);
             validators = GlobalSettingsValidators.VALIDATORS;
         } else {
@@ -1021,6 +1021,17 @@
         return new SettingsBackupWhitelist(whitelist, validators);
     }
 
+    private String[] getGlobalSettingsToBackup() {
+        // On watches, we don't want to backup or restore 'bluetooth_on' setting, as setting it to
+        // false during restore would cause watch OOBE to fail due to bluetooth connection loss.
+        if (isWatch()) {
+            return ArrayUtils.removeElement(
+                    String.class, GlobalSettings.SETTINGS_TO_BACKUP, Settings.Global.BLUETOOTH_ON);
+        }
+
+        return GlobalSettings.SETTINGS_TO_BACKUP;
+    }
+
     private boolean isBlockedByDynamicList(Set<String> dynamicBlockList, Uri areaUri, String key) {
         String contentKey = Uri.withAppendedPath(areaUri, key).toString();
         return dynamicBlockList.contains(contentKey);
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 f53dec6..b1e6d66 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
@@ -28,6 +28,13 @@
 }
 
 flag {
+    name: "use_new_storage_value"
+    namespace: "core_experiments_team_internal"
+    description: "When enabled, read the new storage value in aconfig codegen, and actually use it."
+    bug: "312235596"
+}
+
+flag {
     name: "load_apex_aconfig_protobufs"
     namespace: "core_experiments_team_internal"
     description: "When enabled, loads aconfig default values in apex flag protobufs into DeviceConfig on boot."
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 8c96484..d39b564 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -629,11 +629,11 @@
                     Settings.Global.Wearable.CUSTOM_COLOR_BACKGROUND,
                     Settings.Global.Wearable.PHONE_SWITCHING_STATUS,
                     Settings.Global.Wearable.TETHER_CONFIG_STATE,
-                    Settings.Global.Wearable.PHONE_SWITCHING_SUPPORTED,
                     Settings.Global.Wearable.WEAR_MEDIA_CONTROLS_PACKAGE,
                     Settings.Global.Wearable.WEAR_MEDIA_SESSIONS_PACKAGE,
                     Settings.Global.Wearable.WEAR_POWER_ANOMALY_SERVICE_ENABLED,
-                    Settings.Global.Wearable.CONNECTIVITY_KEEP_DATA_ON);
+                    Settings.Global.Wearable.CONNECTIVITY_KEEP_DATA_ON,
+                    Settings.Global.Wearable.PHONE_SWITCHING_REQUEST_SOURCE);
 
     private static final Set<String> BACKUP_DENY_LIST_SECURE_SETTINGS =
              newHashSet(
@@ -677,6 +677,7 @@
                  Settings.Secure.CAMERA_LIFT_TRIGGER_ENABLED, // Candidate for backup?
                  Settings.Secure.CARRIER_APPS_HANDLED,
                  Settings.Secure.CMAS_ADDITIONAL_BROADCAST_PKG,
+                 Settings.Secure.COMPAT_UI_EDUCATION_SHOWING,
                  Settings.Secure.COMPLETED_CATEGORY_PREFIX,
                  Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS,
                  Settings.Secure.CONTENT_CAPTURE_ENABLED,
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java
index d4ca4a3..3a391505 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java
@@ -26,6 +26,7 @@
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.ContextWrapper;
+import android.content.pm.PackageManager;
 import android.database.Cursor;
 import android.database.MatrixCursor;
 import android.net.Uri;
@@ -221,6 +222,21 @@
     }
 
     @Test
+    public void testOnRestore_bluetoothOnRestoredOnNonWearablesOnly() {
+        TestSettingsHelper settingsHelper = new TestSettingsHelper(mContext);
+        mAgentUnderTest.mSettingsHelper = settingsHelper;
+
+        restoreGlobalSettings(generateBackupData(Map.of(Settings.Global.BLUETOOTH_ON, "0")));
+
+        var isWatch = mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
+        if (isWatch) {
+            assertFalse(settingsHelper.mWrittenValues.containsKey(Settings.Global.BLUETOOTH_ON));
+        } else {
+            assertEquals("0", settingsHelper.mWrittenValues.get(Settings.Global.BLUETOOTH_ON));
+        }
+    }
+
+    @Test
     @EnableFlags(Flags.FLAG_CONFIGURABLE_FONT_SCALE_DEFAULT)
     public void testFindClosestAllowedFontScale() {
         final String[] availableFontScales = new String[]{"0.5", "0.9", "1.0", "1.1", "1.5"};
@@ -266,6 +282,20 @@
         return buffer.array();
     }
 
+    private void restoreGlobalSettings(byte[] backupData) {
+        mAgentUnderTest.restoreSettings(
+                backupData,
+                /* pos= */ 0,
+                backupData.length,
+                Settings.Global.CONTENT_URI,
+                null,
+                null,
+                null,
+                R.array.restore_blocked_global_settings,
+                /* dynamicBlockList= */ Collections.emptySet(),
+                /* settingsToPreserve= */ Collections.emptySet());
+    }
+
     private byte[] generateUncorruptedHeader() throws IOException {
         try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
             mAgentUnderTest.writeHeader(os);
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index b99aead..6d78705 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -953,35 +953,36 @@
     strict_mode: false,
 }
 
-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",
-    ],
-}
+// 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",
+//     ],
+// }
 
 // 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 9f3c2bf..1d9f469 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -482,6 +482,15 @@
             android:exported="true"
             android:theme="@style/Theme.AppCompat.NoActionBar">
             <intent-filter>
+                <action android:name="com.android.systemui.action.TOUCHPAD_TUTORIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity"
+            android:exported="true"
+            android:theme="@style/Theme.AppCompat.NoActionBar">
+            <intent-filter>
                 <action android:name="com.android.systemui.action.TOUCHPAD_KEYBOARD_TUTORIAL"/>
                 <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/model/A11yMenuShortcut.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/model/A11yMenuShortcut.java
index 66a2fae..c698d18 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/model/A11yMenuShortcut.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/model/A11yMenuShortcut.java
@@ -19,7 +19,6 @@
 
 import com.android.systemui.accessibility.accessibilitymenu.R;
 
-import java.util.HashMap;
 import java.util.Map;
 
 /**
@@ -52,80 +51,80 @@
     private static final int LABEL_TEXT_INDEX = 3;
 
     /** Map stores all shortcut resource IDs that is in matching order of defined shortcut. */
-    private static final Map<ShortcutId, int[]> sShortcutResource = new HashMap<>() {{
-            put(ShortcutId.ID_ASSISTANT_VALUE, new int[] {
+    private static final Map<ShortcutId, int[]> sShortcutResource = Map.ofEntries(
+            Map.entry(ShortcutId.ID_ASSISTANT_VALUE, new int[] {
                     R.drawable.ic_logo_a11y_assistant_24dp,
                     R.color.assistant_color,
                     R.string.assistant_utterance,
                     R.string.assistant_label,
-            });
-            put(ShortcutId.ID_A11YSETTING_VALUE, new int[] {
+            }),
+            Map.entry(ShortcutId.ID_A11YSETTING_VALUE, new int[] {
                     R.drawable.ic_logo_a11y_settings_24dp,
                     R.color.a11y_settings_color,
                     R.string.a11y_settings_label,
                     R.string.a11y_settings_label,
-            });
-            put(ShortcutId.ID_POWER_VALUE, new int[] {
+            }),
+            Map.entry(ShortcutId.ID_POWER_VALUE, new int[] {
                     R.drawable.ic_logo_a11y_power_24dp,
                     R.color.power_color,
                     R.string.power_utterance,
                     R.string.power_label,
-            });
-            put(ShortcutId.ID_RECENT_VALUE, new int[] {
+            }),
+            Map.entry(ShortcutId.ID_RECENT_VALUE, new int[] {
                     R.drawable.ic_logo_a11y_recent_apps_24dp,
                     R.color.recent_apps_color,
                     R.string.recent_apps_label,
                     R.string.recent_apps_label,
-            });
-            put(ShortcutId.ID_LOCKSCREEN_VALUE, new int[] {
+            }),
+            Map.entry(ShortcutId.ID_LOCKSCREEN_VALUE, new int[] {
                     R.drawable.ic_logo_a11y_lock_24dp,
                     R.color.lockscreen_color,
                     R.string.lockscreen_label,
                     R.string.lockscreen_label,
-            });
-            put(ShortcutId.ID_QUICKSETTING_VALUE, new int[] {
+            }),
+            Map.entry(ShortcutId.ID_QUICKSETTING_VALUE, new int[] {
                     R.drawable.ic_logo_a11y_quick_settings_24dp,
                     R.color.quick_settings_color,
                     R.string.quick_settings_label,
                     R.string.quick_settings_label,
-            });
-            put(ShortcutId.ID_NOTIFICATION_VALUE, new int[] {
+            }),
+            Map.entry(ShortcutId.ID_NOTIFICATION_VALUE, new int[] {
                     R.drawable.ic_logo_a11y_notifications_24dp,
                     R.color.notifications_color,
                     R.string.notifications_label,
                     R.string.notifications_label,
-            });
-            put(ShortcutId.ID_SCREENSHOT_VALUE, new int[] {
+            }),
+            Map.entry(ShortcutId.ID_SCREENSHOT_VALUE, new int[] {
                     R.drawable.ic_logo_a11y_screenshot_24dp,
                     R.color.screenshot_color,
                     R.string.screenshot_utterance,
                     R.string.screenshot_label,
-            });
-            put(ShortcutId.ID_BRIGHTNESS_UP_VALUE, new int[] {
+            }),
+            Map.entry(ShortcutId.ID_BRIGHTNESS_UP_VALUE, new int[] {
                     R.drawable.ic_logo_a11y_brightness_up_24dp,
                     R.color.brightness_color,
                     R.string.brightness_up_label,
                     R.string.brightness_up_label,
-            });
-            put(ShortcutId.ID_BRIGHTNESS_DOWN_VALUE, new int[] {
+            }),
+            Map.entry(ShortcutId.ID_BRIGHTNESS_DOWN_VALUE, new int[] {
                     R.drawable.ic_logo_a11y_brightness_down_24dp,
                     R.color.brightness_color,
                     R.string.brightness_down_label,
                     R.string.brightness_down_label,
-            });
-            put(ShortcutId.ID_VOLUME_UP_VALUE, new int[] {
+            }),
+            Map.entry(ShortcutId.ID_VOLUME_UP_VALUE, new int[] {
                     R.drawable.ic_logo_a11y_volume_up_24dp,
                     R.color.volume_color,
                     R.string.volume_up_label,
                     R.string.volume_up_label,
-            });
-            put(ShortcutId.ID_VOLUME_DOWN_VALUE, new int[] {
+            }),
+            Map.entry(ShortcutId.ID_VOLUME_DOWN_VALUE, new int[] {
                     R.drawable.ic_logo_a11y_volume_down_24dp,
                     R.color.volume_color,
                     R.string.volume_down_label,
                     R.string.volume_down_label,
-            });
-        }};
+            })
+    );
 
     /** Shortcut id used to identify. */
     private int mShortcutId = ShortcutId.UNSPECIFIED_ID_VALUE.ordinal();
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 2e036e6..6bea30f 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
@@ -348,7 +348,17 @@
 
     /** Toggles a11y menu layout visibility. */
     public void toggleVisibility() {
-        mLayout.setVisibility((mLayout.getVisibility() == View.VISIBLE) ? View.GONE : View.VISIBLE);
+        if (mLayout.getVisibility() == View.VISIBLE) {
+            mLayout.setVisibility(View.GONE);
+        } else {
+            if (Flags.hideRestrictedActions()) {
+                // Reconfigure the shortcut list in case the set of restricted actions has changed.
+                mA11yMenuViewPager.configureViewPagerAndFooter(
+                        mLayout, createShortcutList(), getPageIndex());
+                updateViewLayout();
+            }
+            mLayout.setVisibility(View.VISIBLE);
+        }
     }
 
     /** Shows hint text on a minimal Snackbar-like text view. */
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
index d16617f..4ab771b 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
@@ -542,8 +542,6 @@
         final Context context = sInstrumentation.getTargetContext();
         final UserManager userManager = context.getSystemService(UserManager.class);
         userManager.setUserRestriction(restriction, isRestricted);
-        // Re-enable the service for the restriction to take effect.
-        enableA11yMenuService(context);
     }
 
     private static void unlockSignal() throws IOException {
diff --git a/packages/SystemUI/aconfig/biometrics_framework.aconfig b/packages/SystemUI/aconfig/biometrics_framework.aconfig
index e81d5d5..95e4b59 100644
--- a/packages/SystemUI/aconfig/biometrics_framework.aconfig
+++ b/packages/SystemUI/aconfig/biometrics_framework.aconfig
@@ -3,9 +3,3 @@
 
 # NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors.
 
-flag {
-    name: "constraint_bp"
-    namespace: "biometrics_framework"
-    description: "Refactors Biometric Prompt to use a ConstraintLayout"
-    bug: "288175072"
-}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 23c6098..cdbac33 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -156,17 +156,6 @@
 }
 
 flag {
-   name: "pss_app_selector_abrupt_exit_fix"
-   namespace: "systemui"
-   description: "Fixes the app selector abruptly disappearing without an animation, when the"
-        "selected task is the foreground task."
-   bug: "314385883"
-   metadata {
-        purpose: PURPOSE_BUGFIX
-   }
-}
-
-flag {
    name: "pss_app_selector_recents_split_screen"
    namespace: "systemui"
    description: "Allows recent apps selected for partial screenshare to be launched in split screen mode"
@@ -388,6 +377,16 @@
 }
 
 flag {
+    name: "status_bar_swipe_over_chip"
+    namespace: "systemui"
+    description: "Allow users to swipe over the status bar chip to open the shade"
+    bug: "185897191"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "compose_bouncer"
     namespace: "systemui"
     description: "Use the new compose bouncer in SystemUI"
@@ -475,18 +474,6 @@
 }
 
 flag {
-   name: "centralized_status_bar_height_fix"
-   namespace: "systemui"
-   description: "Refactors shade header and keyguard status bar to read status bar dimens from a"
-        " central place, instead of reading resources directly. This is to take into account display"
-        " cutouts and other special cases. "
-   bug: "317016114"
-   metadata {
-        purpose: PURPOSE_BUGFIX
-   }
-}
-
-flag {
   name: "enable_layout_tracing"
   namespace: "systemui"
   description: "Enables detailed traversal slices during measure and layout in perfetto traces"
@@ -516,6 +503,16 @@
 }
 
 flag {
+    name: "status_bar_switch_to_spn_from_data_spn"
+    namespace: "systemui"
+    description: "Fix usage of the SPN broadcast extras"
+    bug: "350812372"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "haptic_volume_slider"
     namespace: "systemui"
     description: "Adds haptic feedback to the volume slider."
@@ -1209,6 +1206,17 @@
 }
 
 flag {
+  name: "hubmode_fullscreen_vertical_swipe_fix"
+  namespace: "systemui"
+  description: "Bug fix that enables fullscreen vertical swiping in hub mode to bring up and down the bouncer and shade"
+  bug: "340177049"
+  metadata {
+      purpose: PURPOSE_BUGFIX
+  }
+}
+
+
+flag {
    namespace: "systemui"
    name: "remove_update_listener_in_qs_icon_view_impl"
    description: "Remove update listeners in QsIconViewImpl class to avoid memory leak."
@@ -1249,6 +1257,16 @@
 }
 
 flag {
+   name: "relock_with_power_button_immediately"
+   namespace: "systemui"
+   description: "UDFPS unlock followed by immediate power button push should relock"
+   bug: "343327511"
+   metadata {
+        purpose: PURPOSE_BUGFIX
+   }
+}
+
+flag {
    name: "lockscreen_preview_renderer_create_on_main_thread"
    namespace: "systemui"
    description: "Force preview renderer to be created on the main thread"
@@ -1275,6 +1293,13 @@
 }
 
 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."
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterContentObserverSyncViaSettingsProxyDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterContentObserverSyncViaSettingsProxyDetector.kt
new file mode 100644
index 0000000..d8c7c06
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterContentObserverSyncViaSettingsProxyDetector.kt
@@ -0,0 +1,94 @@
+/*
+ * 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.internal.systemui.lint
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiMethod
+import org.jetbrains.uast.UCallExpression
+
+/**
+ * Checks if the synchronous APIs like registerContentObserverSync/unregisterContentObserverSync are
+ * invoked for SettingsProxy or it's sub-classes, and raise a warning notifying the caller to use
+ * the asynchronous/suspend APIs instead.
+ */
+@Suppress("UnstableApiUsage")
+class RegisterContentObserverSyncViaSettingsProxyDetector : Detector(), SourceCodeScanner {
+
+    override fun getApplicableMethodNames(): List<String> {
+        return SYNC_METHOD_LIST
+    }
+
+    override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
+
+        val evaluator = context.evaluator
+        if (evaluator.isMemberInSubClassOf(method, SETTINGS_PROXY_CLASS)) {
+            context.report(
+                issue = SYNC_WARNING,
+                location = context.getNameLocation(node),
+                message =
+                    "`Avoid using ${method.name}()` if calling the API is not " +
+                        "required on the main thread. Instead use an appropriate async interface " +
+                        "API call for eg. `registerContentObserver()` or " +
+                        "`registerContentObserverAsync()`."
+            )
+        }
+    }
+
+    companion object {
+        val SYNC_WARNING: Issue =
+            Issue.create(
+                id = "RegisterContentObserverSyncWarning",
+                briefDescription =
+                    "Synchronous content observer registration API called " +
+                        "instead of the async APIs.`",
+                // lint trims indents and converts \ to line continuations
+                explanation =
+                    """
+                        ContentObserver registration/de-registration done via \
+                        `SettingsProxy.registerContentObserverSync` will block the main thread \
+                        and may cause missed frames. Instead, use \
+                        `SettingsProxy.registerContentObserver()` or \
+                        `SettingsProxy.registerContentObserverAsync()`. These APIs will ensure \
+                        that the registrations/de-registrations happen sequentially on a
+                        background worker thread.""",
+                category = Category.PERFORMANCE,
+                priority = 8,
+                severity = Severity.WARNING,
+                implementation =
+                    Implementation(
+                        RegisterContentObserverSyncViaSettingsProxyDetector::class.java,
+                        Scope.JAVA_FILE_SCOPE
+                    )
+            )
+
+        private val SYNC_METHOD_LIST =
+            listOf(
+                "registerContentObserverSync",
+                "unregisterContentObserverSync",
+                "registerContentObserverForUserSync"
+            )
+
+        private val SETTINGS_PROXY_CLASS = "com.android.systemui.util.settings.SettingsProxy"
+    }
+}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterContentObserverViaContentResolverDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterContentObserverViaContentResolverDetector.kt
new file mode 100644
index 0000000..8f5cdbf
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterContentObserverViaContentResolverDetector.kt
@@ -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.internal.systemui.lint
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiMethod
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UClass
+import org.jetbrains.uast.getParentOfType
+
+/**
+ * Checks if registerContentObserver/registerContentObserverAsUser/unregisterContentObserver is
+ * called on a ContentResolver (or subclasses), and directs the caller to using
+ * com.android.systemui.util.settings.SettingsProxy or its sub-classes.
+ */
+@Suppress("UnstableApiUsage")
+class RegisterContentObserverViaContentResolverDetector : Detector(), SourceCodeScanner {
+
+    override fun getApplicableMethodNames(): List<String> {
+        return CONTENT_RESOLVER_METHOD_LIST
+    }
+
+    override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
+        val classQualifiedName = node.getParentOfType(UClass::class.java)?.qualifiedName
+        if (classQualifiedName in CLASSNAME_ALLOWLIST) {
+            // Don't warn for class we want the developers to use.
+            return
+        }
+
+        val evaluator = context.evaluator
+        if (evaluator.isMemberInSubClassOf(method, "android.content.ContentResolver")) {
+            context.report(
+                issue = CONTENT_RESOLVER_ERROR,
+                location = context.getNameLocation(node),
+                message =
+                    "`ContentResolver.${method.name}()` should be replaced with " +
+                        "an appropriate interface API call, for eg. " +
+                        "`<SettingsProxy>/<UserSettingsProxy>.${method.name}()`"
+            )
+        }
+    }
+
+    companion object {
+        @JvmField
+        val CONTENT_RESOLVER_ERROR: Issue =
+            Issue.create(
+                id = "RegisterContentObserverViaContentResolver",
+                briefDescription =
+                    "Content observer registration done via `ContentResolver`" +
+                        "instead of `SettingsProxy or child interfaces.`",
+                // lint trims indents and converts \ to line continuations
+                explanation =
+                    """
+                        Use registerContentObserver/unregisterContentObserver methods in \
+                        `SettingsProxy`, `UserSettingsProxy` or `GlobalSettings` class instead of \
+                        using `ContentResolver.registerContentObserver` or \
+                        `ContentResolver.unregisterContentObserver`.""",
+                category = Category.PERFORMANCE,
+                priority = 10,
+                severity = Severity.ERROR,
+                implementation =
+                    Implementation(
+                        RegisterContentObserverViaContentResolverDetector::class.java,
+                        Scope.JAVA_FILE_SCOPE
+                    )
+            )
+
+        private val CLASSNAME_ALLOWLIST =
+            listOf(
+                "com.android.systemui.util.settings.SettingsProxy",
+                "com.android.systemui.util.settings.UserSettingsProxy",
+                "com.android.systemui.util.settings.GlobalSettings",
+                "com.android.systemui.util.settings.SecureSettings",
+                "com.android.systemui.util.settings.SystemSettings"
+            )
+
+        private val CONTENT_RESOLVER_METHOD_LIST =
+            listOf(
+                "registerContentObserver",
+                "registerContentObserverAsUser",
+                "unregisterContentObserver"
+            )
+    }
+}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
index 73ac6cc..a1f4f55 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
@@ -46,10 +46,13 @@
                 DemotingTestWithoutBugDetector.ISSUE,
                 TestFunctionNameViolationDetector.ISSUE,
                 MissingApacheLicenseDetector.ISSUE,
+                RegisterContentObserverSyncViaSettingsProxyDetector.SYNC_WARNING,
+                RegisterContentObserverViaContentResolverDetector.CONTENT_RESOLVER_ERROR
             )
 
     override val api: Int
         get() = CURRENT_API
+
     override val minApi: Int
         get() = 8
 
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterContentObserverSyncViaSettingsProxyDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterContentObserverSyncViaSettingsProxyDetectorTest.kt
new file mode 100644
index 0000000..57347d3
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterContentObserverSyncViaSettingsProxyDetectorTest.kt
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+
+/** Test class for [RegisterContentObserverSyncViaSettingsProxyDetector]. */
+class RegisterContentObserverSyncViaSettingsProxyDetectorTest : SystemUILintDetectorTest() {
+    override fun getDetector(): Detector = RegisterContentObserverSyncViaSettingsProxyDetector()
+
+    override fun getIssues(): List<Issue> =
+        listOf(RegisterContentObserverSyncViaSettingsProxyDetector.SYNC_WARNING)
+
+    @Test
+    fun testRegisterContentObserverSync_throwError() {
+        lint()
+            .files(
+                TestFiles.java(
+                        """
+                    package test.pkg;
+                    import com.android.systemui.util.settings.SecureSettings;
+                    public class TestClass {
+                        public void register(SecureSettings secureSettings) {
+                          secureSettings.
+                            registerContentObserverSync(Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED),
+                                false, mSettingObserver);
+                        }
+                    }
+                """
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(RegisterContentObserverSyncViaSettingsProxyDetector.SYNC_WARNING)
+            .run()
+            .expect(
+                """
+                src/test/pkg/TestClass.java:6: Warning: Avoid using registerContentObserverSync() if calling the API is not required on the main thread. Instead use an appropriate async interface API call for eg. registerContentObserver() or registerContentObserverAsync(). [RegisterContentObserverSyncWarning]
+        registerContentObserverSync(Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED),
+        ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+0 errors, 1 warnings
+            """
+                    .trimIndent()
+            )
+    }
+
+    @Test
+    fun testRegisterContentObserverForUserSync_throwError() {
+        lint()
+            .files(
+                TestFiles.java(
+                        """
+                    package test.pkg;
+                    import com.android.systemui.util.settings.SecureSettings;
+                    public class TestClass {
+                        public void register(SecureSettings secureSettings) {
+                          secureSettings.
+                            registerContentObserverForUserSync(Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED),
+                                false, mSettingObserver);
+                        }
+                    }
+                """
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(RegisterContentObserverSyncViaSettingsProxyDetector.SYNC_WARNING)
+            .run()
+            .expect(
+                """
+                src/test/pkg/TestClass.java:6: Warning: Avoid using registerContentObserverForUserSync() if calling the API is not required on the main thread. Instead use an appropriate async interface API call for eg. registerContentObserver() or registerContentObserverAsync(). [RegisterContentObserverSyncWarning]
+        registerContentObserverForUserSync(Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED),
+        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+0 errors, 1 warnings
+            """
+                    .trimIndent()
+            )
+    }
+
+    @Test
+    fun testSuppressRegisterContentObserverSync() {
+        lint()
+            .files(
+                TestFiles.java(
+                        """
+                    package test.pkg;
+                    import com.android.systemui.util.settings.SecureSettings;
+                    public class TestClass {
+                        @SuppressWarnings("RegisterContentObserverSyncWarning")
+                        public void register(SecureSettings secureSettings) {
+                          secureSettings.
+                            registerContentObserverForUserSync(Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED),
+                                false, mSettingObserver);
+                        }
+                    }
+                """
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(RegisterContentObserverSyncViaSettingsProxyDetector.SYNC_WARNING)
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun testNoopIfNoCall() {
+        lint()
+            .files(
+                TestFiles.java(
+                        """
+                    package test.pkg;
+                    import com.android.systemui.util.settings.SecureSettings;
+                    public class TestClass {
+                        public void register(SecureSettings secureSettings) {
+                        }
+                    }
+                """
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(RegisterContentObserverSyncViaSettingsProxyDetector.SYNC_WARNING)
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun testUnRegisterContentObserverSync_throwError() {
+        lint()
+            .files(
+                TestFiles.java(
+                        """
+                    package test.pkg;
+                    import com.android.systemui.util.settings.SecureSettings;
+                    public class TestClass {
+                        public void register(SecureSettings secureSettings) {
+                          secureSettings.
+                            unregisterContentObserverSync(mSettingObserver);
+                        }
+                    }
+                """
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(RegisterContentObserverSyncViaSettingsProxyDetector.SYNC_WARNING)
+            .run()
+            .expect(
+                """
+        src/test/pkg/TestClass.java:6: Warning: Avoid using unregisterContentObserverSync() if calling the API is not required on the main thread. Instead use an appropriate async interface API call for eg. registerContentObserver() or registerContentObserverAsync(). [RegisterContentObserverSyncWarning]
+        unregisterContentObserverSync(mSettingObserver);
+        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+0 errors, 1 warnings
+            """
+                    .trimIndent()
+            )
+    }
+
+    private companion object {
+        private val SETTINGS_PROXY_STUB =
+            kotlin(
+                    """
+            package com.android.systemui.util.settings
+            interface SettingsProxy {
+                fun registerContentObserverSync() {}
+                fun unregisterContentObserverSync() {}
+            }
+            """
+                )
+                .indented()
+
+        private val USER_SETTINGS_PROXY_STUB =
+            kotlin(
+                    """
+            package com.android.systemui.util.settings
+            interface UserSettingsProxy : SettingsProxy {
+                fun registerContentObserverForUserSync() {}
+            }
+            """
+                )
+                .indented()
+
+        private val SECURE_SETTINGS_STUB =
+            kotlin(
+                    """
+            package com.android.systemui.util.settings
+            interface SecureSettings : UserSettingsProxy {}
+            """
+                )
+                .indented()
+    }
+
+    private val stubs = arrayOf(SETTINGS_PROXY_STUB, USER_SETTINGS_PROXY_STUB, SECURE_SETTINGS_STUB)
+}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterContentObserverViaContentResolverDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterContentObserverViaContentResolverDetectorTest.kt
new file mode 100644
index 0000000..1d33bce
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterContentObserverViaContentResolverDetectorTest.kt
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+
+class RegisterContentObserverViaContentResolverDetectorTest : SystemUILintDetectorTest() {
+
+    override fun getDetector(): Detector = RegisterContentObserverViaContentResolverDetector()
+
+    override fun getIssues(): List<Issue> =
+        listOf(RegisterContentObserverViaContentResolverDetector.CONTENT_RESOLVER_ERROR)
+
+    @Test
+    fun testRegisterContentObserver_throwError() {
+        lint()
+            .files(
+                TestFiles.java(
+                        """
+                    package test.pkg;
+                    import android.content.Context;
+
+                    public class TestClass {
+                        public void register(Context context) {
+                          context.getContentResolver().
+                            registerContentObserver(Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED),
+                                false, mSettingObserver);
+                        }
+                    }
+                """
+                    )
+                    .indented(),
+                *androidStubs
+            )
+            .issues(RegisterContentObserverViaContentResolverDetector.CONTENT_RESOLVER_ERROR)
+            .run()
+            .expect(
+                """
+                src/test/pkg/TestClass.java:7: Error: ContentResolver.registerContentObserver() should be replaced with an appropriate interface API call, for eg. <SettingsProxy>/<UserSettingsProxy>.registerContentObserver() [RegisterContentObserverViaContentResolver]
+                        registerContentObserver(Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED),
+                        ~~~~~~~~~~~~~~~~~~~~~~~
+                1 errors, 0 warnings
+            """
+                    .trimIndent()
+            )
+    }
+
+    @Test
+    fun testRegisterContentObserverForUser_throwError() {
+        lint()
+            .files(
+                TestFiles.java(
+                        """
+                    package test.pkg;
+                    import android.content.Context;
+
+                    public class TestClass {
+                        public void register(Context context) {
+                          context.getContentResolver().
+                            registerContentObserverAsUser(Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED),
+                                false, mSettingObserver);
+                        }
+                    }
+                """
+                    )
+                    .indented(),
+                *androidStubs
+            )
+            .issues(RegisterContentObserverViaContentResolverDetector.CONTENT_RESOLVER_ERROR)
+            .run()
+            .expect(
+                """
+                src/test/pkg/TestClass.java:7: Error: ContentResolver.registerContentObserverAsUser() should be replaced with an appropriate interface API call, for eg. <SettingsProxy>/<UserSettingsProxy>.registerContentObserverAsUser() [RegisterContentObserverViaContentResolver]
+        registerContentObserverAsUser(Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED),
+        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+1 errors, 0 warnings
+            """
+                    .trimIndent()
+            )
+    }
+
+    @Test
+    fun testSuppressRegisterContentObserver() {
+        lint()
+            .files(
+                TestFiles.java(
+                        """
+                    package test.pkg;
+                    import android.content.Context;
+
+                    public class TestClass {
+                        @SuppressWarnings("RegisterContentObserverViaContentResolver")
+                        public void register(Context context) {
+                          context.getContentResolver().
+                            registerContentObserver(Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED),
+                                false, mSettingObserver);
+                        }
+                    }
+                """
+                    )
+                    .indented(),
+                *androidStubs
+            )
+            .issues(RegisterContentObserverViaContentResolverDetector.CONTENT_RESOLVER_ERROR)
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun testRegisterContentObserverInSettingsProxy_allowed() {
+        lint()
+            .files(
+                TestFiles.java(
+                        """
+                    package com.android.systemui.util.settings;
+                    import android.content.Context;
+
+                    public class SettingsProxy {
+                        public void register(Context context) {
+                          context.getContentResolver().
+                            registerContentObserver(Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED),
+                                false, mSettingObserver);
+                        }
+                    }
+                """
+                    )
+                    .indented(),
+                *androidStubs
+            )
+            .issues(RegisterContentObserverViaContentResolverDetector.CONTENT_RESOLVER_ERROR)
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun testNoopIfNoCall() {
+        lint()
+            .files(
+                TestFiles.java(
+                        """
+                    package test.pkg;
+                    import android.content.Context;
+
+                    public class SettingsProxy {
+                        public void register(Context context) {
+                        }
+                    }
+                """
+                    )
+                    .indented(),
+                *androidStubs
+            )
+            .issues(RegisterContentObserverViaContentResolverDetector.CONTENT_RESOLVER_ERROR)
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun testUnRegisterContentObserver_throwError() {
+        lint()
+            .files(
+                TestFiles.java(
+                        """
+                    package test.pkg;
+                    import android.content.Context;
+
+                    public class TestClass {
+                        public void register(Context context) {
+                          context.getContentResolver().
+                            unregisterContentObserver(mSettingObserver);
+                        }
+                    }
+                """
+                    )
+                    .indented(),
+                *androidStubs
+            )
+            .issues(RegisterContentObserverViaContentResolverDetector.CONTENT_RESOLVER_ERROR)
+            .run()
+            .expect(
+                """
+                src/test/pkg/TestClass.java:7: Error: ContentResolver.unregisterContentObserver() should be replaced with an appropriate interface API call, for eg. <SettingsProxy>/<UserSettingsProxy>.unregisterContentObserver() [RegisterContentObserverViaContentResolver]
+                        unregisterContentObserver(mSettingObserver);
+                        ~~~~~~~~~~~~~~~~~~~~~~~~~
+                1 errors, 0 warnings
+            """
+                    .trimIndent()
+            )
+    }
+}
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 bd5b795a..72965fb 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
@@ -66,11 +66,11 @@
 
         @Provides
         fun providesLockscreenContent(
-            viewModel: LockscreenContentViewModel,
+            viewModelFactory: LockscreenContentViewModel.Factory,
             blueprints: Set<@JvmSuppressWildcards ComposableLockscreenSceneBlueprint>,
             clockInteractor: KeyguardClockInteractor,
         ): LockscreenContent {
-            return LockscreenContent(viewModel, blueprints, clockInteractor)
+            return LockscreenContent(viewModelFactory, blueprints, clockInteractor)
         }
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
index 8c38253..6fca178 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.communal.smartspace.SmartspaceInteractionHandler
 import com.android.systemui.communal.ui.compose.section.AmbientStatusBarSection
 import com.android.systemui.communal.ui.compose.section.CommunalPopupSection
+import com.android.systemui.communal.ui.view.layout.sections.CommunalAppWidgetSection
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.keyguard.ui.composable.blueprint.BlueprintAlignmentLines
 import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
@@ -46,6 +47,7 @@
     private val bottomAreaSection: BottomAreaSection,
     private val ambientStatusBarSection: AmbientStatusBarSection,
     private val communalPopupSection: CommunalPopupSection,
+    private val widgetSection: CommunalAppWidgetSection,
 ) {
 
     @Composable
@@ -63,6 +65,7 @@
                             viewModel = viewModel,
                             interactionHandler = interactionHandler,
                             dialogFactory = dialogFactory,
+                            widgetSection = widgetSection,
                             modifier = Modifier.element(Communal.Elements.Grid)
                         )
                     }
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 69f1174..b93b049 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
@@ -19,10 +19,7 @@
 import android.content.Context
 import android.content.res.Configuration
 import android.graphics.drawable.Icon
-import android.os.Bundle
 import android.util.SizeF
-import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
-import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
 import android.widget.FrameLayout
 import android.widget.RemoteViews
 import androidx.annotation.VisibleForTesting
@@ -105,7 +102,6 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.runtime.snapshotFlow
 import androidx.compose.ui.Alignment
-import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.drawBehind
 import androidx.compose.ui.geometry.CornerRadius
@@ -160,6 +156,7 @@
 import com.android.compose.theme.LocalAndroidColorScheme
 import com.android.compose.ui.graphics.painter.rememberDrawablePainter
 import com.android.internal.R.dimen.system_app_widget_background_radius
+import com.android.systemui.Flags
 import com.android.systemui.Flags.communalTimerFlickerFix
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.communal.shared.model.CommunalContentSize
@@ -168,6 +165,7 @@
 import com.android.systemui.communal.ui.compose.extensions.detectLongPressGesture
 import com.android.systemui.communal.ui.compose.extensions.firstItemAtOffset
 import com.android.systemui.communal.ui.compose.extensions.observeTaps
+import com.android.systemui.communal.ui.view.layout.sections.CommunalAppWidgetSection
 import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
 import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
@@ -179,11 +177,12 @@
 import com.android.systemui.statusbar.phone.SystemUIDialogFactory
 import kotlinx.coroutines.launch
 
-@OptIn(ExperimentalComposeUiApi::class, ExperimentalMaterial3Api::class)
+@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun CommunalHub(
     modifier: Modifier = Modifier,
     viewModel: BaseCommunalViewModel,
+    widgetSection: CommunalAppWidgetSection,
     interactionHandler: RemoteViews.InteractionHandler? = null,
     dialogFactory: SystemUIDialogFactory? = null,
     widgetConfigurator: WidgetConfigurator? = null,
@@ -269,7 +268,7 @@
                     }
                 }
                 // Nested scroll for full screen swipe to get to shade and bouncer
-                .thenIf(!viewModel.isEditMode) {
+                .thenIf(!viewModel.isEditMode && Flags.hubmodeFullscreenVerticalSwipeFix()) {
                     Modifier.nestedScroll(nestedScrollConnection).pointerInput(viewModel) {
                         awaitPointerEventScope {
                             while (true) {
@@ -382,6 +381,7 @@
                             selectedKey = selectedKey,
                             widgetConfigurator = widgetConfigurator,
                             interactionHandler = interactionHandler,
+                            widgetSection = widgetSection,
                         )
                     }
                 }
@@ -631,6 +631,7 @@
     updateDragPositionForRemove: (offset: Offset) -> Boolean,
     widgetConfigurator: WidgetConfigurator?,
     interactionHandler: RemoteViews.InteractionHandler?,
+    widgetSection: CommunalAppWidgetSection,
 ) {
     var gridModifier =
         Modifier.align(Alignment.TopStart).onGloballyPositioned { setGridCoordinates(it) }
@@ -718,18 +719,20 @@
                         index = index,
                         contentListState = contentListState,
                         interactionHandler = interactionHandler,
+                        widgetSection = widgetSection,
                     )
                 }
             } else {
                 CommunalContent(
-                    modifier = cardModifier.animateItem(),
                     model = list[index],
                     viewModel = viewModel,
                     size = size,
                     selected = false,
+                    modifier = cardModifier.animateItem(),
                     index = index,
                     contentListState = contentListState,
                     interactionHandler = interactionHandler,
+                    widgetSection = widgetSection,
                 )
             }
         }
@@ -971,6 +974,7 @@
     index: Int,
     contentListState: ContentListState,
     interactionHandler: RemoteViews.InteractionHandler?,
+    widgetSection: CommunalAppWidgetSection,
 ) {
     when (model) {
         is CommunalContentModel.WidgetContent.Widget ->
@@ -982,7 +986,8 @@
                 widgetConfigurator,
                 modifier,
                 index,
-                contentListState
+                contentListState,
+                widgetSection,
             )
         is CommunalContentModel.WidgetPlaceholder -> HighlightedItem(modifier)
         is CommunalContentModel.WidgetContent.DisabledWidget ->
@@ -1115,9 +1120,9 @@
     modifier: Modifier = Modifier,
     index: Int,
     contentListState: ContentListState,
+    widgetSection: CommunalAppWidgetSection,
 ) {
     val context = LocalContext.current
-    val isFocusable by viewModel.isFocusable.collectAsStateWithLifecycle(initialValue = false)
     val accessibilityLabel =
         remember(model, context) {
             model.providerInfo.loadLabel(context.packageManager).toString().trim()
@@ -1204,36 +1209,14 @@
                     }
                 }
     ) {
-        AndroidView(
-            modifier = Modifier.fillMaxSize().allowGestures(allowed = !viewModel.isEditMode),
-            factory = { context ->
-                model.appWidgetHost
-                    .createViewForCommunal(context, model.appWidgetId, model.providerInfo)
-                    .apply {
-                        updateAppWidgetSize(
-                            /* newOptions = */ Bundle(),
-                            /* minWidth = */ size.width.toInt(),
-                            /* minHeight = */ size.height.toInt(),
-                            /* maxWidth = */ size.width.toInt(),
-                            /* maxHeight = */ size.height.toInt(),
-                            /* ignorePadding = */ true
-                        )
-                        accessibilityDelegate = viewModel.widgetAccessibilityDelegate
-                    }
-            },
-            update = {
-                it.apply {
-                    importantForAccessibility =
-                        if (isFocusable) {
-                            IMPORTANT_FOR_ACCESSIBILITY_AUTO
-                        } else {
-                            IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
-                        }
-                }
-            },
-            // For reusing composition in lazy lists.
-            onReset = {},
-        )
+        with(widgetSection) {
+            Widget(
+                viewModel = viewModel,
+                model = model,
+                size = size,
+                modifier = Modifier.fillMaxSize().allowGestures(allowed = !viewModel.isEditMode),
+            )
+        }
         if (
             viewModel is CommunalEditModeViewModel &&
                 model.reconfigurable &&
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 5886d7d..6750e41 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
@@ -23,6 +23,7 @@
 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.communal.ui.view.layout.sections.CommunalAppWidgetSection
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.communal.widgets.WidgetInteractionHandler
 import com.android.systemui.dagger.SysUISingleton
@@ -42,6 +43,7 @@
     private val viewModel: CommunalViewModel,
     private val dialogFactory: SystemUIDialogFactory,
     private val interactionHandler: WidgetInteractionHandler,
+    private val widgetSection: CommunalAppWidgetSection,
 ) : ComposableScene {
     override val key = Scenes.Communal
 
@@ -55,6 +57,12 @@
 
     @Composable
     override fun SceneScope.Content(modifier: Modifier) {
-        CommunalHub(modifier, viewModel, interactionHandler, dialogFactory)
+        CommunalHub(
+            modifier = modifier,
+            viewModel = viewModel,
+            interactionHandler = interactionHandler,
+            widgetSection = widgetSection,
+            dialogFactory = dialogFactory,
+        )
     }
 }
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
new file mode 100644
index 0000000..04bcc36
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/AlternateBouncer.kt
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.ui.composable
+
+import androidx.activity.compose.BackHandler
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.Crossfade
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+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.input.pointer.pointerInput
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.compose.modifiers.background
+import com.android.compose.modifiers.height
+import com.android.compose.modifiers.width
+import com.android.systemui.deviceentry.shared.model.BiometricMessage
+import com.android.systemui.deviceentry.ui.binder.UdfpsAccessibilityOverlayBinder
+import com.android.systemui.deviceentry.ui.view.UdfpsAccessibilityOverlay
+import com.android.systemui.deviceentry.ui.viewmodel.AlternateBouncerUdfpsAccessibilityOverlayViewModel
+import com.android.systemui.keyguard.ui.binder.AlternateBouncerUdfpsViewBinder
+import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerMessageAreaViewModel
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel
+import com.android.systemui.res.R
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+@Composable
+fun AlternateBouncer(
+    alternateBouncerDependencies: AlternateBouncerDependencies,
+    modifier: Modifier = Modifier,
+) {
+
+    val isVisible by
+        alternateBouncerDependencies.viewModel.isVisible.collectAsStateWithLifecycle(
+            initialValue = false
+        )
+
+    val udfpsIconLocation by
+        alternateBouncerDependencies.udfpsIconViewModel.iconLocation.collectAsStateWithLifecycle(
+            initialValue = null
+        )
+
+    // TODO (b/353955910): back handling doesn't work
+    BackHandler { alternateBouncerDependencies.viewModel.onBackRequested() }
+
+    AnimatedVisibility(
+        visible = isVisible,
+        enter = fadeIn(),
+        exit = fadeOut(),
+        modifier = modifier,
+    ) {
+        Box(
+            contentAlignment = Alignment.TopCenter,
+            modifier =
+                Modifier.background(color = Colors.AlternateBouncerBackgroundColor, alpha = { 1f })
+                    .pointerInput(Unit) {
+                        detectTapGestures(
+                            onTap = { alternateBouncerDependencies.viewModel.onTapped() }
+                        )
+                    },
+        ) {
+            StatusMessage(
+                viewModel = alternateBouncerDependencies.messageAreaViewModel,
+            )
+        }
+
+        udfpsIconLocation?.let { udfpsLocation ->
+            Box {
+                DeviceEntryIcon(
+                    viewModel = alternateBouncerDependencies.udfpsIconViewModel,
+                    modifier =
+                        Modifier.width { udfpsLocation.width }
+                            .height { udfpsLocation.height }
+                            .fillMaxHeight()
+                            .offset {
+                                IntOffset(
+                                    x = udfpsLocation.left,
+                                    y = udfpsLocation.top,
+                                )
+                            },
+                )
+            }
+
+            UdfpsA11yOverlay(
+                viewModel = alternateBouncerDependencies.udfpsAccessibilityOverlayViewModel.get(),
+                modifier = Modifier.fillMaxHeight(),
+            )
+        }
+    }
+}
+
+@ExperimentalCoroutinesApi
+@Composable
+private fun StatusMessage(
+    viewModel: AlternateBouncerMessageAreaViewModel,
+    modifier: Modifier = Modifier,
+) {
+    val message: BiometricMessage? by
+        viewModel.message.collectAsStateWithLifecycle(initialValue = null)
+
+    Crossfade(
+        targetState = message,
+        label = "Alternate Bouncer message",
+        animationSpec = tween(),
+        modifier = modifier,
+    ) { biometricMessage ->
+        biometricMessage?.let {
+            Text(
+                textAlign = TextAlign.Center,
+                text = it.message ?: "",
+                color = Colors.AlternateBouncerTextColor,
+                fontSize = 18.sp,
+                lineHeight = 24.sp,
+                overflow = TextOverflow.Ellipsis,
+                modifier = Modifier.padding(top = 92.dp),
+            )
+        }
+    }
+}
+
+@ExperimentalCoroutinesApi
+@Composable
+private fun DeviceEntryIcon(
+    viewModel: AlternateBouncerUdfpsIconViewModel,
+    modifier: Modifier = Modifier,
+) {
+    AndroidView(
+        modifier = modifier,
+        factory = { context ->
+            val view =
+                DeviceEntryIconView(context, null).apply {
+                    id = R.id.alternate_bouncer_udfps_icon_view
+                    contentDescription =
+                        context.resources.getString(R.string.accessibility_fingerprint_label)
+                }
+            AlternateBouncerUdfpsViewBinder.bind(view, viewModel)
+            view
+        },
+    )
+}
+
+/** TODO (b/353955910): Validate accessibility CUJs */
+@ExperimentalCoroutinesApi
+@Composable
+private fun UdfpsA11yOverlay(
+    viewModel: AlternateBouncerUdfpsAccessibilityOverlayViewModel,
+    modifier: Modifier = Modifier,
+) {
+    AndroidView(
+        factory = { context ->
+            val view =
+                UdfpsAccessibilityOverlay(context).apply {
+                    id = R.id.alternate_bouncer_udfps_accessibility_overlay
+                }
+            UdfpsAccessibilityOverlayBinder.bind(view, viewModel)
+            view
+        },
+        modifier = modifier,
+    )
+}
+
+private object Colors {
+    val AlternateBouncerBackgroundColor: Color = Color.Black.copy(alpha = .66f)
+    val AlternateBouncerTextColor: Color = Color.White
+}
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 25e91be..672b8a7 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
@@ -29,6 +29,7 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.ui.composable.blueprint.ComposableLockscreenSceneBlueprint
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
+import com.android.systemui.lifecycle.rememberViewModel
 
 /**
  * Renders the content of the lockscreen.
@@ -37,7 +38,7 @@
  * outside the scene container framework.
  */
 class LockscreenContent(
-    private val viewModel: LockscreenContentViewModel,
+    private val viewModelFactory: LockscreenContentViewModel.Factory,
     private val blueprints: Set<@JvmSuppressWildcards ComposableLockscreenSceneBlueprint>,
     private val clockInteractor: KeyguardClockInteractor,
 ) {
@@ -49,6 +50,7 @@
     fun SceneScope.Content(
         modifier: Modifier = Modifier,
     ) {
+        val viewModel = rememberViewModel { 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
@@ -69,6 +71,6 @@
         }
 
         val blueprint = blueprintByBlueprintId[blueprintId] ?: return
-        with(blueprint) { Content(modifier.sysuiResTag("keyguard_root_view")) }
+        with(blueprint) { Content(viewModel, modifier.sysuiResTag("keyguard_root_view")) }
     }
 }
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 b077e18..7fe1b3e 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
@@ -24,7 +24,7 @@
 import com.android.compose.animation.scene.UserActionResult
 import com.android.compose.animation.scene.animateSceneFloatAsState
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneActionsViewModel
 import com.android.systemui.qs.ui.composable.QuickSettings
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.ui.composable.ComposableScene
@@ -37,13 +37,21 @@
 class LockscreenScene
 @Inject
 constructor(
-    viewModel: LockscreenSceneViewModel,
+    actionsViewModelFactory: LockscreenSceneActionsViewModel.Factory,
     private val lockscreenContent: Lazy<LockscreenContent>,
 ) : ComposableScene {
     override val key = Scenes.Lockscreen
 
+    private val actionsViewModel: LockscreenSceneActionsViewModel by lazy {
+        actionsViewModelFactory.create()
+    }
+
     override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
-        viewModel.destinationScenes
+        actionsViewModel.actions
+
+    override suspend fun activate() {
+        actionsViewModel.activate()
+    }
 
     @Composable
     override fun SceneScope.Content(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenSceneBlueprintModule.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenSceneBlueprintModule.kt
index 9afb4d5..a78c038 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenSceneBlueprintModule.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenSceneBlueprintModule.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.keyguard.ui.composable
 
 import com.android.systemui.keyguard.ui.composable.blueprint.CommunalBlueprintModule
-import com.android.systemui.keyguard.ui.composable.blueprint.ShortcutsBesideUdfpsBlueprintModule
 import com.android.systemui.keyguard.ui.composable.section.OptionalSectionModule
 import dagger.Module
 
@@ -26,7 +25,6 @@
         [
             CommunalBlueprintModule::class,
             OptionalSectionModule::class,
-            ShortcutsBesideUdfpsBlueprintModule::class,
         ],
 )
 interface LockscreenSceneBlueprintModule
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt
index 210ca69..adad446 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt
@@ -32,16 +32,15 @@
 import javax.inject.Inject
 
 /** Renders the lockscreen scene when showing the communal glanceable hub. */
-class CommunalBlueprint
-@Inject
-constructor(
-    private val viewModel: LockscreenContentViewModel,
-) : ComposableLockscreenSceneBlueprint {
+class CommunalBlueprint @Inject constructor() : ComposableLockscreenSceneBlueprint {
 
     override val id: String = "communal"
 
     @Composable
-    override fun SceneScope.Content(modifier: Modifier) {
+    override fun SceneScope.Content(
+        viewModel: LockscreenContentViewModel,
+        modifier: Modifier,
+    ) {
         LockscreenLongPress(
             viewModel = viewModel.touchHandling,
             modifier = modifier,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ComposableLockscreenSceneBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ComposableLockscreenSceneBlueprint.kt
index cb73983..df36d07 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ComposableLockscreenSceneBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ComposableLockscreenSceneBlueprint.kt
@@ -20,9 +20,14 @@
 import androidx.compose.ui.Modifier
 import com.android.compose.animation.scene.SceneScope
 import com.android.systemui.keyguard.shared.model.LockscreenSceneBlueprint
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
 
 /** Defines interface for classes that can render the content for a specific blueprint/layout. */
 interface ComposableLockscreenSceneBlueprint : LockscreenSceneBlueprint {
     /** Renders the content of this blueprint. */
-    @Composable fun SceneScope.Content(modifier: Modifier)
+    @Composable
+    fun SceneScope.Content(
+        viewModel: LockscreenContentViewModel,
+        modifier: Modifier,
+    )
 }
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 a9e63c6..a3e0701 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
@@ -53,7 +53,6 @@
 class DefaultBlueprint
 @Inject
 constructor(
-    private val viewModel: LockscreenContentViewModel,
     private val statusBarSection: StatusBarSection,
     private val lockSection: LockSection,
     private val ambientIndicationSectionOptional: Optional<AmbientIndicationSection>,
@@ -66,10 +65,17 @@
     override val id: String = "default"
 
     @Composable
-    override fun SceneScope.Content(modifier: Modifier) {
+    override fun SceneScope.Content(
+        viewModel: LockscreenContentViewModel,
+        modifier: Modifier,
+    ) {
         val isUdfpsVisible = viewModel.isUdfpsVisible
         val isShadeLayoutWide by viewModel.isShadeLayoutWide.collectAsStateWithLifecycle()
         val unfoldTranslations by viewModel.unfoldTranslations.collectAsStateWithLifecycle()
+        val areNotificationsVisible by
+            viewModel
+                .areNotificationsVisible(contentKey)
+                .collectAsStateWithLifecycle(initialValue = false)
 
         LockscreenLongPress(
             viewModel = viewModel.touchHandling,
@@ -94,6 +100,7 @@
                         Box {
                             with(topAreaSection) {
                                 DefaultClockLayout(
+                                    smartSpacePaddingTop = viewModel::getSmartSpacePaddingTop,
                                     modifier =
                                         Modifier.thenIf(isShadeLayoutWide) {
                                                 Modifier.fillMaxWidth(0.5f)
@@ -106,6 +113,8 @@
                             if (isShadeLayoutWide) {
                                 with(notificationSection) {
                                     Notifications(
+                                        areNotificationsVisible = areNotificationsVisible,
+                                        isShadeLayoutWide = isShadeLayoutWide,
                                         burnInParams = null,
                                         modifier =
                                             Modifier.fillMaxWidth(0.5f)
@@ -118,6 +127,8 @@
                         if (!isShadeLayoutWide) {
                             with(notificationSection) {
                                 Notifications(
+                                    areNotificationsVisible = areNotificationsVisible,
+                                    isShadeLayoutWide = isShadeLayoutWide,
                                     burnInParams = null,
                                     modifier = Modifier.weight(weight = 1f)
                                 )
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
deleted file mode 100644
index 72cf832..0000000
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
+++ /dev/null
@@ -1,248 +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.ui.composable.blueprint
-
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxHeight
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.graphicsLayer
-import androidx.compose.ui.layout.Layout
-import androidx.compose.ui.unit.IntRect
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import com.android.compose.animation.scene.SceneScope
-import com.android.compose.modifiers.padding
-import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
-import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
-import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
-import com.android.systemui.keyguard.ui.composable.section.LockSection
-import com.android.systemui.keyguard.ui.composable.section.NotificationSection
-import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
-import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
-import com.android.systemui.keyguard.ui.composable.section.TopAreaSection
-import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
-import dagger.Binds
-import dagger.Module
-import dagger.multibindings.IntoSet
-import java.util.Optional
-import javax.inject.Inject
-import kotlin.math.roundToInt
-
-/**
- * Renders the lockscreen scene when showing with the default layout (e.g. vertical phone form
- * factor).
- */
-class ShortcutsBesideUdfpsBlueprint
-@Inject
-constructor(
-    private val viewModel: LockscreenContentViewModel,
-    private val statusBarSection: StatusBarSection,
-    private val lockSection: LockSection,
-    private val ambientIndicationSectionOptional: Optional<AmbientIndicationSection>,
-    private val bottomAreaSection: BottomAreaSection,
-    private val settingsMenuSection: SettingsMenuSection,
-    private val topAreaSection: TopAreaSection,
-    private val notificationSection: NotificationSection,
-) : ComposableLockscreenSceneBlueprint {
-
-    override val id: String = "shortcuts-besides-udfps"
-
-    @Composable
-    override fun SceneScope.Content(modifier: Modifier) {
-        val isUdfpsVisible = viewModel.isUdfpsVisible
-        val isShadeLayoutWide by viewModel.isShadeLayoutWide.collectAsStateWithLifecycle()
-        val unfoldTranslations by viewModel.unfoldTranslations.collectAsStateWithLifecycle()
-
-        LockscreenLongPress(
-            viewModel = viewModel.touchHandling,
-            modifier = modifier,
-        ) { onSettingsMenuPlaced ->
-            Layout(
-                content = {
-                    // Constrained to above the lock icon.
-                    Column(
-                        modifier = Modifier.fillMaxSize(),
-                    ) {
-                        with(statusBarSection) {
-                            StatusBar(
-                                modifier =
-                                    Modifier.fillMaxWidth()
-                                        .padding(
-                                            horizontal = { unfoldTranslations.start.roundToInt() },
-                                        )
-                            )
-                        }
-
-                        Box {
-                            with(topAreaSection) {
-                                DefaultClockLayout(
-                                    modifier =
-                                        Modifier.graphicsLayer {
-                                            translationX = unfoldTranslations.start
-                                        },
-                                )
-                            }
-                            if (isShadeLayoutWide) {
-                                with(notificationSection) {
-                                    Notifications(
-                                        burnInParams = null,
-                                        modifier =
-                                            Modifier.fillMaxWidth(0.5f)
-                                                .fillMaxHeight()
-                                                .align(alignment = Alignment.TopEnd)
-                                    )
-                                }
-                            }
-                        }
-                        if (!isShadeLayoutWide) {
-                            with(notificationSection) {
-                                Notifications(
-                                    burnInParams = null,
-                                    modifier = Modifier.weight(weight = 1f)
-                                )
-                            }
-                        }
-                        if (!isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
-                            with(ambientIndicationSectionOptional.get()) {
-                                AmbientIndication(modifier = Modifier.fillMaxWidth())
-                            }
-                        }
-                    }
-
-                    // Constrained to the left of the lock icon (in left-to-right layouts).
-                    with(bottomAreaSection) {
-                        Shortcut(
-                            isStart = true,
-                            applyPadding = false,
-                            modifier =
-                                Modifier.graphicsLayer { translationX = unfoldTranslations.start },
-                        )
-                    }
-
-                    with(lockSection) { LockIcon() }
-
-                    // Constrained to the right of the lock icon (in left-to-right layouts).
-                    with(bottomAreaSection) {
-                        Shortcut(
-                            isStart = false,
-                            applyPadding = false,
-                            modifier =
-                                Modifier.graphicsLayer { translationX = unfoldTranslations.end },
-                        )
-                    }
-
-                    // Aligned to bottom and constrained to below the lock icon.
-                    Column(modifier = Modifier.fillMaxWidth()) {
-                        if (isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
-                            with(ambientIndicationSectionOptional.get()) {
-                                AmbientIndication(modifier = Modifier.fillMaxWidth())
-                            }
-                        }
-
-                        with(bottomAreaSection) {
-                            IndicationArea(modifier = Modifier.fillMaxWidth())
-                        }
-                    }
-
-                    // Aligned to bottom and NOT constrained by the lock icon.
-                    with(settingsMenuSection) { SettingsMenu(onSettingsMenuPlaced) }
-                },
-                modifier = Modifier.fillMaxSize(),
-            ) { measurables, constraints ->
-                check(measurables.size == 6)
-                val aboveLockIconMeasurable = measurables[0]
-                val startSideShortcutMeasurable = measurables[1]
-                val lockIconMeasurable = measurables[2]
-                val endSideShortcutMeasurable = measurables[3]
-                val belowLockIconMeasurable = measurables[4]
-                val settingsMenuMeasurable = measurables[5]
-
-                val noMinConstraints =
-                    constraints.copy(
-                        minWidth = 0,
-                        minHeight = 0,
-                    )
-
-                val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
-                val lockIconBounds =
-                    IntRect(
-                        left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
-                        top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
-                        right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
-                        bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
-                    )
-
-                val aboveLockIconPlaceable =
-                    aboveLockIconMeasurable.measure(
-                        noMinConstraints.copy(maxHeight = lockIconBounds.top)
-                    )
-                val startSideShortcutPlaceable =
-                    startSideShortcutMeasurable.measure(noMinConstraints)
-                val endSideShortcutPlaceable = endSideShortcutMeasurable.measure(noMinConstraints)
-                val belowLockIconPlaceable =
-                    belowLockIconMeasurable.measure(
-                        noMinConstraints.copy(
-                            maxHeight = constraints.maxHeight - lockIconBounds.bottom
-                        )
-                    )
-                val settingsMenuPlaceable = settingsMenuMeasurable.measure(noMinConstraints)
-
-                layout(constraints.maxWidth, constraints.maxHeight) {
-                    aboveLockIconPlaceable.place(
-                        x = 0,
-                        y = 0,
-                    )
-                    startSideShortcutPlaceable.placeRelative(
-                        x = lockIconBounds.left / 2 - startSideShortcutPlaceable.width / 2,
-                        y = lockIconBounds.center.y - startSideShortcutPlaceable.height / 2,
-                    )
-                    lockIconPlaceable.place(
-                        x = lockIconBounds.left,
-                        y = lockIconBounds.top,
-                    )
-                    endSideShortcutPlaceable.placeRelative(
-                        x =
-                            lockIconBounds.right +
-                                (constraints.maxWidth - lockIconBounds.right) / 2 -
-                                endSideShortcutPlaceable.width / 2,
-                        y = lockIconBounds.center.y - endSideShortcutPlaceable.height / 2,
-                    )
-                    belowLockIconPlaceable.place(
-                        x = 0,
-                        y = constraints.maxHeight - belowLockIconPlaceable.height,
-                    )
-                    settingsMenuPlaceable.place(
-                        x = (constraints.maxWidth - settingsMenuPlaceable.width) / 2,
-                        y = constraints.maxHeight - settingsMenuPlaceable.height,
-                    )
-                }
-            }
-        }
-    }
-}
-
-@Module
-interface ShortcutsBesideUdfpsBlueprintModule {
-    @Binds
-    @IntoSet
-    fun blueprint(blueprint: ShortcutsBesideUdfpsBlueprint): ComposableLockscreenSceneBlueprint
-}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
index 9c72d93..364adca 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
@@ -32,7 +32,6 @@
 import androidx.core.content.res.ResourcesCompat
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.SceneScope
-import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
 import com.android.systemui.animation.view.LaunchableImageView
 import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder
 import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
@@ -40,10 +39,8 @@
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel
-import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.KeyguardIndicationController
-import com.android.systemui.statusbar.VibratorHelper
 import javax.inject.Inject
 import kotlinx.coroutines.DisposableHandle
 import kotlinx.coroutines.flow.Flow
@@ -52,11 +49,9 @@
 @Inject
 constructor(
     private val viewModel: KeyguardQuickAffordancesCombinedViewModel,
-    private val falsingManager: FalsingManager,
-    private val vibratorHelper: VibratorHelper,
     private val indicationController: KeyguardIndicationController,
     private val indicationAreaViewModel: KeyguardIndicationAreaViewModel,
-    private val shortcutsLogger: KeyguardQuickAffordancesLogger,
+    private val keyguardQuickAffordanceViewBinder: KeyguardQuickAffordanceViewBinder,
 ) {
     /**
      * Renders a single lockscreen shortcut.
@@ -80,9 +75,8 @@
                     viewId = if (isStart) R.id.start_button else R.id.end_button,
                     viewModel = if (isStart) viewModel.startButton else viewModel.endButton,
                     transitionAlpha = viewModel.transitionAlpha,
-                    falsingManager = falsingManager,
-                    vibratorHelper = vibratorHelper,
                     indicationController = indicationController,
+                    binder = keyguardQuickAffordanceViewBinder,
                     modifier =
                         if (applyPadding) {
                             Modifier.shortcutPadding()
@@ -124,9 +118,8 @@
         @IdRes viewId: Int,
         viewModel: Flow<KeyguardQuickAffordanceViewModel>,
         transitionAlpha: Flow<Float>,
-        falsingManager: FalsingManager,
-        vibratorHelper: VibratorHelper,
         indicationController: KeyguardIndicationController,
+        binder: KeyguardQuickAffordanceViewBinder,
         modifier: Modifier = Modifier,
     ) {
         val (binding, setBinding) = mutableStateOf<KeyguardQuickAffordanceViewBinder.Binding?>(null)
@@ -158,13 +151,10 @@
                     }
 
                 setBinding(
-                    KeyguardQuickAffordanceViewBinder.bind(
+                    binder.bind(
                         view,
                         viewModel,
                         transitionAlpha,
-                        falsingManager,
-                        vibratorHelper,
-                        shortcutsLogger,
                     ) {
                         indicationController.showTransientIndication(it)
                     }
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 df068c4..4e117d6 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
@@ -23,21 +23,16 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.modifiers.thenIf
-import com.android.systemui.Flags
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.MigrateClocksToBlueprint
 import com.android.systemui.keyguard.ui.composable.modifier.burnInAware
 import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
 import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
-import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
 import com.android.systemui.notifications.ui.composable.ConstrainedNotificationStack
-import com.android.systemui.res.R
 import com.android.systemui.shade.LargeScreenHeaderHelper
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
 import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
@@ -59,7 +54,6 @@
     sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
     stackScrollLayout: NotificationStackScrollLayout,
     sharedNotificationContainerBinder: SharedNotificationContainerBinder,
-    private val lockscreenContentViewModel: LockscreenContentViewModel,
 ) {
 
     init {
@@ -89,23 +83,18 @@
      *   adjustment
      */
     @Composable
-    fun SceneScope.Notifications(burnInParams: BurnInParameters?, modifier: Modifier = Modifier) {
-        val areNotificationsVisible by
-            lockscreenContentViewModel
-                .areNotificationsVisible(contentKey)
-                .collectAsStateWithLifecycle(initialValue = false)
+    fun SceneScope.Notifications(
+        areNotificationsVisible: Boolean,
+        isShadeLayoutWide: Boolean,
+        burnInParams: BurnInParameters?,
+        modifier: Modifier = Modifier
+    ) {
         if (!areNotificationsVisible) {
             return
         }
 
-        val isShadeLayoutWide by
-            lockscreenContentViewModel.isShadeLayoutWide.collectAsStateWithLifecycle()
         val splitShadeTopMargin: Dp =
-            if (Flags.centralizedStatusBarHeightFix()) {
-                LargeScreenHeaderHelper.getLargeScreenHeaderHeight(LocalContext.current).dp
-            } else {
-                dimensionResource(id = R.dimen.large_screen_shade_header_height)
-            }
+            LargeScreenHeaderHelper.getLargeScreenHeaderHeight(LocalContext.current).dp
 
         ConstrainedNotificationStack(
             stackScrollView = stackScrollView.get(),
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt
index 33ed14b..da78eed 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.keyguard.ui.composable.section
 
+import android.content.res.Resources
 import android.widget.FrameLayout
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.IntrinsicSize
@@ -43,7 +44,6 @@
 import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
 import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
-import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
 import javax.inject.Inject
@@ -55,12 +55,12 @@
     private val keyguardUnlockAnimationController: KeyguardUnlockAnimationController,
     private val keyguardSmartspaceViewModel: KeyguardSmartspaceViewModel,
     private val aodBurnInViewModel: AodBurnInViewModel,
-    private val lockscreenContentViewModel: LockscreenContentViewModel,
 ) {
     @Composable
     fun SceneScope.SmartSpace(
         burnInParams: BurnInParameters,
         onTopChanged: (top: Float?) -> Unit,
+        smartSpacePaddingTop: (Resources) -> Int,
         modifier: Modifier = Modifier,
     ) {
         val resources = LocalContext.current.resources
@@ -72,9 +72,7 @@
                         modifier
                             .onTopPlacementChanged(onTopChanged)
                             .padding(
-                                top = {
-                                    lockscreenContentViewModel.getSmartSpacePaddingTop(resources)
-                                },
+                                top = { smartSpacePaddingTop(resources) },
                                 bottom = {
                                     resources.getDimensionPixelSize(
                                         R.dimen.keyguard_status_view_bottom_margin
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 c0832d9..0eeb79b 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
@@ -17,6 +17,7 @@
 package com.android.systemui.keyguard.ui.composable.section
 
 import android.content.Context
+import android.content.res.Resources
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
@@ -61,6 +62,7 @@
 ) {
     @Composable
     fun SceneScope.DefaultClockLayout(
+        smartSpacePaddingTop: (Resources) -> Int,
         modifier: Modifier = Modifier,
     ) {
         val currentClockLayout by clockViewModel.currentClockLayout.collectAsStateWithLifecycle()
@@ -99,22 +101,41 @@
             SceneTransitionLayout(state) {
                 scene(splitShadeLargeClockScene) {
                     LargeClockWithSmartSpace(
+                        smartSpacePaddingTop = smartSpacePaddingTop,
                         shouldOffSetClockToOneHalf = !hasCustomPositionUpdatedAnimation
                     )
                 }
 
                 scene(splitShadeSmallClockScene) {
-                    SmallClockWithSmartSpace(modifier = Modifier.fillMaxWidth(0.5f))
+                    SmallClockWithSmartSpace(
+                        smartSpacePaddingTop = smartSpacePaddingTop,
+                        modifier = Modifier.fillMaxWidth(0.5f),
+                    )
                 }
 
-                scene(smallClockScene) { SmallClockWithSmartSpace() }
+                scene(smallClockScene) {
+                    SmallClockWithSmartSpace(
+                        smartSpacePaddingTop = smartSpacePaddingTop,
+                    )
+                }
 
-                scene(largeClockScene) { LargeClockWithSmartSpace() }
+                scene(largeClockScene) {
+                    LargeClockWithSmartSpace(
+                        smartSpacePaddingTop = smartSpacePaddingTop,
+                    )
+                }
 
-                scene(WeatherClockScenes.largeClockScene) { WeatherLargeClockWithSmartSpace() }
+                scene(WeatherClockScenes.largeClockScene) {
+                    WeatherLargeClockWithSmartSpace(
+                        smartSpacePaddingTop = smartSpacePaddingTop,
+                    )
+                }
 
                 scene(WeatherClockScenes.splitShadeLargeClockScene) {
-                    WeatherLargeClockWithSmartSpace(modifier = Modifier.fillMaxWidth(0.5f))
+                    WeatherLargeClockWithSmartSpace(
+                        smartSpacePaddingTop = smartSpacePaddingTop,
+                        modifier = Modifier.fillMaxWidth(0.5f),
+                    )
                 }
             }
             with(mediaCarouselSection) { KeyguardMediaCarousel() }
@@ -122,7 +143,10 @@
     }
 
     @Composable
-    private fun SceneScope.SmallClockWithSmartSpace(modifier: Modifier = Modifier) {
+    private fun SceneScope.SmallClockWithSmartSpace(
+        smartSpacePaddingTop: (Resources) -> Int,
+        modifier: Modifier = Modifier,
+    ) {
         val burnIn = rememberBurnIn(clockInteractor)
 
         Column(modifier = modifier) {
@@ -137,13 +161,17 @@
                 SmartSpace(
                     burnInParams = burnIn.parameters,
                     onTopChanged = burnIn.onSmartspaceTopChanged,
+                    smartSpacePaddingTop = smartSpacePaddingTop,
                 )
             }
         }
     }
 
     @Composable
-    private fun SceneScope.LargeClockWithSmartSpace(shouldOffSetClockToOneHalf: Boolean = false) {
+    private fun SceneScope.LargeClockWithSmartSpace(
+        smartSpacePaddingTop: (Resources) -> Int,
+        shouldOffSetClockToOneHalf: Boolean = false,
+    ) {
         val burnIn = rememberBurnIn(clockInteractor)
         val isLargeClockVisible by clockViewModel.isLargeClockVisible.collectAsStateWithLifecycle()
 
@@ -158,6 +186,7 @@
                 SmartSpace(
                     burnInParams = burnIn.parameters,
                     onTopChanged = burnIn.onSmartspaceTopChanged,
+                    smartSpacePaddingTop = smartSpacePaddingTop,
                 )
             }
             with(clockSection) {
@@ -180,7 +209,10 @@
     }
 
     @Composable
-    private fun SceneScope.WeatherLargeClockWithSmartSpace(modifier: Modifier = Modifier) {
+    private fun SceneScope.WeatherLargeClockWithSmartSpace(
+        smartSpacePaddingTop: (Resources) -> Int,
+        modifier: Modifier = Modifier,
+    ) {
         val burnIn = rememberBurnIn(clockInteractor)
         val isLargeClockVisible by clockViewModel.isLargeClockVisible.collectAsStateWithLifecycle()
         val currentClockState = clockViewModel.currentClock.collectAsStateWithLifecycle()
@@ -206,6 +238,7 @@
                 SmartSpace(
                     burnInParams = burnIn.parameters,
                     onTopChanged = burnIn.onSmartspaceTopChanged,
+                    smartSpacePaddingTop = smartSpacePaddingTop,
                     modifier =
                         Modifier.heightIn(
                             min = getDimen(context, "enhanced_smartspace_height", density)
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 26ab10b..808e666 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
@@ -24,9 +24,10 @@
 import androidx.compose.foundation.layout.height
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.approachLayout
 import androidx.compose.ui.layout.layout
-import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.viewinterop.AndroidView
 import com.android.compose.animation.scene.MovableElementKey
 import com.android.compose.animation.scene.SceneScope
@@ -51,38 +52,48 @@
     mediaHost: MediaHost,
     modifier: Modifier = Modifier,
     carouselController: MediaCarouselController,
+    offsetProvider: (() -> IntOffset)? = null,
 ) {
     if (!isVisible) {
         return
     }
 
-    val density = LocalDensity.current
     val mediaHeight = dimensionResource(R.dimen.qs_media_session_height_expanded)
 
-    val layoutWidth = 0
-    val layoutHeight = with(density) { mediaHeight.toPx() }.toInt()
-
-    // Notify controller to size the carousel for the current space
-    mediaHost.measurementInput = MeasurementInput(layoutWidth, layoutHeight)
-    carouselController.setSceneContainerSize(layoutWidth, layoutHeight)
-
     MovableElement(
         key = MediaCarousel.Elements.Content,
-        modifier = modifier.height(mediaHeight).fillMaxWidth()
+        modifier = modifier.height(mediaHeight).fillMaxWidth(),
     ) {
         content {
             AndroidView(
                 modifier =
-                    Modifier.fillMaxSize().layout { measurable, constraints ->
-                        val placeable = measurable.measure(constraints)
+                    Modifier.fillMaxSize()
+                        .approachLayout(
+                            isMeasurementApproachInProgress = { offsetProvider != null },
+                            approachMeasure = { measurable, constraints ->
+                                val placeable = measurable.measure(constraints)
+                                layout(placeable.width, placeable.height) {
+                                    placeable.placeRelative(
+                                        offsetProvider?.invoke() ?: IntOffset.Zero
+                                    )
+                                }
+                            }
+                        )
+                        .layout { measurable, constraints ->
+                            val placeable = measurable.measure(constraints)
 
-                        // Notify controller to size the carousel for the current space
-                        mediaHost.measurementInput =
-                            MeasurementInput(placeable.width, placeable.height)
-                        carouselController.setSceneContainerSize(placeable.width, placeable.height)
+                            // Notify controller to size the carousel for the current space
+                            mediaHost.measurementInput =
+                                MeasurementInput(placeable.width, placeable.height)
+                            carouselController.setSceneContainerSize(
+                                placeable.width,
+                                placeable.height
+                            )
 
-                        layout(placeable.width, placeable.height) { placeable.placeRelative(0, 0) }
-                    },
+                            layout(placeable.width, placeable.height) {
+                                placeable.placeRelative(0, 0)
+                            }
+                        },
                 factory = { context ->
                     FrameLayout(context).apply {
                         layoutParams =
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 3f04f37..70c0db1 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
@@ -27,7 +27,6 @@
 /** [ElementContentPicker] implementation for the media carousel object. */
 object MediaContentPicker : StaticElementContentPicker {
 
-    const val SHADE_FRACTION = 0.66f
     override val contents =
         setOf(
             Scenes.Lockscreen,
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 84782fd..0bef05dc 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
@@ -276,6 +276,7 @@
     shouldReserveSpaceForNavBar: Boolean = true,
     shouldIncludeHeadsUpSpace: Boolean = true,
     shadeMode: ShadeMode,
+    onEmptySpaceClick: (() -> Unit)? = null,
     modifier: Modifier = Modifier,
 ) {
     val coroutineScope = rememberCoroutineScope()
@@ -328,8 +329,6 @@
     // The height of the scrim visible on screen when it is in its resting (collapsed) state.
     val minVisibleScrimHeight: () -> Float = { screenHeight - maxScrimTop() }
 
-    val isClickable by viewModel.isClickable.collectAsStateWithLifecycle()
-
     // we are not scrolled to the top unless the scrim is at its maximum offset.
     LaunchedEffect(viewModel, scrimOffset) {
         snapshotFlow { scrimOffset.value >= 0f }
@@ -437,8 +436,8 @@
                         )
                     )
                 }
-                .thenIf(isClickable) {
-                    Modifier.clickable(onClick = { viewModel.onEmptySpaceClicked() })
+                .thenIf(onEmptySpaceClick != null) {
+                    Modifier.clickable(onClick = { onEmptySpaceClick?.invoke() })
                 }
     ) {
         // Creates a cutout in the background scrim in the shape of the notifications scrim.
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 7159def..af3d9d3 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
@@ -28,7 +28,7 @@
 import com.android.systemui.battery.BatteryMeterViewController
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.ui.composable.LockscreenContent
-import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeSceneViewModel
+import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeSceneActionsViewModel
 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
@@ -50,9 +50,9 @@
 class NotificationsShadeScene
 @Inject
 constructor(
-    sceneViewModel: NotificationsShadeSceneViewModel,
-    private val overlayShadeViewModel: OverlayShadeViewModel,
-    private val shadeHeaderViewModel: ShadeHeaderViewModel,
+    private val actionsViewModelFactory: NotificationsShadeSceneActionsViewModel.Factory,
+    private val overlayShadeViewModelFactory: OverlayShadeViewModel.Factory,
+    private val shadeHeaderViewModelFactory: ShadeHeaderViewModel.Factory,
     private val notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel,
     private val tintedIconManagerFactory: TintedIconManager.Factory,
     private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
@@ -64,8 +64,16 @@
 
     override val key = Scenes.NotificationsShade
 
+    private val actionsViewModel: NotificationsShadeSceneActionsViewModel by lazy {
+        actionsViewModelFactory.create()
+    }
+
     override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
-        sceneViewModel.destinationScenes
+        actionsViewModel.actions
+
+    override suspend fun activate() {
+        actionsViewModel.activate()
+    }
 
     @Composable
     override fun SceneScope.Content(
@@ -73,12 +81,12 @@
     ) {
         OverlayShade(
             modifier = modifier,
-            viewModel = overlayShadeViewModel,
+            viewModelFactory = overlayShadeViewModelFactory,
             lockscreenContent = lockscreenContent,
         ) {
             Column {
                 ExpandedShadeHeader(
-                    viewModel = shadeHeaderViewModel,
+                    viewModelFactory = shadeHeaderViewModelFactory,
                     createTintedIconManager = tintedIconManagerFactory::create,
                     createBatteryMeterViewController = batteryMeterViewControllerFactory::create,
                     statusBarIconController = statusBarIconController,
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 cdcd840..8bba0f4 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.rememberViewModel
 import com.android.systemui.media.controls.ui.composable.MediaCarousel
 import com.android.systemui.media.controls.ui.controller.MediaCarouselController
 import com.android.systemui.media.controls.ui.view.MediaHost
@@ -166,19 +167,23 @@
 ) {
     val cutoutLocation = LocalDisplayCutout.current.location
 
-    val brightnessMirrorShowing by
-        viewModel.brightnessMirrorViewModel.isShowing.collectAsStateWithLifecycle()
+    val brightnessMirrorViewModel = rememberViewModel {
+        viewModel.brightnessMirrorViewModelFactory.create()
+    }
+    val brightnessMirrorShowing by brightnessMirrorViewModel.isShowing.collectAsStateWithLifecycle()
     val contentAlpha by
         animateFloatAsState(
             targetValue = if (brightnessMirrorShowing) 0f else 1f,
             label = "alphaAnimationBrightnessMirrorContentHiding",
         )
 
-    viewModel.notifications.setAlphaForBrightnessMirror(contentAlpha)
-    DisposableEffect(Unit) { onDispose { viewModel.notifications.setAlphaForBrightnessMirror(1f) } }
+    notificationsPlaceholderViewModel.setAlphaForBrightnessMirror(contentAlpha)
+    DisposableEffect(Unit) {
+        onDispose { notificationsPlaceholderViewModel.setAlphaForBrightnessMirror(1f) }
+    }
 
     BrightnessMirror(
-        viewModel = viewModel.brightnessMirrorViewModel,
+        viewModel = brightnessMirrorViewModel,
         qsSceneAdapter = viewModel.qsSceneAdapter,
         modifier =
             Modifier.thenIf(cutoutLocation != CutoutLocation.CENTER) {
@@ -337,7 +342,7 @@
                                         fadeOut(tween(customizingAnimationDuration)),
                             ) {
                                 ExpandedShadeHeader(
-                                    viewModel = viewModel.shadeHeaderViewModel,
+                                    viewModelFactory = viewModel.shadeHeaderViewModelFactory,
                                     createTintedIconManager = createTintedIconManager,
                                     createBatteryMeterViewController =
                                         createBatteryMeterViewController,
@@ -347,7 +352,7 @@
                             }
                         else ->
                             CollapsedShadeHeader(
-                                viewModel = viewModel.shadeHeaderViewModel,
+                                viewModelFactory = viewModel.shadeHeaderViewModelFactory,
                                 createTintedIconManager = createTintedIconManager,
                                 createBatteryMeterViewController = createBatteryMeterViewController,
                                 statusBarIconController = statusBarIconController,
@@ -417,7 +422,7 @@
         )
         NotificationStackCutoffGuideline(
             stackScrollView = notificationStackScrollView,
-            viewModel = viewModel.notifications,
+            viewModel = notificationsPlaceholderViewModel,
             modifier =
                 Modifier.align(Alignment.BottomCenter).navigationBarsPadding().offset {
                     IntOffset(x = 0, y = screenHeight.roundToInt())
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 f6d1283..eea00c4 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
@@ -44,12 +44,14 @@
 import com.android.systemui.brightness.ui.compose.BrightnessSliderContainer
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.ui.composable.LockscreenContent
+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.QuickSettingsShadeSceneViewModel
+import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeSceneActionsViewModel
+import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeSceneContentViewModel
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.ui.composable.ComposableScene
 import com.android.systemui.shade.ui.composable.ExpandedShadeHeader
@@ -66,9 +68,10 @@
 class QuickSettingsShadeScene
 @Inject
 constructor(
-    private val viewModel: QuickSettingsShadeSceneViewModel,
+    private val actionsViewModelFactory: QuickSettingsShadeSceneActionsViewModel.Factory,
+    private val contentViewModelFactory: QuickSettingsShadeSceneContentViewModel.Factory,
     private val lockscreenContent: Lazy<Optional<LockscreenContent>>,
-    private val shadeHeaderViewModel: ShadeHeaderViewModel,
+    private val shadeHeaderViewModelFactory: ShadeHeaderViewModel.Factory,
     private val tintedIconManagerFactory: TintedIconManager.Factory,
     private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
     private val statusBarIconController: StatusBarIconController,
@@ -76,21 +79,26 @@
 
     override val key = Scenes.QuickSettingsShade
 
+    private val actionsViewModel: QuickSettingsShadeSceneActionsViewModel by lazy {
+        actionsViewModelFactory.create()
+    }
+
     override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
-        viewModel.destinationScenes
+        actionsViewModel.actions
 
     @Composable
     override fun SceneScope.Content(
         modifier: Modifier,
     ) {
+        val viewModel = rememberViewModel { contentViewModelFactory.create() }
         OverlayShade(
-            viewModel = viewModel.overlayShadeViewModel,
+            viewModelFactory = viewModel.overlayShadeViewModelFactory,
             lockscreenContent = lockscreenContent,
             modifier = modifier,
         ) {
             Column {
                 ExpandedShadeHeader(
-                    viewModel = shadeHeaderViewModel,
+                    viewModelFactory = shadeHeaderViewModelFactory,
                     createTintedIconManager = tintedIconManagerFactory::create,
                     createBatteryMeterViewController = batteryMeterViewControllerFactory::create,
                     statusBarIconController = statusBarIconController,
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 a9ddf84..b0c3fb31 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
@@ -31,7 +31,7 @@
 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.GoneSceneViewModel
+import com.android.systemui.scene.ui.viewmodel.GoneSceneActionsViewModel
 import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
 import dagger.Lazy
@@ -48,12 +48,18 @@
 constructor(
     private val notificationStackScrolLView: Lazy<NotificationScrollView>,
     private val notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel,
-    private val viewModel: GoneSceneViewModel,
+    private val viewModelFactory: GoneSceneActionsViewModel.Factory,
 ) : ComposableScene {
     override val key = Scenes.Gone
 
+    private val actionsViewModel: GoneSceneActionsViewModel by lazy { viewModelFactory.create() }
+
     override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
-        viewModel.destinationScenes
+        actionsViewModel.actions
+
+    override suspend fun activate() {
+        actionsViewModel.activate()
+    }
 
     @Composable
     override fun SceneScope.Content(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt
index a9da733..71fa6c9 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt
@@ -25,7 +25,6 @@
 import com.android.compose.animation.scene.UserActionDistance
 import com.android.compose.animation.scene.UserActionDistanceScope
 import com.android.systemui.media.controls.ui.composable.MediaCarousel
-import com.android.systemui.media.controls.ui.composable.MediaContentPicker
 import com.android.systemui.notifications.ui.composable.Notifications
 import com.android.systemui.qs.ui.composable.QuickSettings
 import com.android.systemui.shade.ui.composable.Shade
@@ -54,13 +53,7 @@
     fractionRange(end = .33f) { fade(Shade.Elements.BackgroundScrim) }
 
     fractionRange(start = .33f) {
-        val qsTranslation =
-            ShadeHeader.Dimensions.CollapsedHeight * MediaContentPicker.SHADE_FRACTION
-        val qsExpansionDiff =
-            ShadeHeader.Dimensions.ExpandedHeight - ShadeHeader.Dimensions.CollapsedHeight
-        translate(MediaCarousel.Elements.Content, y = -(qsExpansionDiff + qsTranslation))
         fade(MediaCarousel.Elements.Content)
-
         fade(ShadeHeader.Elements.Clock)
         fade(ShadeHeader.Elements.CollapsedContentStart)
         fade(ShadeHeader.Elements.CollapsedContentEnd)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt
index 21dfc49..b677dff 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt
@@ -26,7 +26,6 @@
 import com.android.compose.animation.scene.UserActionDistance
 import com.android.compose.animation.scene.UserActionDistanceScope
 import com.android.systemui.media.controls.ui.composable.MediaCarousel
-import com.android.systemui.media.controls.ui.composable.MediaContentPicker
 import com.android.systemui.notifications.ui.composable.Notifications
 import com.android.systemui.qs.ui.composable.QuickSettings
 import com.android.systemui.scene.shared.model.Scenes
@@ -62,12 +61,9 @@
         fade(QuickSettings.Elements.FooterActions)
     }
 
-    val qsTranslation = ShadeHeader.Dimensions.CollapsedHeight * MediaContentPicker.SHADE_FRACTION
-    val qsExpansionDiff =
-        ShadeHeader.Dimensions.ExpandedHeight - ShadeHeader.Dimensions.CollapsedHeight
-
-    translate(QuickSettings.Elements.QuickQuickSettings, y = -qsTranslation)
-    translate(MediaCarousel.Elements.Content, y = -(qsExpansionDiff + qsTranslation))
+    val qsTranslation = -ShadeHeader.Dimensions.CollapsedHeight * 0.66f
+    translate(QuickSettings.Elements.QuickQuickSettings, y = qsTranslation)
+    translate(MediaCarousel.Elements.Content, y = qsTranslation)
     translate(Notifications.Elements.NotificationScrim, Edge.Top, false)
 }
 
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 facbcaf..445ffcb 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
@@ -53,6 +53,7 @@
 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
@@ -63,11 +64,12 @@
 /** The overlay shade renders a lightweight shade UI container on top of a background scene. */
 @Composable
 fun SceneScope.OverlayShade(
-    viewModel: OverlayShadeViewModel,
+    viewModelFactory: OverlayShadeViewModel.Factory,
     lockscreenContent: Lazy<Optional<LockscreenContent>>,
     modifier: Modifier = Modifier,
     content: @Composable () -> Unit,
 ) {
+    val viewModel = rememberViewModel { viewModelFactory.create() }
     val backgroundScene by viewModel.backgroundScene.collectAsStateWithLifecycle()
 
     Box(modifier) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
index 1cd48bf..8c53740 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
@@ -73,6 +73,7 @@
 import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout
 import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius
 import com.android.systemui.compose.modifiers.sysuiResTag
+import com.android.systemui.lifecycle.rememberViewModel
 import com.android.systemui.privacy.OngoingPrivacyChip
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.model.Scenes
@@ -122,12 +123,13 @@
 
 @Composable
 fun SceneScope.CollapsedShadeHeader(
-    viewModel: ShadeHeaderViewModel,
+    viewModelFactory: ShadeHeaderViewModel.Factory,
     createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
     createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
     statusBarIconController: StatusBarIconController,
     modifier: Modifier = Modifier,
 ) {
+    val viewModel = rememberViewModel { viewModelFactory.create() }
     val isDisabled by viewModel.isDisabled.collectAsStateWithLifecycle()
     if (isDisabled) {
         return
@@ -279,12 +281,13 @@
 
 @Composable
 fun SceneScope.ExpandedShadeHeader(
-    viewModel: ShadeHeaderViewModel,
+    viewModelFactory: ShadeHeaderViewModel.Factory,
     createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
     createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
     statusBarIconController: StatusBarIconController,
     modifier: Modifier = Modifier,
 ) {
+    val viewModel = rememberViewModel { 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/ShadeMediaOffsetProvider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeMediaOffsetProvider.kt
new file mode 100644
index 0000000..e0b7909
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeMediaOffsetProvider.kt
@@ -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.systemui.shade.ui.composable
+
+import androidx.compose.ui.unit.IntOffset
+import com.android.systemui.qs.ui.adapter.QSSceneAdapter
+
+/**
+ * Provider for the extra offset for the Media section in the shade to accommodate for the squishing
+ * qs or qqs tiles.
+ */
+interface ShadeMediaOffsetProvider {
+
+    /** Returns current offset to be applied to the Media Carousel */
+    val offset: IntOffset
+
+    /**
+     * [ShadeMediaOffsetProvider] implementation for Quick Settings.
+     *
+     * [updateLayout] should represent an access to some state to trigger Compose to relayout to
+     * track [QSSceneAdapter] internal state changes during the transition.
+     */
+    class Qs(private val updateLayout: () -> Unit, private val qsSceneAdapter: QSSceneAdapter) :
+        ShadeMediaOffsetProvider {
+
+        override val offset: IntOffset
+            get() =
+                calculateQsOffset(
+                    updateLayout,
+                    qsSceneAdapter.qsHeight,
+                    qsSceneAdapter.squishedQsHeight
+                )
+    }
+
+    /**
+     * [ShadeMediaOffsetProvider] implementation for Quick Quick Settings.
+     *
+     * [updateLayout] should represent an access to some state to trigger Compose to relayout to
+     * track [QSSceneAdapter] internal state changes during the transition.
+     */
+    class Qqs(private val updateLayout: () -> Unit, private val qsSceneAdapter: QSSceneAdapter) :
+        ShadeMediaOffsetProvider {
+
+        override val offset: IntOffset
+            get() =
+                calculateQsOffset(
+                    updateLayout,
+                    qsSceneAdapter.qqsHeight,
+                    qsSceneAdapter.squishedQqsHeight
+                )
+    }
+
+    companion object {
+
+        protected fun calculateQsOffset(
+            updateLayout: () -> Unit,
+            qsHeight: Int,
+            qsSquishedHeight: Int
+        ): IntOffset {
+            updateLayout()
+            val distanceFromBottomToActualBottom = qsHeight - qsSquishedHeight
+            return IntOffset(0, -distanceFromBottomToActualBottom)
+        }
+    }
+}
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 77b48d3..16972bc 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
@@ -80,6 +80,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.rememberViewModel
 import com.android.systemui.media.controls.ui.composable.MediaCarousel
 import com.android.systemui.media.controls.ui.composable.MediaContentPicker
 import com.android.systemui.media.controls.ui.composable.shouldElevateMedia
@@ -102,7 +103,8 @@
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.ui.composable.ComposableScene
 import com.android.systemui.shade.shared.model.ShadeMode
-import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel
+import com.android.systemui.shade.ui.viewmodel.ShadeSceneActionsViewModel
+import com.android.systemui.shade.ui.viewmodel.ShadeSceneContentViewModel
 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
@@ -116,7 +118,6 @@
 
 object Shade {
     object Elements {
-        val MediaCarousel = ElementKey("ShadeMediaCarousel")
         val BackgroundScrim =
             ElementKey("ShadeBackgroundScrim", contentPicker = LowestZIndexContentPicker)
         val SplitShadeStartColumn = ElementKey("SplitShadeStartColumn")
@@ -145,7 +146,8 @@
 constructor(
     private val shadeSession: SaveableSession,
     private val notificationStackScrollView: Lazy<NotificationScrollView>,
-    private val viewModel: ShadeSceneViewModel,
+    private val actionsViewModelFactory: ShadeSceneActionsViewModel.Factory,
+    private val contentViewModelFactory: ShadeSceneContentViewModel.Factory,
     private val notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel,
     private val tintedIconManagerFactory: TintedIconManager.Factory,
     private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
@@ -157,12 +159,16 @@
 
     override val key = Scenes.Shade
 
+    private val actionsViewModel: ShadeSceneActionsViewModel by lazy {
+        actionsViewModelFactory.create()
+    }
+
     override suspend fun activate() {
-        viewModel.activate()
+        actionsViewModel.activate()
     }
 
     override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
-        viewModel.destinationScenes
+        actionsViewModel.actions
 
     @Composable
     override fun SceneScope.Content(
@@ -170,7 +176,7 @@
     ) =
         ShadeScene(
             notificationStackScrollView.get(),
-            viewModel = viewModel,
+            viewModel = rememberViewModel { contentViewModelFactory.create() },
             notificationsPlaceholderViewModel = notificationsPlaceholderViewModel,
             createTintedIconManager = tintedIconManagerFactory::create,
             createBatteryMeterViewController = batteryMeterViewControllerFactory::create,
@@ -196,7 +202,7 @@
 @Composable
 private fun SceneScope.ShadeScene(
     notificationStackScrollView: NotificationScrollView,
-    viewModel: ShadeSceneViewModel,
+    viewModel: ShadeSceneContentViewModel,
     notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel,
     createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
     createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
@@ -242,7 +248,7 @@
 @Composable
 private fun SceneScope.SingleShade(
     notificationStackScrollView: NotificationScrollView,
-    viewModel: ShadeSceneViewModel,
+    viewModel: ShadeSceneContentViewModel,
     notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel,
     createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
     createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
@@ -261,7 +267,7 @@
             key = QuickSettings.SharedValues.TilesSquishiness,
             canOverflow = false
         )
-    val isClickable by viewModel.isClickable.collectAsStateWithLifecycle()
+    val isEmptySpaceClickable by viewModel.isEmptySpaceClickable.collectAsStateWithLifecycle()
     val isMediaVisible by viewModel.isMediaVisible.collectAsStateWithLifecycle()
 
     val shouldPunchHoleBehindScrim =
@@ -276,6 +282,13 @@
 
     val navBarHeight = WindowInsets.systemBars.asPaddingValues().calculateBottomPadding()
 
+    val mediaOffsetProvider = remember {
+        ShadeMediaOffsetProvider.Qqs(
+            { @Suppress("UNUSED_EXPRESSION") tileSquishiness },
+            viewModel.qsSceneAdapter,
+        )
+    }
+
     Box(
         modifier =
             modifier.thenIf(shouldPunchHoleBehindScrim) {
@@ -299,9 +312,9 @@
                             horizontalAlignment = Alignment.CenterHorizontally,
                             modifier =
                                 Modifier.fillMaxWidth()
-                                    .thenIf(isClickable) {
+                                    .thenIf(isEmptySpaceClickable) {
                                         Modifier.clickable(
-                                            onClick = { viewModel.onContentClicked() }
+                                            onClick = { viewModel.onEmptySpaceClicked() }
                                         )
                                     }
                                     .thenIf(cutoutLocation != CutoutLocation.CENTER) {
@@ -309,7 +322,7 @@
                                     },
                         ) {
                             CollapsedShadeHeader(
-                                viewModel = viewModel.shadeHeaderViewModel,
+                                viewModelFactory = viewModel.shadeHeaderViewModelFactory,
                                 createTintedIconManager = createTintedIconManager,
                                 createBatteryMeterViewController = createBatteryMeterViewController,
                                 statusBarIconController = statusBarIconController,
@@ -328,12 +341,12 @@
                                     )
                                 }
 
-                                MediaCarousel(
+                                ShadeMediaCarousel(
                                     isVisible = isMediaVisible,
                                     mediaHost = mediaHost,
+                                    mediaOffsetProvider = mediaOffsetProvider,
                                     modifier =
-                                        Modifier.fillMaxWidth()
-                                            .layoutId(QSMediaMeasurePolicy.LayoutId.Media),
+                                        Modifier.layoutId(QSMediaMeasurePolicy.LayoutId.Media),
                                     carouselController = mediaCarouselController,
                                 )
                             }
@@ -361,6 +374,8 @@
                             maxScrimTop = { maxNotifScrimTop.value },
                             shadeMode = ShadeMode.Single,
                             shouldPunchHoleBehindScrim = shouldPunchHoleBehindScrim,
+                            onEmptySpaceClick =
+                                viewModel::onEmptySpaceClicked.takeIf { isEmptySpaceClickable },
                         )
                     },
                 )
@@ -407,7 +422,7 @@
 @Composable
 private fun SceneScope.SplitShade(
     notificationStackScrollView: NotificationScrollView,
-    viewModel: ShadeSceneViewModel,
+    viewModel: ShadeSceneContentViewModel,
     notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel,
     createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
     createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
@@ -468,8 +483,10 @@
         }
     }
 
-    val brightnessMirrorShowing by
-        viewModel.brightnessMirrorViewModel.isShowing.collectAsStateWithLifecycle()
+    val brightnessMirrorViewModel = rememberViewModel {
+        viewModel.brightnessMirrorViewModelFactory.create()
+    }
+    val brightnessMirrorShowing by brightnessMirrorViewModel.isShowing.collectAsStateWithLifecycle()
     val contentAlpha by
         animateFloatAsState(
             targetValue = if (brightnessMirrorShowing) 0f else 1f,
@@ -481,10 +498,18 @@
         onDispose { notificationsPlaceholderViewModel.setAlphaForBrightnessMirror(1f) }
     }
 
+    val isEmptySpaceClickable by viewModel.isEmptySpaceClickable.collectAsStateWithLifecycle()
     val isMediaVisible by viewModel.isMediaVisible.collectAsStateWithLifecycle()
 
     val brightnessMirrorShowingModifier = Modifier.graphicsLayer { alpha = contentAlpha }
 
+    val mediaOffsetProvider = remember {
+        ShadeMediaOffsetProvider.Qs(
+            { @Suppress("UNUSED_EXPRESSION") tileSquishiness },
+            viewModel.qsSceneAdapter,
+        )
+    }
+
     Box {
         Box(
             modifier =
@@ -503,7 +528,7 @@
             modifier = Modifier.fillMaxSize(),
         ) {
             CollapsedShadeHeader(
-                viewModel = viewModel.shadeHeaderViewModel,
+                viewModelFactory = viewModel.shadeHeaderViewModelFactory,
                 createTintedIconManager = createTintedIconManager,
                 createBatteryMeterViewController = createBatteryMeterViewController,
                 statusBarIconController = statusBarIconController,
@@ -522,7 +547,7 @@
                             .graphicsLayer { translationX = unfoldTranslationXForStartSide },
                 ) {
                     BrightnessMirror(
-                        viewModel = viewModel.brightnessMirrorViewModel,
+                        viewModel = brightnessMirrorViewModel,
                         qsSceneAdapter = viewModel.qsSceneAdapter,
                         // Need to use the offset measured from the container as the header
                         // has to be accounted for
@@ -558,11 +583,13 @@
                                     squishiness = { tileSquishiness },
                                 )
                             }
-                            MediaCarousel(
+
+                            ShadeMediaCarousel(
                                 isVisible = isMediaVisible,
                                 mediaHost = mediaHost,
+                                mediaOffsetProvider = mediaOffsetProvider,
                                 modifier =
-                                    Modifier.fillMaxWidth().thenIf(
+                                    Modifier.thenIf(
                                         MediaContentPicker.shouldElevateMedia(layoutState)
                                     ) {
                                         Modifier.zIndex(1f)
@@ -591,6 +618,8 @@
                     shouldPunchHoleBehindScrim = false,
                     shouldReserveSpaceForNavBar = false,
                     shadeMode = ShadeMode.Split,
+                    onEmptySpaceClick =
+                        viewModel::onEmptySpaceClicked.takeIf { isEmptySpaceClickable },
                     modifier =
                         Modifier.weight(1f)
                             .fillMaxHeight()
@@ -606,3 +635,25 @@
         )
     }
 }
+
+@Composable
+private fun SceneScope.ShadeMediaCarousel(
+    isVisible: Boolean,
+    mediaHost: MediaHost,
+    carouselController: MediaCarouselController,
+    mediaOffsetProvider: ShadeMediaOffsetProvider,
+    modifier: Modifier = Modifier,
+) {
+    MediaCarousel(
+        modifier = modifier.fillMaxWidth(),
+        isVisible = isVisible,
+        mediaHost = mediaHost,
+        carouselController = carouselController,
+        offsetProvider =
+            if (MediaContentPicker.shouldElevateMedia(layoutState)) {
+                null
+            } else {
+                { mediaOffsetProvider.offset }
+            }
+    )
+}
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 9da2a1b..5ffb6f8 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
@@ -50,7 +50,7 @@
 ) {
     val accessibilityTitle = stringResource(R.string.accessibility_volume_settings)
     val state: VolumePanelState by viewModel.volumePanelState.collectAsStateWithLifecycle()
-    val components by viewModel.componentsLayout.collectAsStateWithLifecycle(null)
+    val components by viewModel.componentsLayout.collectAsStateWithLifecycle()
 
     with(VolumePanelComposeScope(state)) {
         components?.let { componentsState ->
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
new file mode 100644
index 0000000..5eabd22
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.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.compose.animation.scene
+
+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 kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+
+internal fun CoroutineScope.animateContent(
+    transition: ContentState.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()
+            }
+        }
+}
+
+internal class OneOffAnimation {
+    /**
+     * The animatable used to animate this transition.
+     *
+     * Note: This is lateinit because we need to first create this object so that
+     * [SceneTransitionLayoutState] can compute the transformations and animation spec associated to
+     * the transition, which is needed to initialize this Animatable.
+     */
+    lateinit var animatable: Animatable<Float, AnimationVector1D>
+
+    /** The job that is animating [animatable]. */
+    lateinit var job: Job
+
+    val progress: Float
+        get() = animatable.value
+
+    val progressVelocity: Float
+        get() = animatable.velocity
+
+    fun finish(): Job = job
+}
+
+// TODO(b/290184746): Compute a good default visibility threshold that depends on the layout size
+// and screen density.
+internal const val ProgressVisibilityThreshold = 1e-3f
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 e13ca391..ae5a84b 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
@@ -120,7 +120,7 @@
 }
 
 @Deprecated(
-    "Use animateSceneFloatAsState() instead",
+    "Use animateContentFloatAsState() instead",
     replaceWith = ReplaceWith("animateContentFloatAsState(value, key, canOverflow)")
 )
 @Composable
@@ -171,7 +171,7 @@
 }
 
 @Deprecated(
-    "Use animateSceneDpAsState() instead",
+    "Use animateContentDpAsState() instead",
     replaceWith = ReplaceWith("animateContentDpAsState(value, key, canOverflow)")
 )
 @Composable
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 1fc1f98..68a6c98 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
@@ -16,15 +16,10 @@
 
 package com.android.compose.animation.scene
 
-import androidx.compose.animation.core.Animatable
-import androidx.compose.animation.core.AnimationVector1D
-import androidx.compose.animation.core.SpringSpec
 import com.android.compose.animation.scene.content.state.TransitionState
 import kotlin.math.absoluteValue
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.CoroutineStart
 import kotlinx.coroutines.Job
-import kotlinx.coroutines.launch
 
 /**
  * Transition to [target] using a canned animation. This function will try to be smart and take over
@@ -50,7 +45,7 @@
 
     return when (transitionState) {
         is TransitionState.Idle -> {
-            animate(
+            animateToScene(
                 layoutState,
                 target,
                 transitionKey,
@@ -80,13 +75,11 @@
                 } else {
                     // The transition is in progress: start the canned animation at the same
                     // progress as it was in.
-                    animate(
+                    animateToScene(
                         layoutState,
                         target,
                         transitionKey,
                         isInitiatedByUserInput,
-                        initialProgress = progress,
-                        initialVelocity = transitionState.progressVelocity,
                         replacedTransition = transitionState,
                     )
                 }
@@ -102,13 +95,11 @@
                     layoutState.finishTransition(transitionState, target)
                     null
                 } else {
-                    animate(
+                    animateToScene(
                         layoutState,
                         target,
                         transitionKey,
                         isInitiatedByUserInput,
-                        initialProgress = progress,
-                        initialVelocity = transitionState.progressVelocity,
                         reversed = true,
                         replacedTransition = transitionState,
                     )
@@ -140,7 +131,7 @@
                     animateToScene(layoutState, animateFrom, transitionKey = null)
                 }
 
-                animate(
+                animateToScene(
                     layoutState,
                     target,
                     transitionKey,
@@ -154,103 +145,68 @@
     }
 }
 
-private fun CoroutineScope.animate(
+private fun CoroutineScope.animateToScene(
     layoutState: MutableSceneTransitionLayoutStateImpl,
     targetScene: SceneKey,
     transitionKey: TransitionKey?,
     isInitiatedByUserInput: Boolean,
     replacedTransition: TransitionState.Transition?,
-    initialProgress: Float = 0f,
-    initialVelocity: Float = 0f,
     reversed: Boolean = false,
     fromScene: SceneKey = layoutState.transitionState.currentScene,
     chain: Boolean = true,
 ): TransitionState.Transition {
+    val oneOffAnimation = OneOffAnimation()
     val targetProgress = if (reversed) 0f else 1f
     val transition =
         if (reversed) {
-            OneOffTransition(
+            OneOffSceneTransition(
                 key = transitionKey,
                 fromScene = targetScene,
                 toScene = fromScene,
                 currentScene = targetScene,
                 isInitiatedByUserInput = isInitiatedByUserInput,
-                isUserInputOngoing = false,
                 replacedTransition = replacedTransition,
+                oneOffAnimation = oneOffAnimation,
             )
         } else {
-            OneOffTransition(
+            OneOffSceneTransition(
                 key = transitionKey,
                 fromScene = fromScene,
                 toScene = targetScene,
                 currentScene = targetScene,
                 isInitiatedByUserInput = isInitiatedByUserInput,
-                isUserInputOngoing = false,
                 replacedTransition = replacedTransition,
+                oneOffAnimation = oneOffAnimation,
             )
         }
 
-    // Change the current layout state to start this new transition. This will compute the
-    // TransformationSpec associated to this transition, which we need to initialize the Animatable
-    // that will actually animate it.
-    layoutState.startTransition(transition, chain)
-
-    // 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 animatable =
-        Animatable(initialProgress, visibilityThreshold = visibilityThreshold).also {
-            transition.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.
-    transition.job =
-        launch(start = CoroutineStart.ATOMIC) {
-            try {
-                animatable.animateTo(targetProgress, animationSpec, initialVelocity)
-            } finally {
-                layoutState.finishTransition(transition, targetScene)
-            }
-        }
+    animateContent(
+        transition = transition,
+        oneOffAnimation = oneOffAnimation,
+        targetProgress = targetProgress,
+        startTransition = { layoutState.startTransition(transition, chain) },
+        finishTransition = { layoutState.finishTransition(transition, targetScene) },
+    )
 
     return transition
 }
 
-private class OneOffTransition(
+private class OneOffSceneTransition(
     override val key: TransitionKey?,
     fromScene: SceneKey,
     toScene: SceneKey,
     override val currentScene: SceneKey,
     override val isInitiatedByUserInput: Boolean,
-    override val isUserInputOngoing: Boolean,
     replacedTransition: TransitionState.Transition?,
+    private val oneOffAnimation: OneOffAnimation,
 ) : TransitionState.Transition(fromScene, toScene, replacedTransition) {
-    /**
-     * The animatable used to animate this transition.
-     *
-     * Note: This is lateinit because we need to first create this Transition object so that
-     * [SceneTransitionLayoutState] can compute the transformations and animation spec associated to
-     * it, which is need to initialize this Animatable.
-     */
-    lateinit var animatable: Animatable<Float, AnimationVector1D>
-
-    /** The job that is animating [animatable]. */
-    lateinit var job: Job
-
     override val progress: Float
-        get() = animatable.value
+        get() = oneOffAnimation.progress
 
     override val progressVelocity: Float
-        get() = animatable.velocity
+        get() = oneOffAnimation.progressVelocity
 
-    override fun finish(): Job = job
+    override val isUserInputOngoing: Boolean = false
+
+    override fun finish(): Job = oneOffAnimation.finish()
 }
-
-// TODO(b/290184746): Compute a good default visibility threshold that depends on the layout size
-// and screen density.
-internal const val ProgressVisibilityThreshold = 1e-3f
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 a43028a..712fe6b 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
@@ -459,38 +459,11 @@
 
             animateTo(targetScene = targetScene, targetOffset = targetOffset)
         } else {
-            // We are doing an overscroll animation between scenes. In this case, we can also start
-            // from the idle position.
-
-            val startFromIdlePosition = swipeTransition.dragOffset == 0f
-
-            if (startFromIdlePosition) {
-                // If there is a target scene, we start the overscroll animation.
-                val result = swipes.findUserActionResultStrict(velocity)
-                if (result == null) {
-                    // We will not animate
-                    swipeTransition.snapToScene(fromScene.key)
-                    return 0f
-                }
-
-                val newSwipeTransition =
-                    SwipeTransition(
-                            layoutState = layoutState,
-                            coroutineScope = draggableHandler.coroutineScope,
-                            fromScene = fromScene,
-                            result = result,
-                            swipes = swipes,
-                            layoutImpl = draggableHandler.layoutImpl,
-                            orientation = draggableHandler.orientation,
-                        )
-                        .apply { _currentScene = swipeTransition._currentScene }
-
-                updateTransition(newSwipeTransition)
-                animateTo(targetScene = fromScene, targetOffset = 0f)
-            } else {
-                // We were between two scenes: animate to the initial scene.
-                animateTo(targetScene = fromScene, targetOffset = 0f)
+            // We are doing an overscroll preview animation between scenes.
+            check(fromScene == swipeTransition._currentScene) {
+                "canChangeScene is false but currentScene != fromScene"
             }
+            animateTo(targetScene = fromScene, targetOffset = 0f)
         }
 
         // The onStop animation consumes any remaining velocity.
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 0105af3..fe16ef751 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
@@ -127,16 +127,7 @@
         return coroutineScope
             .launch(start = CoroutineStart.ATOMIC) {
                 try {
-                    if (currentScene == toScene) {
-                        animatable.animateTo(targetProgress, transformationSpec.progressSpec)
-                    } else {
-                        // If the back gesture is cancelled, the progress is animated back to 0f by
-                        // the system. But we need this animate call anyways because
-                        // PredictiveBackHandler doesn't guarantee that it ends at 0f. Since the
-                        // remaining change in progress is usually very small, the progressSpec is
-                        // omitted and the default spring spec used instead.
-                        animatable.animateTo(targetProgress)
-                    }
+                    animatable.animateTo(targetProgress)
                 } finally {
                     state.finishTransition(this@PredictiveBackTransition, scene)
                 }
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 a30b780..79b3856 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
@@ -17,6 +17,8 @@
 package com.android.compose.animation.scene
 
 import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.Easing
+import androidx.compose.animation.core.LinearEasing
 import androidx.compose.animation.core.SpringSpec
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.ui.geometry.Offset
@@ -140,6 +142,7 @@
     fun fractionRange(
         start: Float? = null,
         end: Float? = null,
+        easing: Easing = LinearEasing,
         builder: PropertyTransformationBuilder.() -> Unit,
     )
 }
@@ -182,6 +185,7 @@
     fun timestampRange(
         startMillis: Int? = null,
         endMillis: Int? = null,
+        easing: Easing = LinearEasing,
         builder: PropertyTransformationBuilder.() -> Unit,
     )
 
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 6515cb8..a63b19a 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
@@ -18,6 +18,7 @@
 
 import androidx.compose.animation.core.AnimationSpec
 import androidx.compose.animation.core.DurationBasedAnimationSpec
+import androidx.compose.animation.core.Easing
 import androidx.compose.animation.core.Spring
 import androidx.compose.animation.core.SpringSpec
 import androidx.compose.animation.core.VectorConverter
@@ -163,9 +164,10 @@
     override fun fractionRange(
         start: Float?,
         end: Float?,
+        easing: Easing,
         builder: PropertyTransformationBuilder.() -> Unit
     ) {
-        range = TransformationRange(start, end)
+        range = TransformationRange(start, end, easing)
         builder()
         range = null
     }
@@ -251,6 +253,7 @@
     override fun timestampRange(
         startMillis: Int?,
         endMillis: Int?,
+        easing: Easing,
         builder: PropertyTransformationBuilder.() -> Unit
     ) {
         if (startMillis != null && (startMillis < 0 || startMillis > durationMillis)) {
@@ -263,7 +266,7 @@
 
         val start = startMillis?.let { it.toFloat() / durationMillis }
         val end = endMillis?.let { it.toFloat() / durationMillis }
-        fractionRange(start, end, builder)
+        fractionRange(start, end, easing, builder)
     }
 }
 
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 77ec891..eda8ede 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
@@ -16,6 +16,8 @@
 
 package com.android.compose.animation.scene.transformation
 
+import androidx.compose.animation.core.Easing
+import androidx.compose.animation.core.LinearEasing
 import androidx.compose.ui.util.fastCoerceAtLeast
 import androidx.compose.ui.util.fastCoerceAtMost
 import androidx.compose.ui.util.fastCoerceIn
@@ -90,11 +92,13 @@
 data class TransformationRange(
     val start: Float,
     val end: Float,
+    val easing: Easing,
 ) {
     constructor(
         start: Float? = null,
-        end: Float? = null
-    ) : this(start ?: BoundUnspecified, end ?: BoundUnspecified)
+        end: Float? = null,
+        easing: Easing = LinearEasing,
+    ) : this(start ?: BoundUnspecified, end ?: BoundUnspecified, easing)
 
     init {
         require(!start.isSpecified() || (start in 0f..1f))
@@ -103,17 +107,20 @@
     }
 
     /** Reverse this range. */
-    fun reversed() = TransformationRange(start = reverseBound(end), end = reverseBound(start))
+    fun reversed() =
+        TransformationRange(start = reverseBound(end), end = reverseBound(start), easing = easing)
 
     /** Get the progress of this range given the global [transitionProgress]. */
     fun progress(transitionProgress: Float): Float {
-        return when {
-            start.isSpecified() && end.isSpecified() ->
-                ((transitionProgress - start) / (end - start)).fastCoerceIn(0f, 1f)
-            !start.isSpecified() && !end.isSpecified() -> transitionProgress
-            end.isSpecified() -> (transitionProgress / end).fastCoerceAtMost(1f)
-            else -> ((transitionProgress - start) / (1f - start)).fastCoerceAtLeast(0f)
-        }
+        val progress =
+            when {
+                start.isSpecified() && end.isSpecified() ->
+                    ((transitionProgress - start) / (end - start)).fastCoerceIn(0f, 1f)
+                !start.isSpecified() && !end.isSpecified() -> transitionProgress
+                end.isSpecified() -> (transitionProgress / end).fastCoerceAtMost(1f)
+                else -> ((transitionProgress - start) / (1f - start)).fastCoerceAtLeast(0f)
+            }
+        return easing.transform(progress)
     }
 
     private fun Float.isSpecified() = this != BoundUnspecified
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 c414fbe..0eaecb0 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,8 +18,6 @@
 
 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.runtime.rememberCoroutineScope
@@ -61,23 +59,7 @@
 
     @Test
     fun testPredictiveBack() {
-        val transitionFrames = 2
-        val layoutState =
-            rule.runOnUiThread {
-                MutableSceneTransitionLayoutState(
-                    SceneA,
-                    transitions =
-                        transitions {
-                            from(SceneA, to = SceneB) {
-                                spec =
-                                    tween(
-                                        durationMillis = transitionFrames * 16,
-                                        easing = LinearEasing
-                                    )
-                            }
-                        }
-                )
-            }
+        val layoutState = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
         rule.setContent {
             SceneTransitionLayout(layoutState) {
                 scene(SceneA, mapOf(Back to SceneB)) { Box(Modifier.fillMaxSize()) }
@@ -106,27 +88,12 @@
         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).isTransition()
-        // 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()
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 68240b5..bed6cef 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
@@ -16,6 +16,7 @@
 
 package com.android.compose.animation.scene
 
+import androidx.compose.animation.core.CubicBezierEasing
 import androidx.compose.animation.core.SpringSpec
 import androidx.compose.animation.core.TweenSpec
 import androidx.compose.animation.core.spring
@@ -107,6 +108,13 @@
                 fractionRange(start = 0.1f, end = 0.8f) { fade(TestElements.Foo) }
                 fractionRange(start = 0.2f) { fade(TestElements.Foo) }
                 fractionRange(end = 0.9f) { fade(TestElements.Foo) }
+                fractionRange(
+                    start = 0.1f,
+                    end = 0.8f,
+                    easing = CubicBezierEasing(0.1f, 0.1f, 0f, 1f)
+                ) {
+                    fade(TestElements.Foo)
+                }
             }
         }
 
@@ -118,6 +126,11 @@
                 TransformationRange(start = 0.1f, end = 0.8f),
                 TransformationRange(start = 0.2f, end = TransformationRange.BoundUnspecified),
                 TransformationRange(start = TransformationRange.BoundUnspecified, end = 0.9f),
+                TransformationRange(
+                    start = 0.1f,
+                    end = 0.8f,
+                    CubicBezierEasing(0.1f, 0.1f, 0f, 1f)
+                ),
             )
     }
 
@@ -130,6 +143,13 @@
                 timestampRange(startMillis = 100, endMillis = 300) { fade(TestElements.Foo) }
                 timestampRange(startMillis = 200) { fade(TestElements.Foo) }
                 timestampRange(endMillis = 400) { fade(TestElements.Foo) }
+                timestampRange(
+                    startMillis = 100,
+                    endMillis = 300,
+                    easing = CubicBezierEasing(0.1f, 0.1f, 0f, 1f)
+                ) {
+                    fade(TestElements.Foo)
+                }
             }
         }
 
@@ -141,6 +161,11 @@
                 TransformationRange(start = 100 / 500f, end = 300 / 500f),
                 TransformationRange(start = 200 / 500f, end = TransformationRange.BoundUnspecified),
                 TransformationRange(start = TransformationRange.BoundUnspecified, end = 400 / 500f),
+                TransformationRange(
+                    start = 100 / 500f,
+                    end = 300 / 500f,
+                    easing = CubicBezierEasing(0.1f, 0.1f, 0f, 1f)
+                ),
             )
     }
 
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/EasingTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/EasingTest.kt
new file mode 100644
index 0000000..07901f2
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/EasingTest.kt
@@ -0,0 +1,126 @@
+/*
+ * 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.compose.animation.scene.transformation
+
+import androidx.compose.animation.core.CubicBezierEasing
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.dp
+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.test.assertSizeIsEqualTo
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class EasingTest {
+    @get:Rule val rule = createComposeRule()
+
+    @Test
+    fun testFractionRangeEasing() {
+        val easing = CubicBezierEasing(0.1f, 0.1f, 0f, 1f)
+        rule.testTransition(
+            fromSceneContent = { Box(Modifier.size(100.dp).element(TestElements.Foo)) },
+            toSceneContent = { Box(Modifier.size(100.dp).element(TestElements.Bar)) },
+            transition = {
+                // Scale during 4 frames.
+                spec = tween(16 * 4, easing = LinearEasing)
+                fractionRange(easing = easing) {
+                    scaleSize(TestElements.Foo, width = 0f, height = 0f)
+                    scaleSize(TestElements.Bar, width = 0f, height = 0f)
+                }
+            },
+        ) {
+            // Foo is entering, is 100dp x 100dp at rest and is scaled by (2, 0.5) during the
+            // transition so it starts at 200dp x 50dp.
+            before { onElement(TestElements.Bar).assertDoesNotExist() }
+            at(0) {
+                onElement(TestElements.Foo).assertSizeIsEqualTo(100.dp, 100.dp)
+                onElement(TestElements.Bar).assertSizeIsEqualTo(0.dp, 0.dp)
+            }
+            at(16) {
+                // 25% linear progress is mapped to 68.5% eased progress
+                onElement(TestElements.Foo).assertSizeIsEqualTo(31.5.dp, 31.5.dp)
+                onElement(TestElements.Bar).assertSizeIsEqualTo(68.5.dp, 68.5.dp)
+            }
+            at(32) {
+                // 50% linear progress is mapped to 89.5% eased progress
+                onElement(TestElements.Foo).assertSizeIsEqualTo(10.5.dp, 10.5.dp)
+                onElement(TestElements.Bar).assertSizeIsEqualTo(89.5.dp, 89.5.dp)
+            }
+            at(48) {
+                // 75% linear progress is mapped to 97.8% eased progress
+                onElement(TestElements.Foo).assertSizeIsEqualTo(2.2.dp, 2.2.dp)
+                onElement(TestElements.Bar).assertSizeIsEqualTo(97.8.dp, 97.8.dp)
+            }
+            after {
+                onElement(TestElements.Foo).assertDoesNotExist()
+                onElement(TestElements.Bar).assertSizeIsEqualTo(100.dp, 100.dp)
+            }
+        }
+    }
+
+    @Test
+    fun testTimestampRangeEasing() {
+        val easing = CubicBezierEasing(0.1f, 0.1f, 0f, 1f)
+        rule.testTransition(
+            fromSceneContent = { Box(Modifier.size(100.dp).element(TestElements.Foo)) },
+            toSceneContent = { Box(Modifier.size(100.dp).element(TestElements.Bar)) },
+            transition = {
+                // Scale during 4 frames.
+                spec = tween(16 * 4, easing = LinearEasing)
+                timestampRange(easing = easing) {
+                    scaleSize(TestElements.Foo, width = 0f, height = 0f)
+                    scaleSize(TestElements.Bar, width = 0f, height = 0f)
+                }
+            },
+        ) {
+            // Foo is entering, is 100dp x 100dp at rest and is scaled by (2, 0.5) during the
+            // transition so it starts at 200dp x 50dp.
+            before { onElement(TestElements.Bar).assertDoesNotExist() }
+            at(0) {
+                onElement(TestElements.Foo).assertSizeIsEqualTo(100.dp, 100.dp)
+                onElement(TestElements.Bar).assertSizeIsEqualTo(0.dp, 0.dp)
+            }
+            at(16) {
+                // 25% linear progress is mapped to 68.5% eased progress
+                onElement(TestElements.Foo).assertSizeIsEqualTo(31.5.dp, 31.5.dp)
+                onElement(TestElements.Bar).assertSizeIsEqualTo(68.5.dp, 68.5.dp)
+            }
+            at(32) {
+                // 50% linear progress is mapped to 89.5% eased progress
+                onElement(TestElements.Foo).assertSizeIsEqualTo(10.5.dp, 10.5.dp)
+                onElement(TestElements.Bar).assertSizeIsEqualTo(89.5.dp, 89.5.dp)
+            }
+            at(48) {
+                // 75% linear progress is mapped to 97.8% eased progress
+                onElement(TestElements.Foo).assertSizeIsEqualTo(2.2.dp, 2.2.dp)
+                onElement(TestElements.Bar).assertSizeIsEqualTo(97.8.dp, 97.8.dp)
+            }
+            after {
+                onElement(TestElements.Foo).assertDoesNotExist()
+                onElement(TestElements.Bar).assertSizeIsEqualTo(100.dp, 100.dp)
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index 9b725eb..954155d 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -88,7 +88,6 @@
     val clockBuffers: ClockMessageBuffers? = null,
     val keepAllLoaded: Boolean,
     subTag: String,
-    var isTransitClockEnabled: Boolean = false,
     val assert: ThreadAssert = ThreadAssert(),
 ) {
     private val TAG = "${ClockRegistry::class.simpleName} ($subTag)"
@@ -188,10 +187,6 @@
                 var isClockListChanged = false
                 for (clock in plugin.getClocks()) {
                     val id = clock.clockId
-                    if (!isTransitClockEnabled && id == "DIGITAL_CLOCK_METRO") {
-                        continue
-                    }
-
                     val info =
                         availableClocks.concurrentGetOrPut(id, ClockInfo(clock, plugin, manager)) {
                             isClockListChanged = true
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
index 9d62e38..8721c78 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
@@ -182,9 +182,6 @@
         const val FLAG_NAME_WALLPAPER_PICKER_UI_FOR_AIWP = "wallpaper_picker_ui_for_aiwp"
 
         /** Flag denoting transit clock are enabled in wallpaper picker. */
-        const val FLAG_NAME_TRANSIT_CLOCK = "lockscreen_custom_transit_clock"
-
-        /** Flag denoting transit clock are enabled in wallpaper picker. */
         const val FLAG_NAME_PAGE_TRANSITIONS = "wallpaper_picker_page_transitions"
 
         /** Flag denoting adding apply button to wallpaper picker's grid preview page. */
diff --git a/packages/SystemUI/lint-baseline.xml b/packages/SystemUI/lint-baseline.xml
index b4c839f..7577147 100644
--- a/packages/SystemUI/lint-baseline.xml
+++ b/packages/SystemUI/lint-baseline.xml
@@ -32157,4 +32157,631 @@
             column="6"/>
     </issue>
 
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.registerContentObserver()`"
+        errorLine1="                contentResolver.registerContentObserver("
+        errorLine2="                                ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt"
+            line="154"
+            column="33"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.registerContentObserver()`"
+        errorLine1="            resolver.registerContentObserver(ALWAYS_ON_DISPLAY_CONSTANTS_URI,"
+        errorLine2="                     ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java"
+            line="164"
+            column="22"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.registerContentObserver()`"
+        errorLine1="                resolver.registerContentObserver("
+        errorLine2="                         ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/util/animation/data/repository/AnimationStatusRepository.kt"
+            line="61"
+            column="26"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.unregisterContentObserver()`"
+        errorLine1="                awaitClose { resolver.unregisterContentObserver(observer) }"
+        errorLine2="                                      ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/util/animation/data/repository/AnimationStatusRepository.kt"
+            line="67"
+            column="39"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.unregisterContentObserver()`"
+        errorLine1="                    mContentResolver.unregisterContentObserver(mSettingObserver);"
+        errorLine2="                                     ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java"
+            line="123"
+            column="38"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.unregisterContentObserver()`"
+        errorLine1="        mContentResolver.unregisterContentObserver(mSettingObserver);"
+        errorLine2="                         ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java"
+            line="180"
+            column="26"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.registerContentObserver()`"
+        errorLine1="        mContentResolver.registerContentObserver("
+        errorLine2="                         ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java"
+            line="211"
+            column="26"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.registerContentObserver()`"
+        errorLine1="        mContentResolver.registerContentObserver("
+        errorLine2="                         ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java"
+            line="219"
+            column="26"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.registerContentObserver()`"
+        errorLine1="        resolver.registerContentObserver("
+        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/demomode/DemoModeAvailabilityTracker.kt"
+            line="46"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.registerContentObserver()`"
+        errorLine1="        resolver.registerContentObserver("
+        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/demomode/DemoModeAvailabilityTracker.kt"
+            line="48"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.unregisterContentObserver()`"
+        errorLine1="        resolver.unregisterContentObserver(allowedObserver)"
+        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/demomode/DemoModeAvailabilityTracker.kt"
+            line="54"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.unregisterContentObserver()`"
+        errorLine1="        resolver.unregisterContentObserver(onObserver)"
+        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/demomode/DemoModeAvailabilityTracker.kt"
+            line="55"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.registerContentObserver()`"
+        errorLine1="                resolver.registerContentObserver(mQuickPickupGesture, false, this,"
+        errorLine2="                         ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java"
+            line="511"
+            column="26"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.registerContentObserver()`"
+        errorLine1="                resolver.registerContentObserver(mPickupGesture, false, this, UserHandle.USER_ALL);"
+        errorLine2="                         ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java"
+            line="513"
+            column="26"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.registerContentObserver()`"
+        errorLine1="                resolver.registerContentObserver(mAlwaysOnEnabled, false, this,"
+        errorLine2="                         ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java"
+            line="514"
+            column="26"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.registerContentObserver()`"
+        errorLine1="        mContext.getContentResolver().registerContentObserver("
+        errorLine2="                                      ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java"
+            line="2470"
+            column="39"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.registerContentObserver()`"
+        errorLine1="        mContext.getContentResolver().registerContentObserver("
+        errorLine2="                                      ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java"
+            line="3077"
+            column="39"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.unregisterContentObserver()`"
+        errorLine1="            mContext.getContentResolver().unregisterContentObserver(mDeviceProvisionedObserver);"
+        errorLine2="                                          ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java"
+            line="3168"
+            column="43"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.unregisterContentObserver()`"
+        errorLine1="            mContext.getContentResolver().unregisterContentObserver(mDeviceProvisionedObserver);"
+        errorLine2="                                          ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java"
+            line="3957"
+            column="43"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.unregisterContentObserver()`"
+        errorLine1="            mContext.getContentResolver().unregisterContentObserver(mTimeFormatChangeObserver);"
+        errorLine2="                                          ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java"
+            line="3961"
+            column="43"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.registerContentObserver()`"
+        errorLine1="        contentResolver.registerContentObserver("
+        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt"
+            line="486"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.registerContentObserver()`"
+        errorLine1="        contentResolver.registerContentObserver("
+        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt"
+            line="492"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.unregisterContentObserver()`"
+        errorLine1="        contentResolver.unregisterContentObserver(settingsObserver)"
+        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt"
+            line="543"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.registerContentObserver()`"
+        errorLine1="        contentResolver.registerContentObserver("
+        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/smartspace/filters/LockscreenTargetFilter.kt"
+            line="82"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.unregisterContentObserver()`"
+        errorLine1="        contentResolver.unregisterContentObserver(settingsObserver)"
+        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/smartspace/filters/LockscreenTargetFilter.kt"
+            line="100"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.unregisterContentObserver()`"
+        errorLine1="        mContext.getContentResolver().unregisterContentObserver(mMenuTargetFeaturesContentObserver);"
+        errorLine2="                                      ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java"
+            line="275"
+            column="39"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.unregisterContentObserver()`"
+        errorLine1="        mContext.getContentResolver().unregisterContentObserver(mMenuSizeContentObserver);"
+        errorLine2="                                      ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java"
+            line="276"
+            column="39"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.unregisterContentObserver()`"
+        errorLine1="        mContext.getContentResolver().unregisterContentObserver(mMenuFadeOutContentObserver);"
+        errorLine2="                                      ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java"
+            line="277"
+            column="39"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.registerContentObserver()`"
+        errorLine1="        mContext.getContentResolver().registerContentObserver(Global.getUriFor(Global.MOBILE_DATA),"
+        errorLine2="                                      ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java"
+            line="200"
+            column="39"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.registerContentObserver()`"
+        errorLine1="        mContext.getContentResolver().registerContentObserver(Global.getUriFor("
+        errorLine2="                                      ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java"
+            line="202"
+            column="39"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.unregisterContentObserver()`"
+        errorLine1="        mContext.getContentResolver().unregisterContentObserver(mObserver);"
+        errorLine2="                                      ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java"
+            line="212"
+            column="39"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.registerContentObserver()`"
+        errorLine1="        context.contentResolver.registerContentObserver("
+        errorLine2="                                ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NaturalScrollingSettingObserver.kt"
+            line="54"
+            column="33"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.registerContentObserver()`"
+        errorLine1="        mContentResolver.registerContentObserver("
+        errorLine2="                         ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java"
+            line="253"
+            column="26"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.registerContentObserver()`"
+        errorLine1="        mContentResolver.registerContentObserver("
+        errorLine2="                         ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java"
+            line="256"
+            column="26"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.registerContentObserver()`"
+        errorLine1="        mContentResolver.registerContentObserver("
+        errorLine2="                         ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java"
+            line="259"
+            column="26"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.registerContentObserver()`"
+        errorLine1="        mContentResolver.registerContentObserver("
+        errorLine2="                         ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java"
+            line="262"
+            column="26"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.unregisterContentObserver()`"
+        errorLine1="        mContentResolver.unregisterContentObserver(mAssistContentObserver);"
+        errorLine2="                         ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java"
+            line="295"
+            column="26"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.registerContentObserver()`"
+        errorLine1="        mContext.getContentResolver().registerContentObserver("
+        errorLine2="                                      ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java"
+            line="395"
+            column="39"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.registerContentObserver()`"
+        errorLine1="        mContext.getContentResolver().registerContentObserver("
+        errorLine2="                                      ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java"
+            line="400"
+            column="39"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.registerContentObserver()`"
+        errorLine1="        mContentResolver.registerContentObserver("
+        errorLine2="                         ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java"
+            line="3675"
+            column="26"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.unregisterContentObserver()`"
+        errorLine1="            mContentResolver.unregisterContentObserver(mSettingsChangeObserver);"
+        errorLine2="                             ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java"
+            line="4705"
+            column="30"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.registerContentObserver()`"
+        errorLine1="        resolver.registerContentObserver(Settings.Global.getUriFor("
+        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/power/PowerUI.java"
+            line="191"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.registerContentObserver()`"
+        errorLine1="        resolver.registerContentObserver("
+        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/power/PowerUI.java"
+            line="205"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.registerContentObserver()`"
+        errorLine1="        resolver.registerContentObserver("
+        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/power/PowerUI.java"
+            line="216"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.registerContentObserver()`"
+        errorLine1="        mContext.getContentResolver().registerContentObserver("
+        errorLine2="                                      ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java"
+            line="178"
+            column="39"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.unregisterContentObserver()`"
+        errorLine1="        mContext.getContentResolver().unregisterContentObserver(mDeveloperSettingsObserver);"
+        errorLine2="                                      ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java"
+            line="186"
+            column="39"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.registerContentObserver()`"
+        errorLine1="            mContentResolver.registerContentObserver("
+        errorLine2="                             ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/accessibility/SecureSettingsContentObserver.java"
+            line="83"
+            column="30"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.unregisterContentObserver()`"
+        errorLine1="            mContentResolver.unregisterContentObserver(mContentObserver);"
+        errorLine2="                             ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/accessibility/SecureSettingsContentObserver.java"
+            line="100"
+            column="30"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.registerContentObserver()`"
+        errorLine1="            mContentResolver.registerContentObserver(uri, false, mObserver, mCurrentUser);"
+        errorLine2="                             ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java"
+            line="219"
+            column="30"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.unregisterContentObserver()`"
+        errorLine1="        mContentResolver.unregisterContentObserver(mObserver);"
+        errorLine2="                         ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java"
+            line="241"
+            column="26"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.registerContentObserver()`"
+        errorLine1="            mContentResolver.registerContentObserver(uri, false, mObserver, mCurrentUser);"
+        errorLine2="                             ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java"
+            line="243"
+            column="30"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.registerContentObserver()`"
+        errorLine1="            mContext.getContentResolver().registerContentObserver(ZEN_MODE_URI, false, this);"
+        errorLine2="                                          ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java"
+            line="1235"
+            column="43"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.registerContentObserver()`"
+        errorLine1="            mContext.getContentResolver().registerContentObserver(ZEN_MODE_CONFIG_URI, false, this);"
+        errorLine2="                                          ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java"
+            line="1236"
+            column="43"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.unregisterContentObserver()`"
+        errorLine1="            mContext.getContentResolver().unregisterContentObserver(this);"
+        errorLine2="                                          ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java"
+            line="1240"
+            column="43"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.unregisterContentObserver()`"
+        errorLine1="                mResolver.unregisterContentObserver(this);"
+        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java"
+            line="377"
+            column="27"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.registerContentObserver()`"
+        errorLine1="            mResolver.registerContentObserver("
+        errorLine2="                      ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java"
+            line="379"
+            column="23"/>
+    </issue>
+
+    <issue
+        id="RegisterContentObserverViaContentResolver"
+        message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`&lt;SettingsProxy>/&lt;UserSettingsProxy>.registerContentObserver()`"
+        errorLine1="            mResolver.registerContentObserver("
+        errorLine2="                      ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java"
+            line="381"
+            column="23"/>
+    </issue>
+
 </issues>
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 40ea0a0..460461a 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
@@ -37,7 +37,6 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-@android.platform.test.annotations.EnabledOnRavenwood
 class AccessibilityQsShortcutsRepositoryImplTest : SysuiTestCase() {
     @Rule @JvmField val mockitoRule: MockitoRule = MockitoJUnit.rule()
 
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 fa47a02..4e1f82c 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
@@ -37,7 +37,6 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-@android.platform.test.annotations.EnabledOnRavenwood
 class ColorCorrectionRepositoryImplTest : SysuiTestCase() {
 
     private val testUser1 = UserHandle.of(1)!!
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 9c9ee53..b99dec4 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
@@ -37,7 +37,6 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-@android.platform.test.annotations.EnabledOnRavenwood
 class ColorInversionRepositoryImplTest : SysuiTestCase() {
 
     private val testUser1 = UserHandle.of(1)!!
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 c0d481c..1378dac 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
@@ -35,7 +35,6 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-@android.platform.test.annotations.EnabledOnRavenwood
 class OneHandedModeRepositoryImplTest : SysuiTestCase() {
 
     private val testUser1 = UserHandle.of(1)!!
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 ed3b4c0..ce22e28 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
@@ -31,7 +31,6 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-@android.platform.test.annotations.EnabledOnRavenwood
 class UserA11yQsShortcutsRepositoryTest : SysuiTestCase() {
     private val secureSettings = FakeSettings()
     private val testDispatcher = StandardTestDispatcher()
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 4850085..d244482 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
@@ -62,7 +62,7 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-@EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+@EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
 @DisableFlags(Flags.FLAG_COMMUNAL_BOUNCER_DO_NOT_MODIFY_PLUGIN_OPEN)
 public class BouncerFullscreenSwipeTouchHandlerTest extends SysuiTestCase {
     private KosmosJavaAdapter mKosmos;
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 0e98b84..b85e32b 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
@@ -74,7 +74,7 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-@DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+@DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
 public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
     private KosmosJavaAdapter mKosmos;
     @Mock
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt
index 204d4b0..38ea4497 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt
@@ -79,7 +79,7 @@
 
     // Verifies that a swipe down in the gesture region is captured by the shade touch handler.
     @Test
-    @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+    @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
     fun testSwipeDown_captured() {
         val captured = swipe(Direction.DOWN)
         Truth.assertThat(captured).isTrue()
@@ -87,7 +87,7 @@
 
     // Verifies that a swipe in the upward direction is not captured.
     @Test
-    @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+    @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
     fun testSwipeUp_notCaptured() {
         val captured = swipe(Direction.UP)
 
@@ -97,7 +97,7 @@
 
     // Verifies that a swipe down forwards captured touches to central surfaces for handling.
     @Test
-    @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+    @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
     @EnableFlags(Flags.FLAG_COMMUNAL_HUB)
     fun testSwipeDown_communalEnabled_sentToCentralSurfaces() {
         kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
@@ -110,7 +110,7 @@
 
     // Verifies that a swipe down forwards captured touches to the shade view for handling.
     @Test
-    @DisableFlags(Flags.FLAG_COMMUNAL_HUB, Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+    @DisableFlags(Flags.FLAG_COMMUNAL_HUB, Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
     fun testSwipeDown_communalDisabled_sentToShadeView() {
         swipe(Direction.DOWN)
 
@@ -121,7 +121,7 @@
     // Verifies that a swipe down while dreaming forwards captured touches to the shade view for
     // handling.
     @Test
-    @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+    @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
     fun testSwipeDown_dreaming_sentToShadeView() {
         whenever(mDreamManager.isDreaming).thenReturn(true)
         swipe(Direction.DOWN)
@@ -132,7 +132,7 @@
 
     // Verifies that a swipe up is not forwarded to central surfaces.
     @Test
-    @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+    @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
     @EnableFlags(Flags.FLAG_COMMUNAL_HUB)
     fun testSwipeUp_communalEnabled_touchesNotSent() {
         kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
@@ -146,7 +146,7 @@
 
     // Verifies that a swipe up is not forwarded to the shade view.
     @Test
-    @DisableFlags(Flags.FLAG_COMMUNAL_HUB, Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+    @DisableFlags(Flags.FLAG_COMMUNAL_HUB, Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
     fun testSwipeUp_communalDisabled_touchesNotSent() {
         swipe(Direction.UP)
 
@@ -156,7 +156,7 @@
     }
 
     @Test
-    @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+    @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
     fun testCancelMotionEvent_popsTouchSession() {
         swipe(Direction.DOWN)
         val event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0f, 0f, 0)
@@ -165,7 +165,7 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+    @EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
     fun testFullVerticalSwipe_initiatedWhenAvailable() {
         // Indicate touches are available
         mTouchHandler.onGlanceableTouchAvailable(true)
@@ -176,7 +176,7 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+    @EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
     fun testFullVerticalSwipe_notInitiatedWhenNotAvailable() {
         // Indicate touches aren't available
         mTouchHandler.onGlanceableTouchAvailable(false)
@@ -187,7 +187,7 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+    @EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
     fun testFullVerticalSwipe_resetsTouchStateOnUp() {
         // Indicate touches are available
         mTouchHandler.onGlanceableTouchAvailable(true)
@@ -203,7 +203,7 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+    @EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
     fun testFullVerticalSwipe_resetsTouchStateOnCancel() {
         // Indicate touches are available
         mTouchHandler.onGlanceableTouchAvailable(true)
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 9b1d4ec..752c93e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -148,8 +148,6 @@
     @Mock
     private WakefulnessLifecycle mWakefulnessLifecycle;
     @Mock
-    private AuthDialogPanelInteractionDetector mPanelInteractionDetector;
-    @Mock
     private UserManager mUserManager;
     @Mock
     private LockPatternUtils mLockPatternUtils;
@@ -1059,10 +1057,9 @@
             super(context, null /* applicationCoroutineScope */,
                     mExecution, mCommandQueue, mActivityTaskManager, mWindowManager,
                     mFingerprintManager, mFaceManager, () -> mUdfpsController, mDisplayManager,
-                    mWakefulnessLifecycle, mPanelInteractionDetector, mUserManager,
-                    mLockPatternUtils, () -> mUdfpsLogger, () -> mLogContextInteractor,
-                    () -> mPromptSelectionInteractor, () -> mCredentialViewModel,
-                    () -> mPromptViewModel, mInteractionJankMonitor,
+                    mWakefulnessLifecycle, mUserManager, mLockPatternUtils, () -> mUdfpsLogger,
+                    () -> mLogContextInteractor, () -> mPromptSelectionInteractor,
+                    () -> mCredentialViewModel, () -> mPromptViewModel, mInteractionJankMonitor,
                     mHandler, mBackgroundExecutor, mUdfpsUtils, mVibratorHelper);
         }
 
@@ -1071,7 +1068,6 @@
                 boolean requireConfirmation, int userId, int[] sensorIds,
                 String opPackageName, boolean skipIntro, long operationId, long requestId,
                 WakefulnessLifecycle wakefulnessLifecycle,
-                AuthDialogPanelInteractionDetector panelInteractionDetector,
                 UserManager userManager,
                 LockPatternUtils lockPatternUtils, PromptViewModel viewModel) {
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java
deleted file mode 100644
index cd9189b..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics;
-
-import static org.junit.Assert.assertEquals;
-
-import android.hardware.biometrics.ComponentInfoInternal;
-import android.hardware.biometrics.SensorLocationInternal;
-import android.hardware.biometrics.SensorProperties;
-import android.hardware.fingerprint.FingerprintSensorProperties;
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class UdfpsDialogMeasureAdapterTest extends SysuiTestCase {
-    @Test
-    public void testUdfpsBottomSpacerHeightForPortrait() {
-        final int displayHeightPx = 3000;
-        final int navbarHeightPx = 10;
-        final int dialogBottomMarginPx = 20;
-        final int buttonBarHeightPx = 100;
-        final int textIndicatorHeightPx = 200;
-
-        final int sensorLocationX = 540;
-        final int sensorLocationY = 1600;
-        final int sensorRadius = 100;
-
-        final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
-        componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */,
-                "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
-                "00000001" /* serialNumber */, "" /* softwareVersion */));
-        componentInfo.add(new ComponentInfoInternal("matchingAlgorithm" /* componentId */,
-                "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
-                "vendor/version/revision" /* softwareVersion */));
-
-        final FingerprintSensorPropertiesInternal props = new FingerprintSensorPropertiesInternal(
-                0 /* sensorId */, SensorProperties.STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
-                componentInfo,
-                FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
-                true /* halControlsIllumination */,
-                true /* resetLockoutRequiresHardwareAuthToken */,
-                List.of(new SensorLocationInternal("" /* displayId */,
-                        sensorLocationX, sensorLocationY, sensorRadius)));
-
-        assertEquals(970,
-                UdfpsDialogMeasureAdapter.calculateBottomSpacerHeightForPortrait(
-                        props, displayHeightPx, textIndicatorHeightPx, buttonBarHeightPx,
-                        dialogBottomMarginPx, navbarHeightPx, 1.0f /* resolutionScale */
-                ));
-    }
-
-    @Test
-    public void testUdfpsBottomSpacerHeightForLandscape_whenMoreSpaceAboveIcon() {
-        final int titleHeightPx = 320;
-        final int subtitleHeightPx = 240;
-        final int descriptionHeightPx = 200;
-        final int topSpacerHeightPx = 550;
-        final int textIndicatorHeightPx = 190;
-        final int buttonBarHeightPx = 160;
-        final int navbarBottomInsetPx = 75;
-
-        assertEquals(885,
-                UdfpsDialogMeasureAdapter.calculateBottomSpacerHeightForLandscape(
-                        titleHeightPx, subtitleHeightPx, descriptionHeightPx, topSpacerHeightPx,
-                        textIndicatorHeightPx, buttonBarHeightPx, navbarBottomInsetPx));
-    }
-
-    @Test
-    public void testUdfpsBottomSpacerHeightForLandscape_whenMoreSpaceBelowIcon() {
-        final int titleHeightPx = 315;
-        final int subtitleHeightPx = 160;
-        final int descriptionHeightPx = 75;
-        final int topSpacerHeightPx = 220;
-        final int textIndicatorHeightPx = 290;
-        final int buttonBarHeightPx = 360;
-        final int navbarBottomInsetPx = 205;
-
-        assertEquals(-85,
-                UdfpsDialogMeasureAdapter.calculateBottomSpacerHeightForLandscape(
-                        titleHeightPx, subtitleHeightPx, descriptionHeightPx, topSpacerHeightPx,
-                        textIndicatorHeightPx, buttonBarHeightPx, navbarBottomInsetPx));
-    }
-
-    @Test
-    public void testUdfpsHorizontalSpacerWidthForLandscape() {
-        final int displayWidthPx = 3000;
-        final int dialogMarginPx = 20;
-        final int navbarHorizontalInsetPx = 75;
-
-        final int sensorLocationX = 540;
-        final int sensorLocationY = 1600;
-        final int sensorRadius = 100;
-
-        final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
-        componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */,
-                "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
-                "00000001" /* serialNumber */, "" /* softwareVersion */));
-        componentInfo.add(new ComponentInfoInternal("matchingAlgorithm" /* componentId */,
-                "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
-                "vendor/version/revision" /* softwareVersion */));
-
-        final FingerprintSensorPropertiesInternal props = new FingerprintSensorPropertiesInternal(
-                0 /* sensorId */, SensorProperties.STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
-                componentInfo,
-                FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
-                true /* halControlsIllumination */,
-                true /* resetLockoutRequiresHardwareAuthToken */,
-                List.of(new SensorLocationInternal("" /* displayId */,
-                        sensorLocationX, sensorLocationY, sensorRadius)));
-
-        assertEquals(1205,
-                UdfpsDialogMeasureAdapter.calculateHorizontalSpacerWidthForLandscape(
-                        props, displayWidthPx, dialogMarginPx, navbarHorizontalInsetPx,
-                        1.0f /* resolutionScale */));
-    }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/CameraAutoRotateRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/CameraAutoRotateRepositoryImplTest.kt
index 9cfa572..667d364 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/CameraAutoRotateRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/CameraAutoRotateRepositoryImplTest.kt
@@ -35,7 +35,6 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-@android.platform.test.annotations.EnabledOnRavenwood
 class CameraAutoRotateRepositoryImplTest : SysuiTestCase() {
     private val kosmos = Kosmos()
     private val testScope = kosmos.testScope
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 2911a50..c37b33e 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
@@ -40,7 +40,6 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-@android.platform.test.annotations.EnabledOnRavenwood
 class CommunalTutorialRepositoryImplTest : SysuiTestCase() {
     @Mock private lateinit var tableLogBuffer: TableLogBuffer
 
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 0242c2d..e57a4cb 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
@@ -1039,6 +1039,22 @@
             assertThat(showCommunalFromOccluded).isTrue()
         }
 
+    @Test
+    fun showCommunalFromOccluded_enteredOccludedFromDreaming() =
+        testScope.runTest {
+            kosmos.setCommunalAvailable(true)
+            val showCommunalFromOccluded by collectLastValue(underTest.showCommunalFromOccluded)
+            assertThat(showCommunalFromOccluded).isFalse()
+
+            kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.DREAMING,
+                to = KeyguardState.OCCLUDED,
+                testScope
+            )
+
+            assertThat(showCommunalFromOccluded).isTrue()
+        }
+
     private fun smartspaceTimer(id: String, timestamp: Long = 0L): CommunalSmartspaceTimer {
         return CommunalSmartspaceTimer(
             smartspaceTargetId = id,
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 ad73853..d6712f0 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
@@ -225,7 +225,7 @@
             kosmos.fakeKeyguardRepository.setKeyguardOccluded(true)
             kosmos.fakeKeyguardRepository.setDreaming(true)
             kosmos.fakeKeyguardRepository.setDreamingWithOverlay(true)
-            advanceTimeBy(100L)
+            advanceTimeBy(600L)
 
             sceneTransitions.value = hubToBlank
 
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 1255248..cc945d6 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
@@ -533,6 +533,8 @@
             keyguardRepository.setDozeTransitionModel(
                 DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
             )
+            advanceTimeBy(600L)
+
             keyguardRepository.setDreaming(true)
             keyguardRepository.setDreamingWithOverlay(true)
             advanceTimeBy(60L)
@@ -641,6 +643,7 @@
             keyguardRepository.setDozeTransitionModel(
                 DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
             )
+            advanceTimeBy(600L)
             keyguardRepository.setDreaming(true)
             keyguardRepository.setDreamingWithOverlay(true)
             advanceTimeBy(60L)
@@ -699,6 +702,7 @@
             keyguardRepository.setDozeTransitionModel(
                 DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
             )
+            advanceTimeBy(600L)
             keyguardRepository.setDreaming(true)
             keyguardRepository.setDreamingWithOverlay(true)
             advanceTimeBy(60L)
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..50fdb31
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/EditWidgetsActivityControllerTest.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class EditWidgetsActivityControllerTest : SysuiTestCase() {
+    @Test
+    fun activityLifecycle_stoppedWhenNotWaitingForResult() {
+        val activity = mock<Activity>()
+        val controller = EditWidgetsActivity.ActivityController(activity)
+
+        val callbackCapture = argumentCaptor<ActivityLifecycleCallbacks>()
+        verify(activity).registerActivityLifecycleCallbacks(callbackCapture.capture())
+
+        callbackCapture.lastValue.onActivityStopped(activity)
+
+        verify(activity).finish()
+    }
+
+    @Test
+    fun activityLifecycle_notStoppedWhenNotWaitingForResult() {
+        val activity = mock<Activity>()
+        val controller = EditWidgetsActivity.ActivityController(activity)
+
+        val callbackCapture = argumentCaptor<ActivityLifecycleCallbacks>()
+        verify(activity).registerActivityLifecycleCallbacks(callbackCapture.capture())
+
+        controller.onWaitingForResult(true)
+        callbackCapture.lastValue.onActivityStopped(activity)
+
+        verify(activity, never()).finish()
+    }
+
+    @Test
+    fun activityLifecycle_stoppedAfterResultReturned() {
+        val activity = mock<Activity>()
+        val controller = EditWidgetsActivity.ActivityController(activity)
+
+        val callbackCapture = argumentCaptor<ActivityLifecycleCallbacks>()
+        verify(activity).registerActivityLifecycleCallbacks(callbackCapture.capture())
+
+        controller.onWaitingForResult(true)
+        controller.onWaitingForResult(false)
+        callbackCapture.lastValue.onActivityStopped(activity)
+
+        verify(activity).finish()
+    }
+
+    @Test
+    fun activityLifecycle_statePreservedThroughInstanceSave() {
+        val activity = mock<Activity>()
+        val bundle = Bundle(1)
+
+        run {
+            val controller = EditWidgetsActivity.ActivityController(activity)
+            val callbackCapture = argumentCaptor<ActivityLifecycleCallbacks>()
+            verify(activity).registerActivityLifecycleCallbacks(callbackCapture.capture())
+
+            controller.onWaitingForResult(true)
+            callbackCapture.lastValue.onActivitySaveInstanceState(activity, bundle)
+        }
+
+        clearInvocations(activity)
+
+        run {
+            val controller = EditWidgetsActivity.ActivityController(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/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 5c09777..7a86e57 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
@@ -596,6 +596,9 @@
         // are created.
         verify(mDreamOverlayComponent).getDreamOverlayContainerViewController()
         verify(mAmbientTouchComponent).getTouchMonitor()
+
+        // Verify DreamOverlayContainerViewController is destroyed.
+        verify(mDreamOverlayContainerViewController).destroy()
     }
 
     @Test
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 331db52..5dd6c22 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
@@ -21,15 +21,14 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.SysuiTestableContext
-import com.android.systemui.coroutines.collectLastValue
 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.kosmos.Kosmos
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
 import com.google.common.truth.Truth.assertThat
 import java.io.File
-import java.time.Clock
-import java.time.Instant
 import javax.inject.Provider
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.test.TestScope
@@ -44,13 +43,13 @@
 @RunWith(AndroidJUnit4::class)
 class ContextualEducationRepositoryTest : SysuiTestCase() {
 
-    private lateinit var underTest: ContextualEducationRepository
+    private lateinit var underTest: UserContextualEducationRepository
     private val kosmos = Kosmos()
     private val testScope = kosmos.testScope
     private val dsScopeProvider: Provider<CoroutineScope> = Provider {
         TestScope(kosmos.testDispatcher).backgroundScope
     }
-    private val clock: Clock = FakeEduClock(Instant.ofEpochMilli(1000))
+
     private val testUserId = 1111
 
     // For deleting any test files created after the test
@@ -61,8 +60,7 @@
         // Create TestContext here because TemporaryFolder.create() is called in @Before. It is
         // needed before calling TemporaryFolder.newFolder().
         val testContext = TestContext(context, tmpFolder.newFolder())
-        val userRepository = UserContextualEducationRepository(testContext, dsScopeProvider)
-        underTest = ContextualEducationRepositoryImpl(clock, userRepository)
+        underTest = UserContextualEducationRepository(testContext, dsScopeProvider)
         underTest.setUser(testUserId)
     }
 
@@ -70,7 +68,7 @@
     fun changeRetrievedValueForNewUser() =
         testScope.runTest {
             // Update data for old user.
-            underTest.incrementSignalCount(BACK)
+            underTest.updateGestureEduModel(BACK) { it.copy(signalCount = 1) }
             val model by collectLastValue(underTest.readGestureEduModelFlow(BACK))
             assertThat(model?.signalCount).isEqualTo(1)
 
@@ -81,20 +79,19 @@
         }
 
     @Test
-    fun incrementSignalCount() =
+    fun dataChangedOnUpdate() =
         testScope.runTest {
-            underTest.incrementSignalCount(BACK)
+            val newModel =
+                GestureEduModel(
+                    signalCount = 2,
+                    educationShownCount = 1,
+                    lastShortcutTriggeredTime = kosmos.fakeEduClock.instant(),
+                    lastEducationTime = kosmos.fakeEduClock.instant(),
+                    usageSessionStartTime = kosmos.fakeEduClock.instant(),
+                )
+            underTest.updateGestureEduModel(BACK) { newModel }
             val model by collectLastValue(underTest.readGestureEduModelFlow(BACK))
-            assertThat(model?.signalCount).isEqualTo(1)
-        }
-
-    @Test
-    fun dataAddedOnUpdateShortcutTriggerTime() =
-        testScope.runTest {
-            val model by collectLastValue(underTest.readGestureEduModelFlow(BACK))
-            assertThat(model?.lastShortcutTriggeredTime).isNull()
-            underTest.updateShortcutTriggerTime(BACK)
-            assertThat(model?.lastShortcutTriggeredTime).isEqualTo(clock.instant())
+            assertThat(model).isEqualTo(newModel)
         }
 
     /** Test context which allows overriding getFilesDir path */
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 ae3302c..6867089 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
@@ -19,13 +19,18 @@
 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.contextualeducation.GestureType
 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.kosmos.testScope
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
@@ -36,8 +41,9 @@
 class KeyboardTouchpadEduInteractorTest : SysuiTestCase() {
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
-    private val repository = kosmos.contextualEducationRepository
+    private val contextualEduInteractor = kosmos.contextualEducationInteractor
     private val underTest: KeyboardTouchpadEduInteractor = kosmos.keyboardTouchpadEduInteractor
+    private val eduClock = kosmos.fakeEduClock
 
     @Before
     fun setup() {
@@ -47,15 +53,35 @@
     @Test
     fun newEducationInfoOnMaxSignalCountReached() =
         testScope.runTest {
-            tryTriggeringEducation(BACK)
+            triggerMaxEducationSignals(BACK)
             val model by collectLastValue(underTest.educationTriggered)
             assertThat(model?.gestureType).isEqualTo(BACK)
         }
 
     @Test
+    fun newEducationToastOn1stEducation() =
+        testScope.runTest {
+            val model by collectLastValue(underTest.educationTriggered)
+            triggerMaxEducationSignals(BACK)
+            assertThat(model?.educationUiType).isEqualTo(EducationUiType.Toast)
+        }
+
+    @Test
+    @kotlinx.coroutines.ExperimentalCoroutinesApi
+    fun newEducationNotificationOn2ndEducation() =
+        testScope.runTest {
+            val model by collectLastValue(underTest.educationTriggered)
+            triggerMaxEducationSignals(BACK)
+            // runCurrent() to trigger 1st education
+            runCurrent()
+            triggerMaxEducationSignals(BACK)
+            assertThat(model?.educationUiType).isEqualTo(EducationUiType.Notification)
+        }
+
+    @Test
     fun noEducationInfoBeforeMaxSignalCountReached() =
         testScope.runTest {
-            repository.incrementSignalCount(BACK)
+            contextualEduInteractor.incrementSignalCount(BACK)
             val model by collectLastValue(underTest.educationTriggered)
             assertThat(model).isNull()
         }
@@ -64,15 +90,34 @@
     fun noEducationInfoWhenShortcutTriggeredPreviously() =
         testScope.runTest {
             val model by collectLastValue(underTest.educationTriggered)
-            repository.updateShortcutTriggerTime(BACK)
-            tryTriggeringEducation(BACK)
+            contextualEduInteractor.updateShortcutTriggerTime(BACK)
+            triggerMaxEducationSignals(BACK)
             assertThat(model).isNull()
         }
 
-    private suspend fun tryTriggeringEducation(gestureType: GestureType) {
+    @Test
+    fun startNewUsageSessionWhen2ndSignalReceivedAfterSessionDeadline() =
+        testScope.runTest {
+            val model by
+                collectLastValue(kosmos.contextualEducationRepository.readGestureEduModelFlow(BACK))
+            contextualEduInteractor.incrementSignalCount(BACK)
+            eduClock.offset(KeyboardTouchpadEduInteractor.usageSessionDuration.plus(1.seconds))
+            val secondSignalReceivedTime = eduClock.instant()
+            contextualEduInteractor.incrementSignalCount(BACK)
+
+            assertThat(model)
+                .isEqualTo(
+                    GestureEduModel(
+                        signalCount = 1,
+                        usageSessionStartTime = secondSignalReceivedTime
+                    )
+                )
+        }
+
+    private suspend fun triggerMaxEducationSignals(gestureType: GestureType) {
         // Increment max number of signal to try triggering education
         for (i in 1..KeyboardTouchpadEduInteractor.MAX_SIGNAL_COUNT) {
-            repository.incrementSignalCount(gestureType)
+            contextualEduInteractor.incrementSignalCount(gestureType)
         }
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt
new file mode 100644
index 0000000..1f73347
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt
@@ -0,0 +1,82 @@
+/*
+ * 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.systemui.education.domain.ui.view
+
+import android.content.applicationContext
+import android.widget.Toast
+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.BACK
+import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduInteractor
+import com.android.systemui.education.domain.interactor.contextualEducationInteractor
+import com.android.systemui.education.domain.interactor.keyboardTouchpadEduInteractor
+import com.android.systemui.education.ui.view.ContextualEduUiCoordinator
+import com.android.systemui.education.ui.viewmodel.ContextualEduViewModel
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+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.Mock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ContextualEduUiCoordinatorTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val interactor = kosmos.contextualEducationInteractor
+    private lateinit var underTest: ContextualEduUiCoordinator
+    @Mock private lateinit var toast: Toast
+
+    @get:Rule val mockitoRule = MockitoJUnit.rule()
+
+    @Before
+    fun setUp() {
+        val viewModel =
+            ContextualEduViewModel(
+                kosmos.applicationContext.resources,
+                kosmos.keyboardTouchpadEduInteractor
+            )
+        underTest =
+            ContextualEduUiCoordinator(kosmos.applicationCoroutineScope, viewModel) { _ -> toast }
+        underTest.start()
+        kosmos.keyboardTouchpadEduInteractor.start()
+    }
+
+    @Test
+    @OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
+    fun showToastOnNewEdu() =
+        testScope.runTest {
+            triggerEducation(BACK)
+            runCurrent()
+            verify(toast).show()
+        }
+
+    private suspend fun triggerEducation(gestureType: GestureType) {
+        for (i in 1..KeyboardTouchpadEduInteractor.MAX_SIGNAL_COUNT) {
+            interactor.incrementSignalCount(gestureType)
+        }
+    }
+}
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 273e3cb..fd4ed38 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
@@ -112,6 +112,26 @@
     }
 
     @Test
+    fun onActionDown_whileClicked_startsWait() =
+        testWhileInState(QSLongPressEffect.State.CLICKED) {
+            // GIVEN an action down event occurs
+            longPressEffect.handleActionDown()
+
+            // THEN the effect moves to the TIMEOUT_WAIT state
+            assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.TIMEOUT_WAIT)
+        }
+
+    @Test
+    fun onActionDown_whileLongClicked_startsWait() =
+        testWhileInState(QSLongPressEffect.State.LONG_CLICKED) {
+            // GIVEN an action down event occurs
+            longPressEffect.handleActionDown()
+
+            // THEN the effect moves to the TIMEOUT_WAIT state
+            assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.TIMEOUT_WAIT)
+        }
+
+    @Test
     fun onActionCancel_whileWaiting_goesIdle() =
         testWhileInState(QSLongPressEffect.State.TIMEOUT_WAIT) {
             // GIVEN an action cancel occurs
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
similarity index 83%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
index 032794c..638c957 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
@@ -14,22 +14,6 @@
  * limitations under the License.
  */
 
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.platform.test.annotations.EnableFlags
@@ -46,17 +30,18 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
 import com.android.systemui.testKosmos
-import kotlin.test.Test
+import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Ignore
+import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.spy
@@ -79,21 +64,6 @@
     @Before
     fun setup() {
         underTest.start()
-
-        kosmos.fakeKeyguardRepository.setDreaming(true)
-        kosmos.keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop(true)
-
-        // Transition to DOZING and set the power interactor asleep.
-        powerInteractor.setAsleepForTest()
-        runBlocking {
-            transitionRepository.sendTransitionSteps(
-                from = KeyguardState.LOCKSCREEN,
-                to = KeyguardState.DREAMING,
-                testScope
-            )
-            kosmos.fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockMode.NONE)
-            reset(transitionRepository)
-        }
     }
 
     @Test
@@ -146,20 +116,27 @@
     @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
     fun testTransitionsToLockscreen_whenOccludingActivityEnds() =
         testScope.runTest {
+            runCurrent()
+
             kosmos.fakeKeyguardRepository.setDreaming(true)
-            kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = true)
+            kosmos.keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop(true)
+            // Transition to DREAMING and set the power interactor awake
+            powerInteractor.setAwakeForTest()
+
             transitionRepository.sendTransitionSteps(
                 from = KeyguardState.LOCKSCREEN,
                 to = KeyguardState.DREAMING,
-                testScope,
+                testScope
             )
-            runCurrent()
+            kosmos.fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockMode.NONE)
 
+            // Get past initial setup
+            advanceTimeBy(600L)
             reset(transitionRepository)
 
             kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = false)
             kosmos.fakeKeyguardRepository.setDreaming(false)
-            runCurrent()
+            advanceTimeBy(60L)
 
             assertThat(transitionRepository)
                 .startedTransition(
@@ -171,6 +148,13 @@
     @Test
     fun testTransitionToAlternateBouncer() =
         testScope.runTest {
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.DREAMING,
+                testScope,
+            )
+            reset(transitionRepository)
+
             kosmos.fakeKeyguardBouncerRepository.setAlternateVisible(true)
             runCurrent()
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index fc827a14..ebefb4d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -33,11 +33,15 @@
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.CameraLaunchType
+import com.android.systemui.keyguard.shared.model.DozeStateModel
+import com.android.systemui.keyguard.shared.model.DozeTransitionModel
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.StatusBarState
 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.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
+import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.res.R
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
@@ -47,6 +51,7 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -67,6 +72,7 @@
     private val configRepository by lazy { kosmos.fakeConfigurationRepository }
     private val bouncerRepository by lazy { kosmos.keyguardBouncerRepository }
     private val shadeRepository by lazy { kosmos.shadeRepository }
+    private val powerInteractor by lazy { kosmos.powerInteractor }
     private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
 
     private val transitionState: MutableStateFlow<ObservableTransitionState> =
@@ -350,6 +356,59 @@
         }
 
     @Test
+    fun isAbleToDream_falseWhenDozing() =
+        testScope.runTest {
+            val isAbleToDream by collectLastValue(underTest.isAbleToDream)
+
+            repository.setDozeTransitionModel(
+                DozeTransitionModel(from = DozeStateModel.INITIALIZED, to = DozeStateModel.DOZE_AOD)
+            )
+
+            assertThat(isAbleToDream).isEqualTo(false)
+        }
+
+    @Test
+    fun isAbleToDream_falseWhenNotDozingAndNotDreaming() =
+        testScope.runTest {
+            val isAbleToDream by collectLastValue(underTest.isAbleToDream)
+
+            repository.setDozeTransitionModel(
+                DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
+            )
+            powerInteractor.setAwakeForTest()
+            advanceTimeBy(1000L)
+
+            assertThat(isAbleToDream).isEqualTo(false)
+        }
+
+    @Test
+    fun isAbleToDream_trueWhenNotDozingAndIsDreaming_afterDelay() =
+        testScope.runTest {
+            val isAbleToDream by collectLastValue(underTest.isAbleToDream)
+            runCurrent()
+
+            repository.setDreaming(true)
+            repository.setDozeTransitionModel(
+                DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
+            )
+            powerInteractor.setAwakeForTest()
+            runCurrent()
+
+            // After some delay, still false
+            advanceTimeBy(300L)
+            assertThat(isAbleToDream).isEqualTo(false)
+
+            // After more delay, is true
+            advanceTimeBy(300L)
+            assertThat(isAbleToDream).isEqualTo(true)
+
+            // Also changes back after the minimal debounce
+            repository.setDreaming(false)
+            advanceTimeBy(55L)
+            assertThat(isAbleToDream).isEqualTo(false)
+        }
+
+    @Test
     @EnableSceneContainer
     fun animationDozingTransitions() =
         testScope.runTest {
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 9762fd8..8c1e8de 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
@@ -258,7 +258,7 @@
             keyguardRepository.setDozeTransitionModel(
                 DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
             )
-            runCurrent()
+            advanceTimeBy(600L)
 
             // GIVEN a prior transition has run to LOCKSCREEN
             runTransitionAndSetWakefulness(KeyguardState.GONE, KeyguardState.LOCKSCREEN)
@@ -287,7 +287,7 @@
             keyguardRepository.setDozeTransitionModel(
                 DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
             )
-            runCurrent()
+            advanceTimeBy(600L)
 
             // GIVEN a prior transition has run to LOCKSCREEN
             runTransitionAndSetWakefulness(KeyguardState.GONE, KeyguardState.LOCKSCREEN)
@@ -625,6 +625,9 @@
     @DisableSceneContainer
     fun dreamingToGoneWithKeyguardNotShowing() =
         testScope.runTest {
+            // Setup - Move past initial delay with [KeyguardInteractor#isAbleToDream]
+            advanceTimeBy(600L)
+
             // GIVEN a prior transition has run to DREAMING
             keyguardRepository.setDreamingWithOverlay(true)
             runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.DREAMING)
@@ -754,15 +757,35 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
+    fun goneToOccluded() =
+        testScope.runTest {
+            // GIVEN a prior transition has run to GONE
+            runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GONE)
+
+            // WHEN an occluding app is running and showDismissibleKeyguard is called
+            keyguardRepository.setKeyguardOccluded(true)
+            keyguardRepository.showDismissibleKeyguard()
+            runCurrent()
+
+            assertThat(transitionRepository)
+                .startedTransition(
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.OCCLUDED,
+                    ownerName =
+                        "FromGoneTransitionInteractor" + "(Dismissible keyguard with occlusion)",
+                    animatorAssertion = { it.isNotNull() }
+                )
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
     @DisableSceneContainer
     fun goneToDreaming() =
         testScope.runTest {
-            // GIVEN a device that is not dreaming or dozing
-            keyguardRepository.setDreamingWithOverlay(false)
-            keyguardRepository.setDozeTransitionModel(
-                DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
-            )
-            runCurrent()
+            // Setup - Move past initial delay with [KeyguardInteractor#isAbleToDream]
+            advanceTimeBy(600L)
 
             // GIVEN a prior transition has run to GONE
             runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GONE)
@@ -1130,6 +1153,9 @@
     @DisableSceneContainer
     fun primaryBouncerToGlanceableHubWhileDreaming() =
         testScope.runTest {
+            // Setup - Move past initial delay with [KeyguardInteractor#isAbleToDream]
+            advanceTimeBy(600L)
+
             // GIVEN the device is idle on the glanceable hub
             communalSceneInteractor.changeScene(CommunalScenes.Communal)
             runCurrent()
@@ -1144,6 +1170,7 @@
             // GIVEN that we are dreaming and occluded
             keyguardRepository.setDreaming(true)
             keyguardRepository.setKeyguardOccluded(true)
+            advanceTimeBy(60L)
 
             // WHEN the primaryBouncer stops showing
             bouncerRepository.setPrimaryShow(false)
@@ -2181,12 +2208,14 @@
     @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
     fun glanceableHubToDreaming() =
         testScope.runTest {
+            runCurrent()
+
             // GIVEN that we are dreaming and not dozing
             keyguardRepository.setDreaming(true)
             keyguardRepository.setDozeTransitionModel(
                 DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
             )
-            runCurrent()
+            advanceTimeBy(600L)
 
             // GIVEN a prior transition has run to GLANCEABLE_HUB
             runTransitionAndSetWakefulness(KeyguardState.DREAMING, KeyguardState.GLANCEABLE_HUB)
@@ -2233,7 +2262,7 @@
             keyguardRepository.setDozeTransitionModel(
                 DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
             )
-            advanceTimeBy(100L)
+            advanceTimeBy(600L)
 
             // GIVEN a prior transition has run to GLANCEABLE_HUB
             communalSceneInteractor.changeScene(CommunalScenes.Communal)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelTest.kt
index 3f6e229..df8afdb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelTest.kt
@@ -23,6 +23,7 @@
 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.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
@@ -117,6 +118,24 @@
         }
 
     @Test
+    fun lockscreenAlphaStartsFromViewStateAccessorAlpha() =
+        testScope.runTest {
+            val viewState = ViewStateAccessor(alpha = { 0.5f })
+            val alpha by collectLastValue(underTest.lockscreenAlpha(viewState))
+
+            keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED))
+
+            keyguardTransitionRepository.sendTransitionStep(step(0f))
+            assertThat(alpha).isEqualTo(0.5f)
+
+            keyguardTransitionRepository.sendTransitionStep(step(0.5f))
+            assertThat(alpha).isEqualTo(0.75f)
+
+            keyguardTransitionRepository.sendTransitionStep(step(1f))
+            assertThat(alpha).isEqualTo(1f)
+        }
+
+    @Test
     fun deviceEntryBackgroundViewDisappear() =
         testScope.runTest {
             val values by collectValues(underTest.deviceEntryBackgroundViewAlpha)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelTest.kt
index 4a10d80..8e4876d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelTest.kt
@@ -42,6 +42,28 @@
     val underTest = kosmos.dozingToLockscreenTransitionViewModel
 
     @Test
+    fun lockscreenAlpha() =
+        testScope.runTest {
+            val lockscreenAlpha by collectValues(underTest.lockscreenAlpha)
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+            repository.sendTransitionStep(step(0.1f))
+            repository.sendTransitionStep(step(0.5f))
+            repository.sendTransitionStep(step(1f))
+            lockscreenAlpha.forEach { assertThat(it).isEqualTo(1f) }
+        }
+
+    @Test
+    fun shortcutsAlpha() =
+        testScope.runTest {
+            val shortcutsAlpha by collectValues(underTest.shortcutsAlpha)
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+            repository.sendTransitionStep(step(0.5f))
+            repository.sendTransitionStep(step(1f))
+            assertThat(shortcutsAlpha[0]).isEqualTo(0f)
+            assertThat(shortcutsAlpha[1]).isEqualTo(1f)
+        }
+
+    @Test
     fun deviceEntryParentViewShows() =
         testScope.runTest {
             val deviceEntryParentViewAlpha by collectValues(underTest.deviceEntryParentViewAlpha)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
index 3075c54..8236eec 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
@@ -34,6 +34,7 @@
 import com.android.systemui.keyguard.shared.model.ClockSize
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
 import com.android.systemui.res.R
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
@@ -79,6 +80,7 @@
             fakeFeatureFlagsClassic.set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, true)
             shadeRepository.setShadeLayoutWide(false)
             underTest = lockscreenContentViewModel
+            underTest.activateIn(testScope)
         }
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneActionsViewModelTest.kt
similarity index 95%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneActionsViewModelTest.kt
index bca83f0..b3ea03e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneActionsViewModelTest.kt
@@ -37,6 +37,7 @@
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
 import com.android.systemui.power.data.repository.fakePowerRepository
 import com.android.systemui.power.shared.model.WakefulnessState
 import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -44,9 +45,7 @@
 import com.android.systemui.scene.shared.model.TransitionKeys
 import com.android.systemui.shade.data.repository.shadeRepository
 import com.android.systemui.shade.domain.interactor.shadeInteractor
-import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
 import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import kotlin.math.pow
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -63,7 +62,7 @@
 @RunWith(ParameterizedAndroidJunit4::class)
 @RunWithLooper
 @EnableSceneContainer
-class LockscreenSceneViewModelTest : SysuiTestCase() {
+class LockscreenSceneActionsViewModelTest : SysuiTestCase() {
 
     companion object {
         private const val parameterCount = 6
@@ -172,6 +171,7 @@
     @EnableFlags(Flags.FLAG_COMMUNAL_HUB)
     fun destinationScenes() =
         testScope.runTest {
+            underTest.activateIn(this)
             kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
             kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
                 if (canSwipeToEnter) {
@@ -192,7 +192,7 @@
                     },
             )
 
-            val destinationScenes by collectLastValue(underTest.destinationScenes)
+            val destinationScenes by collectLastValue(underTest.actions)
             val downDestination =
                 destinationScenes?.get(
                     Swipe(
@@ -255,15 +255,10 @@
                 )
         }
 
-    private fun createLockscreenSceneViewModel(): LockscreenSceneViewModel {
-        return LockscreenSceneViewModel(
+    private fun createLockscreenSceneViewModel(): LockscreenSceneActionsViewModel {
+        return LockscreenSceneActionsViewModel(
             deviceEntryInteractor = kosmos.deviceEntryInteractor,
             communalInteractor = kosmos.communalInteractor,
-            touchHandling =
-                KeyguardTouchHandlingViewModel(
-                    interactor = mock(),
-                ),
-            notifications = kosmos.notificationsPlaceholderViewModel,
             shadeInteractor = kosmos.shadeInteractor,
         )
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt
index d1f908d..46b370f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt
@@ -16,16 +16,27 @@
 
 package com.android.systemui.lifecycle
 
+import android.view.View
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 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 com.android.systemui.util.Assert
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
+import org.mockito.kotlin.verify
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
@@ -110,4 +121,45 @@
 
         assertThat(isActive).isFalse()
     }
+
+    @Test
+    fun viewModel_viewBinder() = runTest {
+        Assert.setTestThread(Thread.currentThread())
+
+        val view: View = mock { on { isAttachedToWindow } doReturn false }
+        val viewModel = FakeViewModel()
+        backgroundScope.launch {
+            view.viewModel(
+                minWindowLifecycleState = WindowLifecycleState.ATTACHED,
+                factory = { viewModel },
+            ) {
+                awaitCancellation()
+            }
+        }
+        runCurrent()
+
+        assertThat(viewModel.isActivated).isFalse()
+
+        view.stub { on { isAttachedToWindow } doReturn true }
+        argumentCaptor<View.OnAttachStateChangeListener>()
+            .apply { verify(view).addOnAttachStateChangeListener(capture()) }
+            .allValues
+            .forEach { it.onViewAttachedToWindow(view) }
+        runCurrent()
+
+        assertThat(viewModel.isActivated).isTrue()
+    }
+}
+
+private class FakeViewModel : SysUiViewModel() {
+    var isActivated = false
+
+    override suspend fun onActivated() {
+        isActivated = true
+        try {
+            awaitCancellation()
+        } finally {
+            isActivated = false
+        }
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneActionsViewModelTest.kt
similarity index 78%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneActionsViewModelTest.kt
index 16c7090..8f925d5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneActionsViewModelTest.kt
@@ -31,12 +31,13 @@
 import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
 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.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.notificationsShadeSceneViewModel
+import com.android.systemui.shade.ui.viewmodel.notificationsShadeSceneActionsViewModel
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -51,23 +52,24 @@
 @RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper
 @EnableSceneContainer
-class NotificationsShadeSceneViewModelTest : SysuiTestCase() {
+class NotificationsShadeSceneActionsViewModelTest : 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.notificationsShadeSceneViewModel }
+    private val underTest by lazy { kosmos.notificationsShadeSceneActionsViewModel }
 
     @Test
     fun upTransitionSceneKey_deviceLocked_lockscreen() =
         testScope.runTest {
-            val destinationScenes by collectLastValue(underTest.destinationScenes)
+            val actions by collectLastValue(underTest.actions)
             lockDevice()
+            underTest.activateIn(this)
 
-            assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
-            assertThat(destinationScenes?.get(Swipe.Down)).isNull()
+            assertThat(actions?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+            assertThat(actions?.get(Swipe.Down)).isNull()
             assertThat(kosmos.homeSceneFamilyResolver.resolvedScene.value)
                 .isEqualTo(Scenes.Lockscreen)
         }
@@ -75,23 +77,25 @@
     @Test
     fun upTransitionSceneKey_deviceLocked_keyguardDisabled_gone() =
         testScope.runTest {
-            val destinationScenes by collectLastValue(underTest.destinationScenes)
+            val actions by collectLastValue(underTest.actions)
             lockDevice()
             kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false)
+            underTest.activateIn(this)
 
-            assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+            assertThat(actions?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
             assertThat(kosmos.homeSceneFamilyResolver.resolvedScene.value).isEqualTo(Scenes.Gone)
         }
 
     @Test
     fun upTransitionSceneKey_deviceUnlocked_gone() =
         testScope.runTest {
-            val destinationScenes by collectLastValue(underTest.destinationScenes)
+            val actions by collectLastValue(underTest.actions)
             lockDevice()
             unlockDevice()
+            underTest.activateIn(this)
 
-            assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
-            assertThat(destinationScenes?.get(Swipe.Down)).isNull()
+            assertThat(actions?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+            assertThat(actions?.get(Swipe.Down)).isNull()
             assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Gone)
         }
 
@@ -99,11 +103,12 @@
     fun downTransitionSceneKey_deviceLocked_bottomAligned_lockscreen() =
         testScope.runTest {
             kosmos.fakeShadeRepository.setDualShadeAlignedToBottom(true)
-            val destinationScenes by collectLastValue(underTest.destinationScenes)
+            val actions by collectLastValue(underTest.actions)
             lockDevice()
+            underTest.activateIn(this)
 
-            assertThat(destinationScenes?.get(Swipe.Down)?.toScene).isEqualTo(SceneFamilies.Home)
-            assertThat(destinationScenes?.get(Swipe.Up)).isNull()
+            assertThat(actions?.get(Swipe.Down)?.toScene).isEqualTo(SceneFamilies.Home)
+            assertThat(actions?.get(Swipe.Up)).isNull()
             assertThat(kosmos.homeSceneFamilyResolver.resolvedScene.value)
                 .isEqualTo(Scenes.Lockscreen)
         }
@@ -112,26 +117,28 @@
     fun downTransitionSceneKey_deviceUnlocked_bottomAligned_gone() =
         testScope.runTest {
             kosmos.fakeShadeRepository.setDualShadeAlignedToBottom(true)
-            val destinationScenes by collectLastValue(underTest.destinationScenes)
+            val actions by collectLastValue(underTest.actions)
             lockDevice()
             unlockDevice()
+            underTest.activateIn(this)
 
-            assertThat(destinationScenes?.get(Swipe.Down)?.toScene).isEqualTo(SceneFamilies.Home)
-            assertThat(destinationScenes?.get(Swipe.Up)).isNull()
+            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 destinationScenes by collectLastValue(underTest.destinationScenes)
+            val actions by collectLastValue(underTest.actions)
             kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
             kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.None
             )
             sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
+            underTest.activateIn(this)
 
-            assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+            assertThat(actions?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
             assertThat(kosmos.homeSceneFamilyResolver.resolvedScene.value)
                 .isEqualTo(Scenes.Lockscreen)
         }
@@ -139,7 +146,7 @@
     @Test
     fun upTransitionSceneKey_authMethodSwipe_lockscreenDismissed_goesToGone() =
         testScope.runTest {
-            val destinationScenes by collectLastValue(underTest.destinationScenes)
+            val actions by collectLastValue(underTest.actions)
             kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
             kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.None
@@ -147,8 +154,9 @@
             sceneInteractor // force the lazy; this will kick off StateFlows
             runCurrent()
             sceneInteractor.changeScene(Scenes.Gone, "reason")
+            underTest.activateIn(this)
 
-            assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+            assertThat(actions?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
             assertThat(kosmos.homeSceneFamilyResolver.resolvedScene.value).isEqualTo(Scenes.Gone)
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorTest.kt
index c3a5df06..661d4b0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorTest.kt
@@ -67,19 +67,25 @@
             }
         }
 
+    @OptIn(ExperimentalCoroutinesApi::class)
     @Test
     fun resize_updatesSharedPreferences() =
         with(kosmos) {
             testScope.runTest {
+                qsPreferencesRepository.setLargeTilesSpecs(setOf())
+                runCurrent()
+
                 val latest by collectLastValue(qsPreferencesRepository.largeTilesSpecs)
                 val spec = TileSpec.create("large")
 
                 // Assert that the tile is added to the large tiles after resizing
-                underTest.resize(spec, toIcon = false)
+                underTest.resize(spec)
+                runCurrent()
                 assertThat(latest).contains(spec)
 
                 // Assert that the tile is removed from the large tiles after resizing
-                underTest.resize(spec, toIcon = true)
+                underTest.resize(spec)
+                runCurrent()
                 assertThat(latest).doesNotContain(spec)
             }
         }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropStateTest.kt
index 45262ca..b2f5765 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropStateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropStateTest.kt
@@ -22,6 +22,8 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.shared.model.Text
+import com.android.systemui.qs.panels.shared.model.SizedTile
+import com.android.systemui.qs.panels.shared.model.SizedTileImpl
 import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.google.common.truth.Truth.assertThat
@@ -37,15 +39,15 @@
     @Test
     fun isMoving_returnsCorrectValue() {
         // Asserts no tiles is moving
-        TestEditTiles.forEach { assertThat(underTest.isMoving(it.tileSpec)).isFalse() }
+        TestEditTiles.forEach { assertThat(underTest.isMoving(it.tile.tileSpec)).isFalse() }
 
         // Start the drag movement
         underTest.onStarted(TestEditTiles[0])
 
         // Assert that the correct tile is marked as moving
         TestEditTiles.forEach {
-            assertThat(underTest.isMoving(it.tileSpec))
-                .isEqualTo(TestEditTiles[0].tileSpec == it.tileSpec)
+            assertThat(underTest.isMoving(it.tile.tileSpec))
+                .isEqualTo(TestEditTiles[0].tile.tileSpec == it.tile.tileSpec)
         }
     }
 
@@ -55,11 +57,11 @@
         underTest.onStarted(TestEditTiles[0])
 
         // Move the tile to the end of the list
-        underTest.onMoved(listState.tiles[5].tileSpec)
+        underTest.onMoved(listState.tiles[5].tile.tileSpec)
         assertThat(underTest.currentPosition()).isEqualTo(5)
 
         // Move the tile to the middle of the list
-        underTest.onMoved(listState.tiles[2].tileSpec)
+        underTest.onMoved(listState.tiles[2].tile.tileSpec)
         assertThat(underTest.currentPosition()).isEqualTo(2)
     }
 
@@ -69,13 +71,13 @@
         underTest.onStarted(TestEditTiles[0])
 
         // Move the tile to the end of the list
-        underTest.onMoved(listState.tiles[5].tileSpec)
+        underTest.onMoved(listState.tiles[5].tile.tileSpec)
 
         // Drop the tile
         underTest.onDrop()
 
         // Asserts no tiles is moving
-        TestEditTiles.forEach { assertThat(underTest.isMoving(it.tileSpec)).isFalse() }
+        TestEditTiles.forEach { assertThat(underTest.isMoving(it.tile.tileSpec)).isFalse() }
     }
 
     @Test
@@ -87,19 +89,24 @@
         underTest.movedOutOfBounds()
 
         // Asserts the moving tile is not current
-        assertThat(listState.tiles.firstOrNull { it.tileSpec == TestEditTiles[0].tileSpec })
+        assertThat(
+                listState.tiles.firstOrNull { it.tile.tileSpec == TestEditTiles[0].tile.tileSpec }
+            )
             .isNull()
     }
 
     companion object {
-        private fun createEditTile(tileSpec: String): EditTileViewModel {
-            return EditTileViewModel(
-                tileSpec = TileSpec.create(tileSpec),
-                icon = Icon.Resource(0, null),
-                label = Text.Loaded("unused"),
-                appName = null,
-                isCurrent = true,
-                availableEditActions = emptySet(),
+        private fun createEditTile(tileSpec: String): SizedTile<EditTileViewModel> {
+            return SizedTileImpl(
+                EditTileViewModel(
+                    tileSpec = TileSpec.create(tileSpec),
+                    icon = Icon.Resource(0, null),
+                    label = Text.Loaded("unused"),
+                    appName = null,
+                    isCurrent = true,
+                    availableEditActions = emptySet(),
+                ),
+                1,
             )
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt
index e76d389..a3a6a33 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt
@@ -21,6 +21,8 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.shared.model.Text
+import com.android.systemui.qs.panels.shared.model.SizedTile
+import com.android.systemui.qs.panels.shared.model.SizedTileImpl
 import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.google.common.truth.Truth.assertThat
@@ -35,7 +37,7 @@
     @Test
     fun movingNonExistentTile_tileAdded() {
         val newTile = createEditTile("other_tile", false)
-        underTest.move(newTile, TestEditTiles[0].tileSpec)
+        underTest.move(newTile, TestEditTiles[0].tile.tileSpec)
 
         assertThat(underTest.tiles[0]).isEqualTo(newTile)
         assertThat(underTest.tiles.subList(1, underTest.tiles.size))
@@ -51,7 +53,7 @@
 
     @Test
     fun movingTileToItself_listUnchanged() {
-        underTest.move(TestEditTiles[0], TestEditTiles[0].tileSpec)
+        underTest.move(TestEditTiles[0], TestEditTiles[0].tile.tileSpec)
 
         assertThat(underTest.tiles).containsExactly(*TestEditTiles.toTypedArray())
     }
@@ -59,7 +61,7 @@
     @Test
     fun movingTileToSameSection_listUpdates() {
         // Move tile at index 0 to index 1. Tile 0 should remain current.
-        underTest.move(TestEditTiles[0], TestEditTiles[1].tileSpec)
+        underTest.move(TestEditTiles[0], TestEditTiles[1].tile.tileSpec)
 
         // Assert the tiles 0 and 1 have changed places.
         assertThat(underTest.tiles[0]).isEqualTo(TestEditTiles[1])
@@ -72,21 +74,27 @@
 
     fun removingTile_listUpdates() {
         // Remove tile at index 0
-        underTest.remove(TestEditTiles[0].tileSpec)
+        underTest.remove(TestEditTiles[0].tile.tileSpec)
 
         // Assert the tile was removed
         assertThat(underTest.tiles).containsExactly(*TestEditTiles.subList(1, 6).toTypedArray())
     }
 
     companion object {
-        private fun createEditTile(tileSpec: String, isCurrent: Boolean): EditTileViewModel {
-            return EditTileViewModel(
-                tileSpec = TileSpec.create(tileSpec),
-                icon = Icon.Resource(0, null),
-                label = Text.Loaded("unused"),
-                appName = null,
-                isCurrent = isCurrent,
-                availableEditActions = emptySet(),
+        private fun createEditTile(
+            tileSpec: String,
+            isCurrent: Boolean
+        ): SizedTile<EditTileViewModel> {
+            return SizedTileImpl(
+                EditTileViewModel(
+                    tileSpec = TileSpec.create(tileSpec),
+                    icon = Icon.Resource(0, null),
+                    label = Text.Loaded("unused"),
+                    appName = null,
+                    isCurrent = isCurrent,
+                    availableEditActions = emptySet(),
+                ),
+                1,
             )
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/PaginatableGridLayoutTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/PaginatableGridLayoutTest.kt
index 6df3f8d..0d93686 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/PaginatableGridLayoutTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/PaginatableGridLayoutTest.kt
@@ -19,7 +19,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.qs.panels.shared.model.SizedTile
+import com.android.systemui.qs.panels.shared.model.SizedTileImpl
 import com.android.systemui.qs.panels.ui.viewmodel.MockTileViewModel
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.google.common.truth.Truth.assertThat
@@ -72,10 +72,10 @@
     }
 
     companion object {
-        fun extraLargeTile() = SizedTile(MockTileViewModel(TileSpec.create("XLarge")), 3)
+        fun extraLargeTile() = SizedTileImpl(MockTileViewModel(TileSpec.create("XLarge")), 3)
 
-        fun largeTile() = SizedTile(MockTileViewModel(TileSpec.create("large")), 2)
+        fun largeTile() = SizedTileImpl(MockTileViewModel(TileSpec.create("large")), 2)
 
-        fun smallTile() = SizedTile(MockTileViewModel(TileSpec.create("small")), 1)
+        fun smallTile() = SizedTileImpl(MockTileViewModel(TileSpec.create("small")), 1)
     }
 }
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 cfb84a7..d153e9d 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
@@ -16,7 +16,6 @@
 
 package com.android.systemui.qs.pipeline.domain.autoaddable
 
-import android.platform.test.annotations.EnabledOnRavenwood
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -36,7 +35,6 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@EnabledOnRavenwood
 @RunWith(AndroidJUnit4::class)
 class AutoAddableSettingTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/AirplaneModeMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/AirplaneModeMapperTest.kt
new file mode 100644
index 0000000..5a73fe2
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/AirplaneModeMapperTest.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.qs.tiles.impl
+
+import android.graphics.drawable.TestStubDrawable
+import android.service.quicksettings.Tile
+import android.widget.Switch
+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.kosmos.Kosmos
+import com.android.systemui.qs.tiles.impl.airplane.domain.AirplaneModeMapper
+import com.android.systemui.qs.tiles.impl.airplane.domain.model.AirplaneModeTileModel
+import com.android.systemui.qs.tiles.impl.airplane.qsAirplaneModeTileConfig
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AirplaneModeMapperTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val airplaneModeConfig = kosmos.qsAirplaneModeTileConfig
+
+    private lateinit var mapper: AirplaneModeMapper
+
+    @Before
+    fun setup() {
+        mapper =
+            AirplaneModeMapper(
+                context.orCreateTestableResources
+                    .apply {
+                        addOverride(R.drawable.qs_airplane_icon_off, TestStubDrawable())
+                        addOverride(R.drawable.qs_airplane_icon_on, TestStubDrawable())
+                    }
+                    .resources,
+                context.theme,
+            )
+    }
+
+    @Test
+    fun enabledModel_mapsCorrectly() {
+        val inputModel = AirplaneModeTileModel(true)
+
+        val outputState = mapper.map(airplaneModeConfig, inputModel)
+
+        val expectedState =
+            createAirplaneModeState(
+                QSTileState.ActivationState.ACTIVE,
+                context.resources.getStringArray(R.array.tile_states_airplane)[Tile.STATE_ACTIVE],
+                R.drawable.qs_airplane_icon_on
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun disabledModel_mapsCorrectly() {
+        val inputModel = AirplaneModeTileModel(false)
+
+        val outputState = mapper.map(airplaneModeConfig, inputModel)
+
+        val expectedState =
+            createAirplaneModeState(
+                QSTileState.ActivationState.INACTIVE,
+                context.resources.getStringArray(R.array.tile_states_airplane)[Tile.STATE_INACTIVE],
+                R.drawable.qs_airplane_icon_off
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    private fun createAirplaneModeState(
+        activationState: QSTileState.ActivationState,
+        secondaryLabel: String,
+        iconRes: Int
+    ): QSTileState {
+        val label = context.getString(R.string.airplane_mode)
+        return QSTileState(
+            { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+            iconRes,
+            label,
+            activationState,
+            secondaryLabel,
+            setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
+            label,
+            null,
+            QSTileState.SideViewIcon.None,
+            QSTileState.EnabledState.ENABLED,
+            Switch::class.qualifiedName
+        )
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt
index b4ff565..f1d08c0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt
@@ -18,6 +18,7 @@
 
 import android.app.IUriGrantsManager
 import android.content.ComponentName
+import android.content.Context
 import android.graphics.drawable.Drawable
 import android.graphics.drawable.Icon
 import android.graphics.drawable.TestStubDrawable
@@ -51,11 +52,13 @@
 class CustomTileMapperTest : SysuiTestCase() {
 
     private val uriGrantsManager: IUriGrantsManager = mock {}
+    private val mockContext =
+        mock<Context> { whenever(createContextAsUser(any(), any())).thenReturn(context) }
     private val kosmos =
         testKosmos().apply { customTileSpec = TileSpec.Companion.create(TEST_COMPONENT) }
     private val underTest by lazy {
         CustomTileMapper(
-            context = mock { whenever(createContextAsUser(any(), any())).thenReturn(context) },
+            context = mockContext,
             uriGrantsManager = uriGrantsManager,
         )
     }
@@ -164,7 +167,7 @@
                     )
                 val expected =
                     createTileState(
-                        activationState = QSTileState.ActivationState.INACTIVE,
+                        activationState = QSTileState.ActivationState.UNAVAILABLE,
                         icon = DEFAULT_DRAWABLE,
                     )
 
@@ -173,7 +176,7 @@
         }
 
     @Test
-    fun failedToLoadIconTileIsInactive() =
+    fun failedToLoadIconTileIsUnavailable() =
         with(kosmos) {
             testScope.runTest {
                 val actual =
@@ -187,13 +190,32 @@
                 val expected =
                     createTileState(
                         icon = null,
-                        activationState = QSTileState.ActivationState.INACTIVE,
+                        activationState = QSTileState.ActivationState.UNAVAILABLE,
                     )
 
                 assertThat(actual).isEqualTo(expected)
             }
         }
 
+    @Test
+    fun nullUserContextDoesNotCauseExceptionReturnsNullIconAndUnavailableState() =
+        with(kosmos) {
+            testScope.runTest {
+                // map() will catch this exception
+                whenever(mockContext.createContextAsUser(any(), any()))
+                    .thenThrow(IllegalStateException("Unable to create userContext"))
+
+                val actual = underTest.map(customTileQsTileConfig, createModel())
+
+                val expected =
+                    createTileState(
+                        icon = null,
+                        activationState = QSTileState.ActivationState.UNAVAILABLE,
+                    )
+                assertThat(actual).isEqualTo(expected)
+            }
+        }
+
     private fun Kosmos.createModel(
         tileState: Int = Tile.STATE_ACTIVE,
         tileIcon: Icon = createIcon(DRAWABLE, false),
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 13d6411..1ea8abc 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
@@ -204,7 +204,7 @@
 
             val actualIcon = latest?.icon
             assertThat(actualIcon).isEqualTo(expectedIcon)
-            assertThat(latest?.iconId).isNull()
+            assertThat(latest?.iconId).isEqualTo(WifiIcons.WIFI_NO_INTERNET_ICONS[4])
             assertThat(latest?.contentDescription.loadContentDescription(context))
                 .isEqualTo("$internet,test ssid")
             val expectedSd = wifiIcon.contentDescription
@@ -443,15 +443,15 @@
      * on the mentioned context. Since that context does not have a looper assigned to it, the
      * handler instantiation will throw a RuntimeException.
      *
-     * TODO(b/338068066): Robolectric behavior differs in that it does not throw the exception
-     * So either we should make Robolectric behvase similar to the device test, or change this
-     * test to look for a different signal than the exception, when run by Robolectric. For now
-     * we just assume the test is not Robolectric.
+     * TODO(b/338068066): Robolectric behavior differs in that it does not throw the exception So
+     *   either we should make Robolectric behave similar to the device test, or change this test to
+     *   look for a different signal than the exception, when run by Robolectric. For now we just
+     *   assume the test is not Robolectric.
      */
     @Test(expected = java.lang.RuntimeException::class)
     fun mobileDefault_usesNetworkNameAndIcon_throwsRunTimeException() =
         testScope.runTest {
-            assumeFalse(isRobolectricTest());
+            assumeFalse(isRobolectricTest())
 
             collectLastValue(underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)))
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index 3ca802e..0363808 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -49,9 +49,8 @@
 import com.android.systemui.scene.domain.startable.sceneContainerStartable
 import com.android.systemui.scene.shared.model.SceneFamilies
 import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModel
-import com.android.systemui.shade.ui.viewmodel.shadeHeaderViewModel
-import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
+import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModelFactory
+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
@@ -93,10 +92,9 @@
         sceneContainerStartable.start()
         underTest =
             QuickSettingsSceneViewModel(
-                brightnessMirrorViewModel = kosmos.brightnessMirrorViewModel,
-                shadeHeaderViewModel = kosmos.shadeHeaderViewModel,
+                brightnessMirrorViewModelFactory = kosmos.brightnessMirrorViewModelFactory,
+                shadeHeaderViewModelFactory = kosmos.shadeHeaderViewModelFactory,
                 qsSceneAdapter = qsFlexiglassAdapter,
-                notifications = kosmos.notificationsPlaceholderViewModel,
                 footerActionsViewModelFactory = footerActionsViewModelFactory,
                 footerActionsController = footerActionsController,
                 sceneBackInteractor = sceneBackInteractor,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModelTest.kt
similarity index 77%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModelTest.kt
index a7a3a0f..647fdf6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModelTest.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
 import com.android.systemui.qs.panels.ui.viewmodel.editModeViewModel
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver
@@ -44,6 +45,7 @@
 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
 
@@ -52,49 +54,54 @@
 @RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper
 @EnableSceneContainer
-class QuickSettingsShadeSceneViewModelTest : SysuiTestCase() {
+class QuickSettingsShadeSceneActionsViewModelTest : 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.quickSettingsShadeSceneViewModel }
+    private val underTest by lazy { kosmos.quickSettingsShadeSceneActionsViewModel }
+
+    @Before
+    fun setUp() {
+        underTest.activateIn(testScope)
+    }
 
     @Test
     fun upTransitionSceneKey_deviceLocked_lockscreen() =
         testScope.runTest {
-            val destinationScenes by collectLastValue(underTest.destinationScenes)
+            val actions by collectLastValue(underTest.actions)
             val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
             lockDevice()
 
-            assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
-            assertThat(destinationScenes?.get(Swipe.Down)).isNull()
+            assertThat(actions?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+            assertThat(actions?.get(Swipe.Down)).isNull()
             assertThat(homeScene).isEqualTo(Scenes.Lockscreen)
         }
 
     @Test
     fun upTransitionSceneKey_deviceLocked_keyguardDisabled_gone() =
         testScope.runTest {
-            val destinationScenes by collectLastValue(underTest.destinationScenes)
+            val actions by collectLastValue(underTest.actions)
             val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
             lockDevice()
             kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false)
 
-            assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+            assertThat(actions?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
             assertThat(homeScene).isEqualTo(Scenes.Gone)
         }
 
     @Test
     fun upTransitionSceneKey_deviceUnlocked_gone() =
         testScope.runTest {
-            val destinationScenes by collectLastValue(underTest.destinationScenes)
+            val actions by collectLastValue(underTest.actions)
             val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
             lockDevice()
             unlockDevice()
 
-            assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
-            assertThat(destinationScenes?.get(Swipe.Down)).isNull()
+            assertThat(actions?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+            assertThat(actions?.get(Swipe.Down)).isNull()
             assertThat(homeScene).isEqualTo(Scenes.Gone)
         }
 
@@ -102,12 +109,12 @@
     fun downTransitionSceneKey_deviceLocked_bottomAligned_lockscreen() =
         testScope.runTest {
             kosmos.fakeShadeRepository.setDualShadeAlignedToBottom(true)
-            val destinationScenes by collectLastValue(underTest.destinationScenes)
+            val actions by collectLastValue(underTest.actions)
             val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
             lockDevice()
 
-            assertThat(destinationScenes?.get(Swipe.Down)?.toScene).isEqualTo(SceneFamilies.Home)
-            assertThat(destinationScenes?.get(Swipe.Up)).isNull()
+            assertThat(actions?.get(Swipe.Down)?.toScene).isEqualTo(SceneFamilies.Home)
+            assertThat(actions?.get(Swipe.Up)).isNull()
             assertThat(homeScene).isEqualTo(Scenes.Lockscreen)
         }
 
@@ -115,20 +122,20 @@
     fun downTransitionSceneKey_deviceUnlocked_bottomAligned_gone() =
         testScope.runTest {
             kosmos.fakeShadeRepository.setDualShadeAlignedToBottom(true)
-            val destinationScenes by collectLastValue(underTest.destinationScenes)
+            val actions by collectLastValue(underTest.actions)
             val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
             lockDevice()
             unlockDevice()
 
-            assertThat(destinationScenes?.get(Swipe.Down)?.toScene).isEqualTo(SceneFamilies.Home)
-            assertThat(destinationScenes?.get(Swipe.Up)).isNull()
+            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 {
-            val destinationScenes by collectLastValue(underTest.destinationScenes)
+            val actions by collectLastValue(underTest.actions)
             val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
             kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
             kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
@@ -136,14 +143,14 @@
             )
             sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
 
-            assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+            assertThat(actions?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
             assertThat(homeScene).isEqualTo(Scenes.Lockscreen)
         }
 
     @Test
     fun upTransitionSceneKey_authMethodSwipe_lockscreenDismissed_goesToGone() =
         testScope.runTest {
-            val destinationScenes by collectLastValue(underTest.destinationScenes)
+            val actions by collectLastValue(underTest.actions)
             val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
             kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
             kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
@@ -152,26 +159,26 @@
             runCurrent()
             sceneInteractor.changeScene(Scenes.Gone, "reason")
 
-            assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+            assertThat(actions?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
             assertThat(homeScene).isEqualTo(Scenes.Gone)
         }
 
     @Test
     fun backTransitionSceneKey_notEditing_Home() =
         testScope.runTest {
-            val destinationScenes by collectLastValue(underTest.destinationScenes)
+            val actions by collectLastValue(underTest.actions)
 
-            assertThat(destinationScenes?.get(Back)?.toScene).isEqualTo(SceneFamilies.Home)
+            assertThat(actions?.get(Back)?.toScene).isEqualTo(SceneFamilies.Home)
         }
 
     @Test
     fun backTransition_editing_noDestination() =
         testScope.runTest {
-            val destinationScenes by collectLastValue(underTest.destinationScenes)
+            val actions by collectLastValue(underTest.actions)
             kosmos.editModeViewModel.startEditing()
 
-            assertThat(destinationScenes!!).isNotEmpty()
-            assertThat(destinationScenes?.get(Back)).isNull()
+            assertThat(actions!!).isNotEmpty()
+            assertThat(actions?.get(Back)).isNull()
         }
 
     private fun TestScope.lockDevice() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/OWNERS b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/OWNERS
new file mode 100644
index 0000000..e322e38
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/OWNERS
@@ -0,0 +1 @@
+file:/packages/SystemUI/src/com/android/systemui/scene/OWNERS
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 228d61a..66e45ab 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -48,9 +48,9 @@
 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.KeyguardTouchHandlingViewModel
-import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneActionsViewModel
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
 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
@@ -63,15 +63,15 @@
 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.ShadeSceneViewModel
-import com.android.systemui.shade.ui.viewmodel.shadeSceneViewModel
-import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
+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.shadeSceneContentViewModel
 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
 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.android.telecom.telecomManager
 import com.google.common.truth.Truth.assertThat
@@ -141,20 +141,16 @@
     private lateinit var bouncerActionButtonInteractor: BouncerActionButtonInteractor
     private lateinit var bouncerViewModel: BouncerViewModel
 
-    private val lockscreenSceneViewModel by lazy {
-        LockscreenSceneViewModel(
+    private val lockscreenSceneActionsViewModel by lazy {
+        LockscreenSceneActionsViewModel(
             deviceEntryInteractor = deviceEntryInteractor,
             communalInteractor = communalInteractor,
-            touchHandling =
-                KeyguardTouchHandlingViewModel(
-                    interactor = mock(),
-                ),
-            notifications = kosmos.notificationsPlaceholderViewModel,
             shadeInteractor = kosmos.shadeInteractor,
         )
     }
 
-    private lateinit var shadeSceneViewModel: ShadeSceneViewModel
+    private lateinit var shadeSceneContentViewModel: ShadeSceneContentViewModel
+    private lateinit var shadeSceneActionsViewModel: ShadeSceneActionsViewModel
 
     private val powerInteractor by lazy { kosmos.powerInteractor }
 
@@ -193,11 +189,16 @@
         bouncerActionButtonInteractor = kosmos.bouncerActionButtonInteractor
         bouncerViewModel = kosmos.bouncerViewModel
 
-        shadeSceneViewModel = kosmos.shadeSceneViewModel
+        shadeSceneContentViewModel = kosmos.shadeSceneContentViewModel
+        shadeSceneActionsViewModel = kosmos.shadeSceneActionsViewModel
 
         val startable = kosmos.sceneContainerStartable
         startable.start()
 
+        lockscreenSceneActionsViewModel.activateIn(testScope)
+        shadeSceneContentViewModel.activateIn(testScope)
+        shadeSceneActionsViewModel.activateIn(testScope)
+
         assertWithMessage("Initial scene key mismatch!")
             .that(sceneContainerViewModel.currentScene.value)
             .isEqualTo(sceneContainerConfig.initialSceneKey)
@@ -225,8 +226,8 @@
     @Test
     fun swipeUpOnLockscreen_enterCorrectPin_unlocksDevice() =
         testScope.runTest {
-            val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
-            val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
+            val actions by collectLastValue(lockscreenSceneActionsViewModel.actions)
+            val upDestinationSceneKey = actions?.get(Swipe.Up)?.toScene
             assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
             emulateUserDrivenTransition(
                 to = upDestinationSceneKey,
@@ -245,8 +246,8 @@
         testScope.runTest {
             setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
 
-            val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
-            val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
+            val actions by collectLastValue(lockscreenSceneActionsViewModel.actions)
+            val upDestinationSceneKey = actions?.get(Swipe.Up)?.toScene
             assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone)
             emulateUserDrivenTransition(
                 to = upDestinationSceneKey,
@@ -256,7 +257,7 @@
     @Test
     fun swipeUpOnShadeScene_withAuthMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
         testScope.runTest {
-            val destinationScenes by collectLastValue(shadeSceneViewModel.destinationScenes)
+            val actions by collectLastValue(shadeSceneActionsViewModel.actions)
             val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
             setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
             assertCurrentScene(Scenes.Lockscreen)
@@ -265,7 +266,7 @@
             emulateUserDrivenTransition(to = Scenes.Shade)
             assertCurrentScene(Scenes.Shade)
 
-            val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
+            val upDestinationSceneKey = actions?.get(Swipe.Up)?.toScene
             assertThat(upDestinationSceneKey).isEqualTo(SceneFamilies.Home)
             assertThat(homeScene).isEqualTo(Scenes.Lockscreen)
             emulateUserDrivenTransition(
@@ -276,7 +277,7 @@
     @Test
     fun swipeUpOnShadeScene_withAuthMethodSwipe_lockscreenDismissed_goesToGone() =
         testScope.runTest {
-            val destinationScenes by collectLastValue(shadeSceneViewModel.destinationScenes)
+            val actions by collectLastValue(shadeSceneActionsViewModel.actions)
             val canSwipeToEnter by collectLastValue(deviceEntryInteractor.canSwipeToEnter)
             val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
 
@@ -293,7 +294,7 @@
             emulateUserDrivenTransition(to = Scenes.Shade)
             assertCurrentScene(Scenes.Shade)
 
-            val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
+            val upDestinationSceneKey = actions?.get(Swipe.Up)?.toScene
             assertThat(upDestinationSceneKey).isEqualTo(SceneFamilies.Home)
             assertThat(homeScene).isEqualTo(Scenes.Gone)
             emulateUserDrivenTransition(
@@ -351,8 +352,8 @@
     fun swipeUpOnLockscreenWhileUnlocked_dismissesLockscreen() =
         testScope.runTest {
             unlockDevice()
-            val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
-            val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
+            val actions by collectLastValue(lockscreenSceneActionsViewModel.actions)
+            val upDestinationSceneKey = actions?.get(Swipe.Up)?.toScene
             assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone)
         }
 
@@ -373,8 +374,8 @@
     fun dismissingIme_whileOnPasswordBouncer_navigatesToLockscreen() =
         testScope.runTest {
             setAuthMethod(AuthenticationMethodModel.Password)
-            val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
-            val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
+            val actions by collectLastValue(lockscreenSceneActionsViewModel.actions)
+            val upDestinationSceneKey = actions?.get(Swipe.Up)?.toScene
             assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
             emulateUserDrivenTransition(
                 to = upDestinationSceneKey,
@@ -391,8 +392,8 @@
     fun bouncerActionButtonClick_opensEmergencyServicesDialer() =
         testScope.runTest {
             setAuthMethod(AuthenticationMethodModel.Password)
-            val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
-            val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
+            val actions by collectLastValue(lockscreenSceneActionsViewModel.actions)
+            val upDestinationSceneKey = actions?.get(Swipe.Up)?.toScene
             assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
             emulateUserDrivenTransition(to = upDestinationSceneKey)
 
@@ -411,8 +412,8 @@
         testScope.runTest {
             setAuthMethod(AuthenticationMethodModel.Password)
             startPhoneCall()
-            val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
-            val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
+            val actions by collectLastValue(lockscreenSceneActionsViewModel.actions)
+            val upDestinationSceneKey = actions?.get(Swipe.Up)?.toScene
             assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
             emulateUserDrivenTransition(to = upDestinationSceneKey)
 
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 f26c39d..2dfff3f 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
@@ -32,6 +32,7 @@
 import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
 import com.android.systemui.authentication.domain.interactor.authenticationInteractor
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
 import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
 import com.android.systemui.bouncer.shared.logging.BouncerUiEvent
 import com.android.systemui.classifier.FalsingCollector
@@ -109,6 +110,7 @@
     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 fakeSceneDataSource = kosmos.fakeSceneDataSource
@@ -275,6 +277,66 @@
         }
 
     @Test
+    fun switchFromLockscreenToGoneAndHideAltBouncerWhenDeviceUnlocked() =
+        testScope.runTest {
+            val alternateBouncerVisible by
+                collectLastValue(bouncerRepository.alternateBouncerVisible)
+            val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
+
+            bouncerRepository.setAlternateVisible(true)
+            assertThat(alternateBouncerVisible).isTrue()
+
+            prepareState(
+                authenticationMethod = AuthenticationMethodModel.Pin,
+                isDeviceUnlocked = false,
+                initialSceneKey = Scenes.Lockscreen,
+            )
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
+            underTest.start()
+
+            kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+                SuccessFingerprintAuthenticationStatus(0, true)
+            )
+
+            assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
+            assertThat(alternateBouncerVisible).isFalse()
+        }
+
+    @Test
+    fun stayOnCurrentSceneAndHideAltBouncerWhenDeviceUnlocked_whenLeaveOpenShade() =
+        testScope.runTest {
+            val alternateBouncerVisible by
+                collectLastValue(bouncerRepository.alternateBouncerVisible)
+            val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
+
+            kosmos.sysuiStatusBarStateController.leaveOpen = true // leave shade open
+            bouncerRepository.setAlternateVisible(true)
+            assertThat(alternateBouncerVisible).isTrue()
+
+            val transitionState =
+                prepareState(
+                    authenticationMethod = AuthenticationMethodModel.Pin,
+                    isDeviceUnlocked = false,
+                    initialSceneKey = Scenes.Lockscreen,
+                )
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
+            underTest.start()
+            runCurrent()
+
+            sceneInteractor.changeScene(Scenes.QuickSettings, "switching to qs for test")
+            transitionState.value = ObservableTransitionState.Idle(Scenes.QuickSettings)
+            runCurrent()
+            assertThat(currentSceneKey).isEqualTo(Scenes.QuickSettings)
+
+            kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+                SuccessFingerprintAuthenticationStatus(0, true)
+            )
+
+            assertThat(currentSceneKey).isEqualTo(Scenes.QuickSettings)
+            assertThat(alternateBouncerVisible).isFalse()
+        }
+
+    @Test
     fun switchFromBouncerToQuickSettingsWhenDeviceUnlocked_whenLeaveOpenShade() =
         testScope.runTest {
             val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneActionsViewModelTest.kt
similarity index 91%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneActionsViewModelTest.kt
index a4992e2..b526275 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneActionsViewModelTest.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.lifecycle.activateIn
 import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
 import com.android.systemui.shade.data.repository.shadeRepository
 import com.android.systemui.shade.domain.interactor.shadeInteractor
@@ -42,25 +43,26 @@
 @RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper
 @EnableSceneContainer
-class GoneSceneViewModelTest : SysuiTestCase() {
+class GoneSceneActionsViewModelTest : SysuiTestCase() {
 
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
     private val shadeRepository by lazy { kosmos.shadeRepository }
-    private lateinit var underTest: GoneSceneViewModel
+    private lateinit var underTest: GoneSceneActionsViewModel
 
     @Before
     fun setUp() {
         underTest =
-            GoneSceneViewModel(
+            GoneSceneActionsViewModel(
                 shadeInteractor = kosmos.shadeInteractor,
             )
+        underTest.activateIn(testScope)
     }
 
     @Test
     fun downTransitionKey_splitShadeEnabled_isGoneToSplitShade() =
         testScope.runTest {
-            val destinationScenes by collectLastValue(underTest.destinationScenes)
+            val destinationScenes by collectLastValue(underTest.actions)
             shadeRepository.setShadeLayoutWide(true)
             runCurrent()
 
@@ -71,7 +73,7 @@
     @Test
     fun downTransitionKey_splitShadeDisabled_isNull() =
         testScope.runTest {
-            val destinationScenes by collectLastValue(underTest.destinationScenes)
+            val destinationScenes by collectLastValue(underTest.actions)
             shadeRepository.setShadeLayoutWide(false)
             runCurrent()
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModelTest.kt
new file mode 100644
index 0000000..206d3ac
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModelTest.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.scene.ui.viewmodel
+
+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.SwipeDirection
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
+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.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
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SceneActionsViewModelTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+
+    private val underTest = FakeSceneActionsViewModel()
+
+    @Test
+    fun actions_emptyBeforeActivation() =
+        testScope.runTest {
+            val actions by collectLastValue(underTest.actions)
+
+            assertThat(underTest.isActive).isFalse()
+            assertThat(actions).isEmpty()
+        }
+
+    @Test
+    fun actions_emptyBeforeFirstValue() =
+        testScope.runTest {
+            val actions by collectLastValue(underTest.actions)
+            underTest.activateIn(testScope)
+            runCurrent()
+
+            assertThat(underTest.isActive).isTrue()
+            assertThat(actions).isEmpty()
+        }
+
+    @Test
+    fun actions() =
+        testScope.runTest {
+            val actions by collectLastValue(underTest.actions)
+            underTest.activateIn(testScope)
+            runCurrent()
+            assertThat(underTest.isActive).isTrue()
+
+            val expected1 =
+                mapOf(
+                    Back to UserActionResult(toScene = Scenes.Gone),
+                    Swipe(SwipeDirection.Up) to UserActionResult(toScene = Scenes.Shade)
+                )
+            underTest.upstream.value = expected1
+            runCurrent()
+            assertThat(actions).isEqualTo(expected1)
+
+            val expected2 =
+                mapOf(
+                    Back to UserActionResult(toScene = Scenes.Lockscreen),
+                    Swipe(SwipeDirection.Down) to UserActionResult(toScene = Scenes.Shade)
+                )
+            underTest.upstream.value = expected2
+            runCurrent()
+            assertThat(actions).isEqualTo(expected2)
+        }
+
+    @Test
+    fun actions_emptyAfterCancellation() =
+        testScope.runTest {
+            val actions by collectLastValue(underTest.actions)
+            val job = Job()
+            underTest.activateIn(testScope, job)
+            runCurrent()
+
+            val expected =
+                mapOf(
+                    Back to UserActionResult(toScene = Scenes.Lockscreen),
+                    Swipe(SwipeDirection.Down) to UserActionResult(toScene = Scenes.Shade)
+                )
+            underTest.upstream.value = expected
+            runCurrent()
+            assertThat(actions).isEqualTo(expected)
+
+            job.cancel()
+            runCurrent()
+            assertThat(underTest.isActive).isFalse()
+            assertThat(actions).isEmpty()
+        }
+
+    private class FakeSceneActionsViewModel : SceneActionsViewModel() {
+
+        val upstream = MutableStateFlow<Map<UserAction, UserActionResult>>(emptyMap())
+
+        override suspend fun hydrateActions(
+            setActions: (Map<UserAction, UserActionResult>) -> Unit,
+        ) {
+            upstream.collectLatest { setActions(it) }
+        }
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
index 8a43198..fadb1d7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
@@ -22,13 +22,13 @@
 import com.android.systemui.SysuiTestCase
 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.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.res.R
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shade.shadeTestUtil
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth
 import com.google.common.truth.Truth.assertThat
@@ -43,6 +43,7 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
 class ShadeInteractorSceneContainerImplTest : SysuiTestCase() {
 
     private val kosmos = testKosmos()
@@ -50,7 +51,7 @@
     private val configurationRepository = kosmos.fakeConfigurationRepository
     private val keyguardRepository = kosmos.fakeKeyguardRepository
     private val sceneInteractor = kosmos.sceneInteractor
-    private val shadeRepository = kosmos.shadeRepository
+    private val shadeTestUtil = kosmos.shadeTestUtil
 
     private val underTest = kosmos.shadeInteractorSceneContainerImpl
 
@@ -60,7 +61,7 @@
             val actual by collectLastValue(underTest.qsExpansion)
 
             // WHEN split shade is enabled and QS is expanded
-            overrideResource(R.bool.config_use_split_notification_shade, true)
+            shadeTestUtil.setSplitShade(true)
             configurationRepository.onAnyConfigurationChange()
             runCurrent()
             val transitionState =
@@ -89,7 +90,7 @@
 
             // WHEN split shade is not enabled and QS is expanded
             keyguardRepository.setStatusBarState(StatusBarState.SHADE)
-            overrideResource(R.bool.config_use_split_notification_shade, false)
+            shadeTestUtil.setSplitShade(false)
             configurationRepository.onAnyConfigurationChange()
             runCurrent()
             val progress = MutableStateFlow(.3f)
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 add33da..6a88664 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
@@ -43,7 +43,7 @@
     }
 
     @Test
-    fun transitionToOccluded() =
+    fun transitionToOccludedByOCCLUDEDTransition() =
         testScope.runTest {
             val isKeyguardOccluded by collectLastValue(underTest.isKeyguardOccluded)
             assertThat(isKeyguardOccluded).isFalse()
@@ -62,4 +62,25 @@
             )
             assertThat(isKeyguardOccluded).isFalse()
         }
+
+    @Test
+    fun transitionToOccludedByDREAMINGTransition() =
+        testScope.runTest {
+            val isKeyguardOccluded by collectLastValue(underTest.isKeyguardOccluded)
+            assertThat(isKeyguardOccluded).isFalse()
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.DREAMING,
+                testScope,
+            )
+            assertThat(isKeyguardOccluded).isTrue()
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.DREAMING,
+                to = KeyguardState.AOD,
+                testScope,
+            )
+            assertThat(isKeyguardOccluded).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
index 0ffabd8..3f087b4 100644
--- 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
@@ -29,6 +29,7 @@
 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
@@ -37,6 +38,7 @@
 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,6 +56,11 @@
 
     private val underTest = kosmos.overlayShadeViewModel
 
+    @Before
+    fun setUp() {
+        underTest.activateIn(testScope)
+    }
+
     @Test
     fun backgroundScene_deviceLocked_lockscreen() =
         testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
index 3ded8a3..f6fe667ff 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
@@ -15,6 +15,7 @@
 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.plugins.activityStarter
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
@@ -52,6 +53,7 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        underTest.activateIn(testScope)
     }
 
     @Test
@@ -107,15 +109,15 @@
 
     @Test
     fun onSystemIconContainerClicked_unlocked_collapsesShadeToGone() =
-            testScope.runTest {
-                setDeviceEntered(true)
-                setScene(Scenes.Shade)
+        testScope.runTest {
+            setDeviceEntered(true)
+            setScene(Scenes.Shade)
 
-                underTest.onSystemIconContainerClicked()
-                runCurrent()
+            underTest.onSystemIconContainerClicked()
+            runCurrent()
 
-                assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Gone)
-            }
+            assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Gone)
+        }
 
     companion object {
         private val SUB_1 =
@@ -137,7 +139,7 @@
     private fun setScene(key: SceneKey) {
         sceneInteractor.changeScene(key, "test")
         sceneInteractor.setTransitionState(
-                MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key))
+            MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key))
         )
         testScope.runCurrent()
     }
@@ -146,16 +148,16 @@
         if (isEntered) {
             // Unlock the device marking the device has entered.
             kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
-                    SuccessFingerprintAuthenticationStatus(0, true)
+                SuccessFingerprintAuthenticationStatus(0, true)
             )
             runCurrent()
         }
         setScene(
-                if (isEntered) {
-                    Scenes.Gone
-                } else {
-                    Scenes.Lockscreen
-                }
+            if (isEntered) {
+                Scenes.Gone
+            } else {
+                Scenes.Lockscreen
+            }
         )
         assertThat(deviceEntryInteractor.isDeviceEntered.value).isEqualTo(isEntered)
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModelTest.kt
new file mode 100644
index 0000000..06a02c6
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModelTest.kt
@@ -0,0 +1,237 @@
+/*
+ * 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.shade.ui.viewmodel
+
+import android.platform.test.annotations.DisableFlags
+import android.testing.TestableLooper
+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.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
+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.deviceEntryInteractor
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.qs.ui.adapter.fakeQSSceneAdapter
+import com.android.systemui.res.R
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+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.scene.shared.model.TransitionKeys.ToSplitShade
+import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shade.domain.startable.shadeStartable
+import com.android.systemui.shade.shared.flag.DualShade
+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.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
+@DisableFlags(DualShade.FLAG_NAME)
+class ShadeSceneActionsViewModelTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val sceneInteractor by lazy { kosmos.sceneInteractor }
+    private val shadeRepository by lazy { kosmos.shadeRepository }
+    private val qsSceneAdapter by lazy { kosmos.fakeQSSceneAdapter }
+
+    private val underTest: ShadeSceneActionsViewModel by lazy { kosmos.shadeSceneActionsViewModel }
+
+    @Before
+    fun setUp() {
+        underTest.activateIn(testScope)
+    }
+
+    @Test
+    fun upTransitionSceneKey_deviceLocked_lockScreen() =
+        testScope.runTest {
+            val actions by collectLastValue(underTest.actions)
+            val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
+
+            assertThat(actions?.get(Swipe(SwipeDirection.Up))?.toScene)
+                .isEqualTo(SceneFamilies.Home)
+            assertThat(homeScene).isEqualTo(Scenes.Lockscreen)
+        }
+
+    @Test
+    fun upTransitionSceneKey_deviceUnlocked_gone() =
+        testScope.runTest {
+            val actions by collectLastValue(underTest.actions)
+            val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
+            setDeviceEntered(true)
+
+            assertThat(actions?.get(Swipe(SwipeDirection.Up))?.toScene)
+                .isEqualTo(SceneFamilies.Home)
+            assertThat(homeScene).isEqualTo(Scenes.Gone)
+        }
+
+    @Test
+    fun upTransitionSceneKey_keyguardDisabled_gone() =
+        testScope.runTest {
+            val actions by collectLastValue(underTest.actions)
+            val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
+            kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false)
+
+            assertThat(actions?.get(Swipe(SwipeDirection.Up))?.toScene)
+                .isEqualTo(SceneFamilies.Home)
+            assertThat(homeScene).isEqualTo(Scenes.Gone)
+        }
+
+    @Test
+    fun upTransitionSceneKey_authMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
+        testScope.runTest {
+            val actions by collectLastValue(underTest.actions)
+            val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
+            kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.None
+            )
+            sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
+
+            assertThat(actions?.get(Swipe(SwipeDirection.Up))?.toScene)
+                .isEqualTo(SceneFamilies.Home)
+            assertThat(homeScene).isEqualTo(Scenes.Lockscreen)
+        }
+
+    @Test
+    fun upTransitionSceneKey_authMethodSwipe_lockscreenDismissed_goesToGone() =
+        testScope.runTest {
+            val actions by collectLastValue(underTest.actions)
+            val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
+            kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.None
+            )
+            runCurrent()
+            sceneInteractor.changeScene(Scenes.Gone, "reason")
+
+            assertThat(actions?.get(Swipe(SwipeDirection.Up))?.toScene)
+                .isEqualTo(SceneFamilies.Home)
+            assertThat(homeScene).isEqualTo(Scenes.Gone)
+        }
+
+    @Test
+    fun upTransitionKey_splitShadeEnabled_isGoneToSplitShade() =
+        testScope.runTest {
+            val actions by collectLastValue(underTest.actions)
+            shadeRepository.setShadeLayoutWide(true)
+            runCurrent()
+
+            assertThat(actions?.get(Swipe(SwipeDirection.Up))?.transitionKey)
+                .isEqualTo(ToSplitShade)
+        }
+
+    @Test
+    fun upTransitionKey_splitShadeDisable_isNull() =
+        testScope.runTest {
+            val actions by collectLastValue(underTest.actions)
+            shadeRepository.setShadeLayoutWide(false)
+            runCurrent()
+
+            assertThat(actions?.get(Swipe(SwipeDirection.Up))?.transitionKey).isNull()
+        }
+
+    @Test
+    fun downTransitionSceneKey_inSplitShade_null() =
+        testScope.runTest {
+            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()
+        }
+
+    @Test
+    fun downTransitionSceneKey_notSplitShade_quickSettings() =
+        testScope.runTest {
+            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)
+                .isEqualTo(Scenes.QuickSettings)
+        }
+
+    @Test
+    fun upTransitionSceneKey_customizing_noTransition() =
+        testScope.runTest {
+            val actions by collectLastValue(underTest.actions)
+
+            qsSceneAdapter.setCustomizing(true)
+            assertThat(
+                    actions!!.keys.filterIsInstance<Swipe>().filter {
+                        it.direction == SwipeDirection.Up
+                    }
+                )
+                .isEmpty()
+        }
+
+    private fun TestScope.setDeviceEntered(isEntered: Boolean) {
+        if (isEntered) {
+            // Unlock the device marking the device has 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) {
+        sceneInteractor.changeScene(key, "test")
+        sceneInteractor.setTransitionState(
+            MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key))
+        )
+        runCurrent()
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModelTest.kt
new file mode 100644
index 0000000..558606f
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModelTest.kt
@@ -0,0 +1,231 @@
+/*
+ * 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.shade.ui.viewmodel
+
+import android.platform.test.annotations.DisableFlags
+import android.testing.TestableLooper
+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.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+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.media.controls.data.repository.mediaFilterRepository
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.qs.ui.adapter.fakeQSSceneAdapter
+import com.android.systemui.res.R
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shade.shared.flag.DualShade
+import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.testKosmos
+import com.android.systemui.unfold.fakeUnfoldTransitionProgressProvider
+import com.google.common.truth.Truth.assertThat
+import java.util.Locale
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.combine
+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
+@DisableFlags(DualShade.FLAG_NAME)
+class ShadeSceneContentViewModelTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val sceneInteractor by lazy { kosmos.sceneInteractor }
+    private val shadeRepository by lazy { kosmos.shadeRepository }
+    private val qsSceneAdapter by lazy { kosmos.fakeQSSceneAdapter }
+
+    private val underTest: ShadeSceneContentViewModel by lazy { kosmos.shadeSceneContentViewModel }
+
+    @Before
+    fun setUp() {
+        underTest.activateIn(testScope)
+    }
+
+    @Test
+    fun isEmptySpaceClickable_deviceUnlocked_false() =
+        testScope.runTest {
+            val isEmptySpaceClickable by collectLastValue(underTest.isEmptySpaceClickable)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
+            setDeviceEntered(true)
+            runCurrent()
+
+            assertThat(isEmptySpaceClickable).isFalse()
+        }
+
+    @Test
+    fun isEmptySpaceClickable_deviceLockedSecurely_true() =
+        testScope.runTest {
+            val isEmptySpaceClickable by collectLastValue(underTest.isEmptySpaceClickable)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
+            runCurrent()
+
+            assertThat(isEmptySpaceClickable).isTrue()
+        }
+
+    @Test
+    fun onEmptySpaceClicked_deviceLockedSecurely_switchesToLockscreen() =
+        testScope.runTest {
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
+            runCurrent()
+
+            underTest.onEmptySpaceClicked()
+
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+        }
+
+    @Test
+    fun addAndRemoveMedia_mediaVisibilityisUpdated() =
+        testScope.runTest {
+            val isMediaVisible by collectLastValue(underTest.isMediaVisible)
+            val userMedia = MediaData(active = true)
+
+            assertThat(isMediaVisible).isFalse()
+
+            kosmos.mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
+
+            assertThat(isMediaVisible).isTrue()
+
+            kosmos.mediaFilterRepository.removeSelectedUserMediaEntry(userMedia.instanceId)
+
+            assertThat(isMediaVisible).isFalse()
+        }
+
+    @Test
+    fun shadeMode() =
+        testScope.runTest {
+            val shadeMode by collectLastValue(underTest.shadeMode)
+
+            shadeRepository.setShadeLayoutWide(true)
+            assertThat(shadeMode).isEqualTo(ShadeMode.Split)
+
+            shadeRepository.setShadeLayoutWide(false)
+            assertThat(shadeMode).isEqualTo(ShadeMode.Single)
+
+            shadeRepository.setShadeLayoutWide(true)
+            assertThat(shadeMode).isEqualTo(ShadeMode.Split)
+        }
+
+    @Test
+    fun unfoldTransitionProgress() =
+        testScope.runTest {
+            val maxTranslation = prepareConfiguration()
+            val translations by
+                collectLastValue(
+                    combine(
+                        underTest.unfoldTranslationX(isOnStartSide = true),
+                        underTest.unfoldTranslationX(isOnStartSide = false),
+                    ) { start, end ->
+                        Translations(
+                            start = start,
+                            end = end,
+                        )
+                    }
+                )
+
+            val unfoldProvider = kosmos.fakeUnfoldTransitionProgressProvider
+            unfoldProvider.onTransitionStarted()
+            assertThat(translations?.start).isEqualTo(0f)
+            assertThat(translations?.end).isEqualTo(-0f)
+
+            repeat(10) { repetition ->
+                val transitionProgress = 0.1f * (repetition + 1)
+                unfoldProvider.onTransitionProgress(transitionProgress)
+                assertThat(translations?.start).isEqualTo((1 - transitionProgress) * maxTranslation)
+                assertThat(translations?.end).isEqualTo(-(1 - transitionProgress) * maxTranslation)
+            }
+
+            unfoldProvider.onTransitionFinishing()
+            assertThat(translations?.start).isEqualTo(0f)
+            assertThat(translations?.end).isEqualTo(-0f)
+
+            unfoldProvider.onTransitionFinished()
+            assertThat(translations?.start).isEqualTo(0f)
+            assertThat(translations?.end).isEqualTo(-0f)
+        }
+
+    private fun prepareConfiguration(): Int {
+        val configuration = context.resources.configuration
+        configuration.setLayoutDirection(Locale.US)
+        kosmos.fakeConfigurationRepository.onConfigurationChange(configuration)
+        val maxTranslation = 10
+        kosmos.fakeConfigurationRepository.setDimensionPixelSize(
+            R.dimen.notification_side_paddings,
+            maxTranslation
+        )
+        return maxTranslation
+    }
+
+    private fun TestScope.setDeviceEntered(isEntered: Boolean) {
+        if (isEntered) {
+            // Unlock the device marking the device has 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) {
+        sceneInteractor.changeScene(key, "test")
+        sceneInteractor.setTransitionState(
+            MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key))
+        )
+        runCurrent()
+    }
+
+    private data class Translations(
+        val start: Float,
+        val end: Float,
+    )
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
deleted file mode 100644
index 3b2c981..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ /dev/null
@@ -1,370 +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.shade.ui.viewmodel
-
-import android.platform.test.annotations.DisableFlags
-import android.testing.TestableLooper
-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.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
-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.common.ui.data.repository.fakeConfigurationRepository
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
-import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
-import com.android.systemui.flags.EnableSceneContainer
-import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
-import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
-import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.lifecycle.activateIn
-import com.android.systemui.media.controls.data.repository.mediaFilterRepository
-import com.android.systemui.media.controls.shared.model.MediaData
-import com.android.systemui.qs.ui.adapter.fakeQSSceneAdapter
-import com.android.systemui.res.R
-import com.android.systemui.scene.domain.interactor.sceneInteractor
-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.scene.shared.model.TransitionKeys.ToSplitShade
-import com.android.systemui.shade.data.repository.shadeRepository
-import com.android.systemui.shade.domain.startable.shadeStartable
-import com.android.systemui.shade.shared.flag.DualShade
-import com.android.systemui.shade.shared.model.ShadeMode
-import com.android.systemui.testKosmos
-import com.android.systemui.unfold.fakeUnfoldTransitionProgressProvider
-import com.google.common.truth.Truth.assertThat
-import java.util.Locale
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.combine
-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
-@DisableFlags(DualShade.FLAG_NAME)
-class ShadeSceneViewModelTest : SysuiTestCase() {
-
-    private val kosmos = testKosmos()
-    private val testScope = kosmos.testScope
-    private val sceneInteractor by lazy { kosmos.sceneInteractor }
-    private val shadeRepository by lazy { kosmos.shadeRepository }
-    private val qsSceneAdapter by lazy { kosmos.fakeQSSceneAdapter }
-
-    private val underTest: ShadeSceneViewModel by lazy { kosmos.shadeSceneViewModel }
-
-    @Before
-    fun setUp() {
-        underTest.activateIn(testScope)
-    }
-
-    @Test
-    fun upTransitionSceneKey_deviceLocked_lockScreen() =
-        testScope.runTest {
-            val destinationScenes by collectLastValue(underTest.destinationScenes)
-            val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
-            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
-                AuthenticationMethodModel.Pin
-            )
-
-            assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene)
-                .isEqualTo(SceneFamilies.Home)
-            assertThat(homeScene).isEqualTo(Scenes.Lockscreen)
-        }
-
-    @Test
-    fun upTransitionSceneKey_deviceUnlocked_gone() =
-        testScope.runTest {
-            val destinationScenes by collectLastValue(underTest.destinationScenes)
-            val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
-            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
-                AuthenticationMethodModel.Pin
-            )
-            setDeviceEntered(true)
-
-            assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene)
-                .isEqualTo(SceneFamilies.Home)
-            assertThat(homeScene).isEqualTo(Scenes.Gone)
-        }
-
-    @Test
-    fun upTransitionSceneKey_keyguardDisabled_gone() =
-        testScope.runTest {
-            val destinationScenes by collectLastValue(underTest.destinationScenes)
-            val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
-            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
-                AuthenticationMethodModel.Pin
-            )
-            kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false)
-
-            assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene)
-                .isEqualTo(SceneFamilies.Home)
-            assertThat(homeScene).isEqualTo(Scenes.Gone)
-        }
-
-    @Test
-    fun upTransitionSceneKey_authMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
-        testScope.runTest {
-            val destinationScenes by collectLastValue(underTest.destinationScenes)
-            val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
-            kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
-            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
-                AuthenticationMethodModel.None
-            )
-            sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
-
-            assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene)
-                .isEqualTo(SceneFamilies.Home)
-            assertThat(homeScene).isEqualTo(Scenes.Lockscreen)
-        }
-
-    @Test
-    fun upTransitionSceneKey_authMethodSwipe_lockscreenDismissed_goesToGone() =
-        testScope.runTest {
-            val destinationScenes by collectLastValue(underTest.destinationScenes)
-            val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
-            kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
-            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
-                AuthenticationMethodModel.None
-            )
-            runCurrent()
-            sceneInteractor.changeScene(Scenes.Gone, "reason")
-
-            assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene)
-                .isEqualTo(SceneFamilies.Home)
-            assertThat(homeScene).isEqualTo(Scenes.Gone)
-        }
-
-    @Test
-    fun upTransitionKey_splitShadeEnabled_isGoneToSplitShade() =
-        testScope.runTest {
-            val destinationScenes by collectLastValue(underTest.destinationScenes)
-            shadeRepository.setShadeLayoutWide(true)
-            runCurrent()
-
-            assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.transitionKey)
-                .isEqualTo(ToSplitShade)
-        }
-
-    @Test
-    fun upTransitionKey_splitShadeDisable_isNull() =
-        testScope.runTest {
-            val destinationScenes by collectLastValue(underTest.destinationScenes)
-            shadeRepository.setShadeLayoutWide(false)
-            runCurrent()
-
-            assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.transitionKey).isNull()
-        }
-
-    @Test
-    fun isClickable_deviceUnlocked_false() =
-        testScope.runTest {
-            val isClickable by collectLastValue(underTest.isClickable)
-            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
-                AuthenticationMethodModel.Pin
-            )
-            setDeviceEntered(true)
-            runCurrent()
-
-            assertThat(isClickable).isFalse()
-        }
-
-    @Test
-    fun isClickable_deviceLockedSecurely_true() =
-        testScope.runTest {
-            val isClickable by collectLastValue(underTest.isClickable)
-            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
-                AuthenticationMethodModel.Pin
-            )
-            runCurrent()
-
-            assertThat(isClickable).isTrue()
-        }
-
-    @Test
-    fun onContentClicked_deviceLockedSecurely_switchesToLockscreen() =
-        testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
-            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
-                AuthenticationMethodModel.Pin
-            )
-            runCurrent()
-
-            underTest.onContentClicked()
-
-            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
-        }
-
-    @Test
-    fun addAndRemoveMedia_mediaVisibilityisUpdated() =
-        testScope.runTest {
-            val isMediaVisible by collectLastValue(underTest.isMediaVisible)
-            val userMedia = MediaData(active = true)
-
-            assertThat(isMediaVisible).isFalse()
-
-            kosmos.mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
-
-            assertThat(isMediaVisible).isTrue()
-
-            kosmos.mediaFilterRepository.removeSelectedUserMediaEntry(userMedia.instanceId)
-
-            assertThat(isMediaVisible).isFalse()
-        }
-
-    @Test
-    fun downTransitionSceneKey_inSplitShade_null() =
-        testScope.runTest {
-            overrideResource(R.bool.config_use_split_notification_shade, true)
-            kosmos.shadeStartable.start()
-            val destinationScenes by collectLastValue(underTest.destinationScenes)
-            assertThat(destinationScenes?.get(Swipe(SwipeDirection.Down))?.toScene).isNull()
-        }
-
-    @Test
-    fun downTransitionSceneKey_notSplitShade_quickSettings() =
-        testScope.runTest {
-            overrideResource(R.bool.config_use_split_notification_shade, false)
-            kosmos.shadeStartable.start()
-            val destinationScenes by collectLastValue(underTest.destinationScenes)
-            assertThat(destinationScenes?.get(Swipe(SwipeDirection.Down))?.toScene)
-                .isEqualTo(Scenes.QuickSettings)
-        }
-
-    @Test
-    fun upTransitionSceneKey_customizing_noTransition() =
-        testScope.runTest {
-            val destinationScenes by collectLastValue(underTest.destinationScenes)
-
-            qsSceneAdapter.setCustomizing(true)
-            assertThat(
-                    destinationScenes!!.keys.filterIsInstance<Swipe>().filter {
-                        it.direction == SwipeDirection.Up
-                    }
-                )
-                .isEmpty()
-        }
-
-    @Test
-    fun shadeMode() =
-        testScope.runTest {
-            val shadeMode by collectLastValue(underTest.shadeMode)
-
-            shadeRepository.setShadeLayoutWide(true)
-            assertThat(shadeMode).isEqualTo(ShadeMode.Split)
-
-            shadeRepository.setShadeLayoutWide(false)
-            assertThat(shadeMode).isEqualTo(ShadeMode.Single)
-
-            shadeRepository.setShadeLayoutWide(true)
-            assertThat(shadeMode).isEqualTo(ShadeMode.Split)
-        }
-
-    @Test
-    fun unfoldTransitionProgress() =
-        testScope.runTest {
-            val maxTranslation = prepareConfiguration()
-            val translations by
-                collectLastValue(
-                    combine(
-                        underTest.unfoldTranslationX(isOnStartSide = true),
-                        underTest.unfoldTranslationX(isOnStartSide = false),
-                    ) { start, end ->
-                        Translations(
-                            start = start,
-                            end = end,
-                        )
-                    }
-                )
-
-            val unfoldProvider = kosmos.fakeUnfoldTransitionProgressProvider
-            unfoldProvider.onTransitionStarted()
-            assertThat(translations?.start).isEqualTo(0f)
-            assertThat(translations?.end).isEqualTo(-0f)
-
-            repeat(10) { repetition ->
-                val transitionProgress = 0.1f * (repetition + 1)
-                unfoldProvider.onTransitionProgress(transitionProgress)
-                assertThat(translations?.start).isEqualTo((1 - transitionProgress) * maxTranslation)
-                assertThat(translations?.end).isEqualTo(-(1 - transitionProgress) * maxTranslation)
-            }
-
-            unfoldProvider.onTransitionFinishing()
-            assertThat(translations?.start).isEqualTo(0f)
-            assertThat(translations?.end).isEqualTo(-0f)
-
-            unfoldProvider.onTransitionFinished()
-            assertThat(translations?.start).isEqualTo(0f)
-            assertThat(translations?.end).isEqualTo(-0f)
-        }
-
-    private fun prepareConfiguration(): Int {
-        val configuration = context.resources.configuration
-        configuration.setLayoutDirection(Locale.US)
-        kosmos.fakeConfigurationRepository.onConfigurationChange(configuration)
-        val maxTranslation = 10
-        kosmos.fakeConfigurationRepository.setDimensionPixelSize(
-            R.dimen.notification_side_paddings,
-            maxTranslation
-        )
-        return maxTranslation
-    }
-
-    private fun TestScope.setDeviceEntered(isEntered: Boolean) {
-        if (isEntered) {
-            // Unlock the device marking the device has 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) {
-        sceneInteractor.changeScene(key, "test")
-        sceneInteractor.setTransitionState(
-            MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key))
-        )
-        runCurrent()
-    }
-
-    private data class Translations(
-        val start: Float,
-        val end: Float,
-    )
-}
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 9fea7a2..733cac9 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
@@ -56,6 +56,7 @@
 import com.android.systemui.keyguard.ui.viewmodel.keyguardRootViewModel
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
+import com.android.systemui.shade.data.repository.fakeShadeRepository
 import com.android.systemui.shade.mockLargeScreenHeaderHelper
 import com.android.systemui.shade.shadeTestUtil
 import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
@@ -135,11 +136,14 @@
     val communalSceneRepository
         get() = kosmos.communalSceneRepository
 
+    val shadeRepository
+        get() = kosmos.fakeShadeRepository
+
     lateinit var underTest: SharedNotificationContainerViewModel
 
     @Before
     fun setUp() {
-        overrideResource(R.bool.config_use_split_notification_shade, false)
+        shadeTestUtil.setSplitShade(false)
         movementFlow = MutableStateFlow(BurnInModel())
         whenever(aodBurnInViewModel.movement(any())).thenReturn(movementFlow)
         underTest = kosmos.sharedNotificationContainerViewModel
@@ -148,7 +152,7 @@
     @Test
     fun validateMarginStartInSplitShade() =
         testScope.runTest {
-            overrideResource(R.bool.config_use_split_notification_shade, true)
+            shadeTestUtil.setSplitShade(true)
             overrideResource(R.dimen.notification_panel_margin_horizontal, 20)
 
             val dimens by collectLastValue(underTest.configurationBasedDimensions)
@@ -161,7 +165,7 @@
     @Test
     fun validateMarginStart() =
         testScope.runTest {
-            overrideResource(R.bool.config_use_split_notification_shade, false)
+            shadeTestUtil.setSplitShade(false)
             overrideResource(R.dimen.notification_panel_margin_horizontal, 20)
 
             val dimens by collectLastValue(underTest.configurationBasedDimensions)
@@ -172,10 +176,10 @@
         }
 
     @Test
-    fun validatePaddingTopInSplitShade_refactorFlagOn_usesLargeHeaderHelper() =
+    fun validatePaddingTopInSplitShade_usesLargeHeaderHelper() =
         testScope.runTest {
             whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5)
-            overrideResource(R.bool.config_use_split_notification_shade, true)
+            shadeTestUtil.setSplitShade(true)
             overrideResource(R.bool.config_use_large_screen_shade_header, true)
             overrideResource(R.dimen.large_screen_shade_header_height, 10)
             overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
@@ -191,7 +195,7 @@
     fun validatePaddingTopInNonSplitShade_usesLargeScreenHeader() =
         testScope.runTest {
             whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(10)
-            overrideResource(R.bool.config_use_split_notification_shade, false)
+            shadeTestUtil.setSplitShade(false)
             overrideResource(R.bool.config_use_large_screen_shade_header, true)
             overrideResource(R.dimen.large_screen_shade_header_height, 10)
             overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
@@ -207,7 +211,7 @@
     fun validatePaddingTopInNonSplitShade_doesNotUseLargeScreenHeader() =
         testScope.runTest {
             whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(10)
-            overrideResource(R.bool.config_use_split_notification_shade, false)
+            shadeTestUtil.setSplitShade(false)
             overrideResource(R.bool.config_use_large_screen_shade_header, false)
             overrideResource(R.dimen.large_screen_shade_header_height, 10)
             overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
@@ -508,7 +512,7 @@
             val bounds by collectLastValue(underTest.bounds)
 
             // When not in split shade
-            overrideResource(R.bool.config_use_split_notification_shade, false)
+            shadeTestUtil.setSplitShade(false)
             configurationRepository.onAnyConfigurationChange()
             runCurrent()
 
@@ -567,7 +571,7 @@
 
             // When in split shade
             whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5)
-            overrideResource(R.bool.config_use_split_notification_shade, true)
+            shadeTestUtil.setSplitShade(true)
             overrideResource(R.bool.config_use_large_screen_shade_header, true)
             overrideResource(R.dimen.large_screen_shade_header_height, 10)
             overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
@@ -628,7 +632,7 @@
             advanceTimeBy(50L)
             showLockscreen()
 
-            overrideResource(R.bool.config_use_split_notification_shade, false)
+            shadeTestUtil.setSplitShade(false)
             configurationRepository.onAnyConfigurationChange()
 
             assertThat(maxNotifications).isEqualTo(10)
@@ -656,7 +660,7 @@
             advanceTimeBy(50L)
             showLockscreen()
 
-            overrideResource(R.bool.config_use_split_notification_shade, false)
+            shadeTestUtil.setSplitShade(false)
             configurationRepository.onAnyConfigurationChange()
 
             assertThat(maxNotifications).isEqualTo(10)
@@ -690,7 +694,7 @@
             // Show lockscreen with shade expanded
             showLockscreenWithShadeExpanded()
 
-            overrideResource(R.bool.config_use_split_notification_shade, false)
+            shadeTestUtil.setSplitShade(false)
             configurationRepository.onAnyConfigurationChange()
 
             // -1 means No Limit
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
index 2114489..6441405 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
@@ -88,7 +88,6 @@
     @Mock private PowerManager mPowerManager;
     @Mock private WakefulnessLifecycle mWakefullnessLifecycle;
     @Mock private CentralSurfaces mCentralSurfaces;
-    @Mock private NotificationIconAreaController mNotificationIconAreaController;
     @Mock private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
     @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     @Mock private ShadeLockscreenInteractor mShadeLockscreenInteractor;
@@ -106,7 +105,7 @@
                 mHeadsUpManager, mBatteryController, mScrimController,
                 () -> mBiometricUnlockController, () -> mAssistManager, mDozeScrimController,
                 mKeyguardUpdateMonitor, mPulseExpansionHandler, mNotificationShadeWindowController,
-                mNotificationWakeUpCoordinator, mAuthController, mNotificationIconAreaController,
+                mNotificationWakeUpCoordinator, mAuthController,
                 mShadeLockscreenInteractor, mDozeInteractor);
 
         mDozeServiceHost.initialize(
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 32f66c1..11504aa 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
@@ -172,7 +172,7 @@
     @Test
     fun shouldAskForZenDuration_changesWithSetting() =
         testScope.runTest {
-            val manualDnd = TestModeBuilder.MANUAL_DND
+            val manualDnd = TestModeBuilder.MANUAL_DND_ACTIVE
 
             settingsRepository.setInt(ZEN_DURATION, ZEN_DURATION_FOREVER)
             runCurrent()
@@ -201,7 +201,7 @@
     @Test
     fun activateMode_usesCorrectDuration() =
         testScope.runTest {
-            val manualDnd = TestModeBuilder.MANUAL_DND
+            val manualDnd = TestModeBuilder.MANUAL_DND_ACTIVE
             zenModeRepository.addModes(listOf(manualDnd))
             settingsRepository.setInt(ZEN_DURATION, ZEN_DURATION_FOREVER)
             runCurrent()
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 62161bf..bcad7e7 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
@@ -69,7 +69,7 @@
                         .setName("Disabled by other")
                         .setEnabled(false, /* byUser= */ false)
                         .build(),
-                    TestModeBuilder.MANUAL_DND,
+                    TestModeBuilder.MANUAL_DND_ACTIVE,
                     TestModeBuilder()
                         .setName("Enabled")
                         .setEnabled(true)
@@ -91,7 +91,7 @@
                 assertThat(this.enabled).isEqualTo(false)
             }
             with(tiles?.elementAt(1)!!) {
-                assertThat(this.text).isEqualTo("Manual DND")
+                assertThat(this.text).isEqualTo("Do Not Disturb")
                 assertThat(this.subtext).isEqualTo("On")
                 assertThat(this.enabled).isEqualTo(true)
             }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorImplTest.kt
index ab184ab..f232d52 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorImplTest.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.volume.panel.domain.unavailableCriteria
 import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
 import com.android.systemui.volume.panel.ui.composable.enabledComponents
+import com.android.systemui.volume.shared.volumePanelLogger
 import com.google.common.truth.Truth.assertThat
 import javax.inject.Provider
 import kotlinx.coroutines.test.runTest
@@ -49,6 +50,7 @@
                     enabledComponents,
                     { defaultCriteria },
                     testScope.backgroundScope,
+                    volumePanelLogger,
                     criteriaByKey,
                 )
             }
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 420b955..51a70bd 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
@@ -24,21 +24,30 @@
 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.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.policy.configurationController
 import com.android.systemui.statusbar.policy.fakeConfigurationController
 import com.android.systemui.testKosmos
+import com.android.systemui.volume.panel.dagger.factory.volumePanelComponentFactory
 import com.android.systemui.volume.panel.data.repository.volumePanelGlobalStateRepository
 import com.android.systemui.volume.panel.domain.interactor.criteriaByKey
+import com.android.systemui.volume.panel.domain.interactor.volumePanelGlobalStateInteractor
 import com.android.systemui.volume.panel.domain.unavailableCriteria
 import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
 import com.android.systemui.volume.panel.shared.model.mockVolumePanelUiComponentProvider
 import com.android.systemui.volume.panel.ui.composable.componentByKey
 import com.android.systemui.volume.panel.ui.layout.DefaultComponentsLayoutManager
 import com.android.systemui.volume.panel.ui.layout.componentsLayoutManager
+import com.android.systemui.volume.shared.volumePanelLogger
 import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.StringWriter
 import javax.inject.Provider
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
@@ -55,6 +64,7 @@
             volumePanelGlobalStateRepository.updateVolumePanelState { it.copy(isVisible = true) }
         }
 
+    private val realDumpManager = DumpManager()
     private val testableResources = context.orCreateTestableResources
 
     private lateinit var underTest: VolumePanelViewModel
@@ -124,6 +134,60 @@
     }
 
     @Test
+    fun testDumpableRegister_unregister() =
+        with(kosmos) {
+            testScope.runTest {
+                val job = launch {
+                    applicationCoroutineScope = this
+                    underTest = createViewModel()
+
+                    runCurrent()
+
+                    assertThat(realDumpManager.getDumpables().any { it.name == DUMPABLE_NAME })
+                        .isTrue()
+                }
+
+                runCurrent()
+                job.cancel()
+
+                assertThat(realDumpManager.getDumpables().any { it.name == DUMPABLE_NAME }).isTrue()
+            }
+        }
+
+    @Test
+    fun testDumpingState() =
+        test({
+            componentByKey =
+                mapOf(
+                    COMPONENT_1 to mockVolumePanelUiComponentProvider,
+                    COMPONENT_2 to mockVolumePanelUiComponentProvider,
+                    BOTTOM_BAR to mockVolumePanelUiComponentProvider,
+                )
+            criteriaByKey = mapOf(COMPONENT_2 to Provider { unavailableCriteria })
+        }) {
+            testScope.runTest {
+                runCurrent()
+
+                StringWriter().use {
+                    underTest.dump(PrintWriter(it), emptyArray())
+
+                    assertThat(it.buffer.toString())
+                        .isEqualTo(
+                            "volumePanelState=" +
+                                "VolumePanelState(orientation=1, isLargeScreen=false)\n" +
+                                "componentsLayout=( " +
+                                "headerComponents= " +
+                                "contentComponents=" +
+                                "test_component:1:visible=true, " +
+                                "test_component:2:visible=false " +
+                                "footerComponents= " +
+                                "bottomBarComponent=test_bottom_bar:visible=true )\n"
+                        )
+                }
+            }
+        }
+
+    @Test
     fun dismissBroadcast_dismissesPanel() = test {
         testScope.runTest {
             runCurrent() // run the flows to let allow the receiver to be registered
@@ -140,11 +204,26 @@
     private fun test(setup: Kosmos.() -> Unit = {}, test: Kosmos.() -> Unit) =
         with(kosmos) {
             setup()
-            underTest = volumePanelViewModel
+            underTest = createViewModel()
+
             test()
         }
 
+    private fun Kosmos.createViewModel(): VolumePanelViewModel =
+        VolumePanelViewModel(
+            context.orCreateTestableResources.resources,
+            applicationCoroutineScope,
+            volumePanelComponentFactory,
+            configurationController,
+            broadcastDispatcher,
+            realDumpManager,
+            volumePanelLogger,
+            volumePanelGlobalStateInteractor,
+        )
+
     private companion object {
+        const val DUMPABLE_NAME = "VolumePanelViewModel"
+
         const val BOTTOM_BAR: VolumePanelComponentKey = "test_bottom_bar"
         const val COMPONENT_1: VolumePanelComponentKey = "test_component:1"
         const val COMPONENT_2: VolumePanelComponentKey = "test_component:2"
diff --git a/packages/SystemUI/res/drawable/ic_bugreport.xml b/packages/SystemUI/res/drawable/ic_bugreport.xml
index ed1c6c7..badbd88 100644
--- a/packages/SystemUI/res/drawable/ic_bugreport.xml
+++ b/packages/SystemUI/res/drawable/ic_bugreport.xml
@@ -19,14 +19,14 @@
     android:height="24.0dp"
     android:viewportWidth="24.0"
     android:viewportHeight="24.0"
-    android:tint="?attr/colorControlNormal">
+    android:tint="?android:attr/colorControlNormal">
     <path
-        android:fillColor="#FF000000"
+        android:fillColor="#FFFFFFFF"
         android:pathData="M20,10V8h-2.81c-0.45,-0.78 -1.07,-1.46 -1.82,-1.96L17,4.41L15.59,3l-2.17,2.17c-0.03,-0.01 -0.05,-0.01 -0.08,-0.01c-0.16,-0.04 -0.32,-0.06 -0.49,-0.09c-0.06,-0.01 -0.11,-0.02 -0.17,-0.03C12.46,5.02 12.23,5 12,5h0c-0.49,0 -0.97,0.07 -1.42,0.18l0.02,-0.01L8.41,3L7,4.41l1.62,1.63l0.01,0C7.88,6.54 7.26,7.22 6.81,8H4v2h2.09C6.03,10.33 6,10.66 6,11v1H4v2h2v1c0,0.34 0.04,0.67 0.09,1H4v2h2.81c1.04,1.79 2.97,3 5.19,3h0c2.22,0 4.15,-1.21 5.19,-3H20v-2h-2.09l0,0c0.05,-0.33 0.09,-0.66 0.09,-1v-1h2v-2h-2v-1c0,-0.34 -0.04,-0.67 -0.09,-1l0,0H20zM16,15c0,2.21 -1.79,4 -4,4c-2.21,0 -4,-1.79 -4,-4v-4c0,-2.21 1.79,-4 4,-4h0c2.21,0 4,1.79 4,4V15z"/>
     <path
-        android:fillColor="#FF000000"
+        android:fillColor="#FFFFFFFF"
         android:pathData="M10,14h4v2h-4z"/>
     <path
-        android:fillColor="#FF000000"
+        android:fillColor="#FFFFFFFF"
         android:pathData="M10,10h4v2h-4z"/>
 </vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/biometric_prompt_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_layout.xml
deleted file mode 100644
index ff89ed9..0000000
--- a/packages/SystemUI/res/layout/biometric_prompt_layout.xml
+++ /dev/null
@@ -1,208 +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.
-  -->
-<com.android.systemui.biometrics.ui.BiometricPromptLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/contents"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:orientation="vertical">
-
-    <ImageView
-        android:id="@+id/logo"
-        android:layout_width="@dimen/biometric_auth_icon_size"
-        android:layout_height="@dimen/biometric_auth_icon_size"
-        android:layout_gravity="center"
-        android:scaleType="fitXY"/>
-
-    <TextView
-        android:id="@+id/logo_description"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:gravity="@integer/biometric_dialog_text_gravity"
-        android:singleLine="true"
-        android:marqueeRepeatLimit="1"
-        android:ellipsize="marquee"/>
-
-    <TextView
-        android:id="@+id/title"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:gravity="@integer/biometric_dialog_text_gravity"
-        android:singleLine="true"
-        android:marqueeRepeatLimit="1"
-        android:ellipsize="marquee"
-        style="@style/TextAppearance.AuthCredential.OldTitle"/>
-
-    <TextView
-        android:id="@+id/subtitle"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:gravity="@integer/biometric_dialog_text_gravity"
-        android:singleLine="true"
-        android:marqueeRepeatLimit="1"
-        android:ellipsize="marquee"
-        style="@style/TextAppearance.AuthCredential.OldSubtitle"/>
-
-    <TextView
-        android:id="@+id/description"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:gravity="@integer/biometric_dialog_text_gravity"
-        android:scrollbars ="vertical"
-        android:importantForAccessibility="no"
-        style="@style/TextAppearance.AuthCredential.OldDescription"/>
-
-    <Space
-        android:id="@+id/space_above_content"
-        android:layout_width="match_parent"
-        android:layout_height="24dp"
-        android:visibility="gone" />
-
-    <LinearLayout
-        android:id="@+id/customized_view_container"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:fadeScrollbars="false"
-        android:gravity="center_vertical"
-        android:orientation="vertical"
-        android:scrollbars="vertical"
-        android:visibility="gone" />
-
-    <Space android:id="@+id/space_above_icon"
-        android:layout_width="match_parent"
-        android:layout_height="48dp" />
-
-    <FrameLayout
-        android:id="@+id/biometric_icon_frame"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center">
-
-        <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper
-            android:id="@+id/biometric_icon"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center"
-            android:contentDescription="@null"
-            android:scaleType="fitXY" />
-
-        <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper
-            android:id="@+id/biometric_icon_overlay"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center"
-            android:contentDescription="@null"
-            android:scaleType="fitXY" />
-    </FrameLayout>
-
-    <!-- For sensors such as UDFPS, this view is used during custom measurement/layout to add extra
-         padding so that the biometric icon is always in the right physical position. -->
-    <Space android:id="@+id/space_below_icon"
-        android:layout_width="match_parent"
-        android:layout_height="12dp" />
-
-    <TextView
-        android:id="@+id/indicator"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:paddingHorizontal="24dp"
-        android:textSize="12sp"
-        android:gravity="center_horizontal"
-        android:accessibilityLiveRegion="polite"
-        android:singleLine="true"
-        android:ellipsize="marquee"
-        android:marqueeRepeatLimit="marquee_forever"
-        android:scrollHorizontally="true"
-        android:fadingEdge="horizontal"
-        android:textColor="@color/biometric_dialog_gray"/>
-
-    <LinearLayout
-        android:id="@+id/button_bar"
-        android:layout_width="match_parent"
-        android:layout_height="88dp"
-        style="?android:attr/buttonBarStyle"
-        android:orientation="horizontal"
-        android:paddingTop="24dp">
-
-        <Space android:id="@+id/leftSpacer"
-            android:layout_width="8dp"
-            android:layout_height="match_parent"
-            android:visibility="visible" />
-
-        <!-- Negative Button, reserved for app -->
-        <Button android:id="@+id/button_negative"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
-            android:layout_gravity="center_vertical"
-            android:ellipsize="end"
-            android:maxLines="2"
-            android:maxWidth="@dimen/biometric_dialog_button_negative_max_width"
-            android:visibility="gone"/>
-        <!-- Cancel Button, replaces negative button when biometric is accepted -->
-        <Button android:id="@+id/button_cancel"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
-            android:layout_gravity="center_vertical"
-            android:maxWidth="@dimen/biometric_dialog_button_negative_max_width"
-            android:text="@string/cancel"
-            android:visibility="gone"/>
-        <!-- "Use Credential" Button, replaces if device credential is allowed -->
-        <Button android:id="@+id/button_use_credential"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
-            android:layout_gravity="center_vertical"
-            android:maxWidth="@dimen/biometric_dialog_button_negative_max_width"
-            android:visibility="gone"/>
-
-        <Space android:id="@+id/middleSpacer"
-            android:layout_width="0dp"
-            android:layout_height="match_parent"
-            android:layout_weight="1"
-            android:visibility="visible"/>
-
-        <!-- Positive Button -->
-        <Button android:id="@+id/button_confirm"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            style="@*android:style/Widget.DeviceDefault.Button.Colored"
-            android:layout_gravity="center_vertical"
-            android:ellipsize="end"
-            android:maxLines="2"
-            android:maxWidth="@dimen/biometric_dialog_button_positive_max_width"
-            android:text="@string/biometric_dialog_confirm"
-            android:visibility="gone"/>
-        <!-- Try Again Button -->
-        <Button android:id="@+id/button_try_again"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            style="@*android:style/Widget.DeviceDefault.Button.Colored"
-            android:layout_gravity="center_vertical"
-            android:ellipsize="end"
-            android:maxLines="2"
-            android:maxWidth="@dimen/biometric_dialog_button_positive_max_width"
-            android:text="@string/biometric_dialog_try_again"
-            android:visibility="gone"/>
-
-        <Space android:id="@+id/rightSpacer"
-            android:layout_width="8dp"
-            android:layout_height="match_parent"
-            android:visibility="visible" />
-    </LinearLayout>
-
-</com.android.systemui.biometrics.ui.BiometricPromptLayout>
diff --git a/packages/SystemUI/res/layout/chipbar.xml b/packages/SystemUI/res/layout/chipbar.xml
index 8fa975b..e1b8ab4 100644
--- a/packages/SystemUI/res/layout/chipbar.xml
+++ b/packages/SystemUI/res/layout/chipbar.xml
@@ -49,7 +49,7 @@
             android:alpha="0.0"
             />
 
-        <!-- LINT.IfChange textColor -->
+        <!-- LINT.IfChange -->
         <TextView
             android:id="@+id/text"
             android:layout_width="0dp"
@@ -78,7 +78,7 @@
             android:layout_height="@dimen/chipbar_end_icon_size"
             android:layout_marginStart="@dimen/chipbar_end_item_start_margin"
             android:src="@drawable/ic_warning"
-            android:tint="@color/GM2_red_600"
+            android:tint="@color/GM2_red_800"
             android:alpha="0.0"
             />
 
diff --git a/packages/SystemUI/res/layout/custom_trace_settings_dialog.xml b/packages/SystemUI/res/layout/custom_trace_settings_dialog.xml
index 6180bf5..9e84052 100644
--- a/packages/SystemUI/res/layout/custom_trace_settings_dialog.xml
+++ b/packages/SystemUI/res/layout/custom_trace_settings_dialog.xml
@@ -52,7 +52,8 @@
             android:layout_weight="1"
             android:layout_gravity="fill_vertical"
             android:gravity="start"
-            android:textAppearance="@style/TextAppearance.Dialog.Body.Message" />
+            android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
+            android:importantForAccessibility="no" />
 
         <Switch
             android:id="@+id/attach_to_bugreport_switch"
@@ -80,7 +81,8 @@
             android:layout_weight="1"
             android:layout_gravity="fill_vertical"
             android:gravity="start"
-            android:textAppearance="@style/TextAppearance.Dialog.Body.Message"/>
+            android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
+            android:importantForAccessibility="no" />
 
         <Switch
             android:id="@+id/winscope_switch"
@@ -108,7 +110,8 @@
             android:layout_weight="1"
             android:layout_gravity="fill_vertical"
             android:gravity="start"
-            android:textAppearance="@style/TextAppearance.Dialog.Body.Message" />
+            android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
+            android:importantForAccessibility="no" />
 
         <Switch
             android:id="@+id/trace_debuggable_apps_switch"
@@ -136,7 +139,8 @@
             android:layout_weight="1"
             android:layout_gravity="fill_vertical"
             android:gravity="start"
-            android:textAppearance="@style/TextAppearance.Dialog.Body.Message" />
+            android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
+            android:importantForAccessibility="no" />
 
         <Switch
             android:id="@+id/long_traces_switch"
diff --git a/packages/SystemUI/res/layout/media_projection_app_selector.xml b/packages/SystemUI/res/layout/media_projection_app_selector.xml
index b77f78d..3b6a64f 100644
--- a/packages/SystemUI/res/layout/media_projection_app_selector.xml
+++ b/packages/SystemUI/res/layout/media_projection_app_selector.xml
@@ -37,6 +37,7 @@
         android:background="@*android:drawable/bottomsheet_background">
 
         <ImageView
+            android:id="@+id/media_projection_app_selector_icon"
             android:layout_width="@dimen/media_projection_app_selector_icon_size"
             android:layout_height="@dimen/media_projection_app_selector_icon_size"
             android:layout_marginTop="@*android:dimen/chooser_edge_margin_normal"
diff --git a/packages/SystemUI/res/raw/action_key_edu.json b/packages/SystemUI/res/raw/action_key_edu.json
new file mode 100644
index 0000000..014d837
--- /dev/null
+++ b/packages/SystemUI/res/raw/action_key_edu.json
@@ -0,0 +1 @@
+{"v":"5.12.1","fr":60,"ip":0,"op":181,"w":554,"h":564,"nm":"Trackpad-JSON_ActionKey-EDU","ddd":0,"assets":[{"id":"comp_0","nm":"BlankButton","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[40,39.79,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[80,79.581]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14.032},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980401039,0.768627464771,0.627451002598,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"shortcut symbols","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":80},"r":{"a":0,"k":0},"p":{"a":0,"k":[40,49.21,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[80,79.581]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14.032},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705890417,0.258823543787,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"shadow","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]},{"id":"comp_1","nm":"actionKey_themed","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".onSecondaryFixed","cl":"onSecondaryFixed","parent":3,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[0.288,-0.035,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0.579,-0.158],[0.605,0],[1.21,1.21],[0,1.684],[-1.184,1.184],[-1.684,0],[-1.21,-1.21],[0,-1.71],[0.184,-0.553],[0.316,-0.474],[0,0],[0,0]],"o":[[-0.474,0.316],[-0.553,0.158],[-1.684,0],[-1.184,-1.21],[0,-1.71],[1.21,-1.21],[1.71,0],[1.21,1.184],[0,0.605],[-0.158,0.553],[0,0],[0,0],[0,0]],"v":[[10.241,12.155],[8.663,12.866],[6.926,13.103],[2.585,11.287],[0.809,6.946],[2.585,2.605],[6.926,0.789],[11.307,2.605],[13.122,6.946],[12.846,8.682],[12.136,10.222],[16.911,14.997],[15.017,16.891]],"c":true}},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":0,"k":{"i":[[1.21,1.21],[0,1.684],[-1.21,1.184],[-1.684,0],[-1.184,-1.21],[0,-1.736],[1.21,-1.21],[1.736,0]],"o":[[-1.21,-1.21],[0,-1.736],[1.21,-1.21],[1.736,0],[1.21,1.184],[0,1.684],[-1.184,1.21],[-1.684,0]],"v":[[-15.096,11.327],[-16.911,6.985],[-15.096,2.605],[-10.754,0.789],[-6.374,2.605],[-4.558,6.985],[-6.374,11.327],[-10.754,13.142]],"c":true}},"nm":"Path 2","hd":false},{"ind":2,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.684,0.658],[0,0.947],[0.684,0.684],[0.973,0],[0.684,-0.684],[0,-0.973],[-0.658,-0.684],[-0.947,0]],"o":[[0.684,-0.684],[0,-0.973],[-0.684,-0.684],[-0.947,0],[-0.658,0.684],[0,0.947],[0.684,0.658],[0.973,0]],"v":[[-8.268,9.432],[-7.242,6.985],[-8.268,4.499],[-10.754,3.473],[-13.201,4.499],[-14.188,6.985],[-13.201,9.432],[-10.754,10.419]],"c":true}},"nm":"Path 3","hd":false},{"ind":3,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.684,0.658],[0,0.947],[0.684,0.684],[0.973,0],[0.684,-0.684],[0,-0.973],[-0.684,-0.684],[-0.947,0]],"o":[[0.684,-0.684],[0,-0.973],[-0.684,-0.684],[-0.947,0],[-0.684,0.684],[0,0.947],[0.684,0.658],[0.973,0]],"v":[[9.413,9.432],[10.439,6.985],[9.413,4.499],[6.926,3.473],[4.479,4.499],[3.453,6.985],[4.479,9.432],[6.926,10.419]],"c":true}},"nm":"Path 4","hd":false},{"ind":4,"ty":"sh","ks":{"a":0,"k":{"i":[[1.21,1.21],[0,1.684],[-1.21,1.21],[-1.684,0],[-1.184,-1.21],[0,-1.71],[1.21,-1.21],[1.736,0]],"o":[[-1.21,-1.21],[0,-1.71],[1.21,-1.21],[1.736,0],[1.21,1.21],[0,1.684],[-1.184,1.21],[-1.684,0]],"v":[[-15.096,-6.354],[-16.911,-10.695],[-15.096,-15.076],[-10.754,-16.891],[-6.374,-15.076],[-4.558,-10.695],[-6.374,-6.354],[-10.754,-4.539]],"c":true}},"nm":"Path 5","hd":false},{"ind":5,"ty":"sh","ks":{"a":0,"k":{"i":[[1.21,1.21],[0,1.684],[-1.21,1.21],[-1.684,0],[-1.21,-1.21],[0,-1.71],[1.21,-1.21],[1.71,0]],"o":[[-1.21,-1.21],[0,-1.71],[1.21,-1.21],[1.71,0],[1.21,1.21],[0,1.684],[-1.21,1.21],[-1.684,0]],"v":[[2.585,-6.354],[0.77,-10.695],[2.585,-15.076],[6.926,-16.891],[11.307,-15.076],[13.122,-10.695],[11.307,-6.354],[6.926,-4.539]],"c":true}},"nm":"Path 6","hd":false},{"ind":6,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.684,0.684],[0,0.947],[0.684,0.684],[0.973,0],[0.684,-0.684],[0,-0.973],[-0.658,-0.684],[-0.947,0]],"o":[[0.684,-0.684],[0,-0.973],[-0.684,-0.684],[-0.947,0],[-0.658,0.684],[0,0.947],[0.684,0.684],[0.973,0]],"v":[[-8.268,-8.248],[-7.242,-10.695],[-8.268,-13.182],[-10.754,-14.208],[-13.201,-13.182],[-14.188,-10.695],[-13.201,-8.248],[-10.754,-7.222]],"c":true}},"nm":"Path 7","hd":false},{"ind":7,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.684,0.684],[0,0.947],[0.684,0.684],[0.973,0],[0.684,-0.684],[0,-0.973],[-0.684,-0.684],[-0.947,0]],"o":[[0.684,-0.684],[0,-0.973],[-0.684,-0.684],[-0.947,0],[-0.684,0.684],[0,0.947],[0.684,0.684],[0.973,0]],"v":[[9.413,-8.248],[10.439,-10.695],[9.413,-13.182],[6.926,-14.208],[4.479,-13.182],[3.453,-10.695],[4.479,-8.248],[6.926,-7.222]],"c":true}},"nm":"Path 8","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098045468,0.101960785687,0.015686275437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"icon","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".primaryFixedDim","cl":"primaryFixedDim","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.64],"y":[1]},"o":{"x":[0.33],"y":[0.52]},"t":34,"s":[0]},{"i":{"x":[0.64],"y":[1]},"o":{"x":[0.36],"y":[0]},"t":37,"s":[100]},{"i":{"x":[0.64],"y":[0.48]},"o":{"x":[0.36],"y":[0]},"t":40,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":46,"s":[0]},{"i":{"x":[0.64],"y":[1]},"o":{"x":[0.33],"y":[0.52]},"t":124,"s":[0]},{"i":{"x":[0.64],"y":[1]},"o":{"x":[0.36],"y":[0]},"t":127,"s":[100]},{"i":{"x":[0.64],"y":[0.48]},"o":{"x":[0.36],"y":[0]},"t":130,"s":[100]},{"t":136,"s":[0]}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,0,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[80,79.581]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14.032},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.92549020052,0.752941191196,0.423529416323,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"shortcut symbols","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":1,"k":[{"i":{"x":0.072,"y":0.635},"o":{"x":0.424,"y":0.112},"t":27,"s":[40,39.79,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0,"y":1},"o":{"x":0.313,"y":0.131},"t":39,"s":[40,49.21,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":57,"s":[40,39.79,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.07,"y":0.63},"o":{"x":0.42,"y":0.11},"t":117,"s":[40,39.79,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0,"y":1},"o":{"x":0.313,"y":0.131},"t":129,"s":[40,49.21,0],"to":[0,0,0],"ti":[0,0,0]},{"t":147,"s":[40,39.79,0]}]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[80,79.581]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14.032},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"shortcut symbols","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":80},"r":{"a":0,"k":0},"p":{"a":0,"k":[40,49.21,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[80,79.581]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14.032},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705890417,0.258823543787,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"shadow","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]},{"id":"comp_2","nm":"AllApps_Tray_themed","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,-8.859,0]},"a":{"a":0,"k":[277,256.562,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Safety app","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[400.047,330.391]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 4 App - 6","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Settings","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[350.828,330.391]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 4 App - 5","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"SoundCloud","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[301.609,330.391]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 4 App - 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Starbucks","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252.391,330.391]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 4 App - 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Snapchat","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[203.172,330.391]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 4 App - 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Spotify","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[153.953,330.391]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 4 App - 1","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Safety app","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[400.047,281.172]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 3 App - 6","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Settings","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[350.828,281.172]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 3 App - 5","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"SoundCloud","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[301.609,281.172]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 3 App - 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Starbucks","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252.391,281.172]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 3 App - 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Snapchat","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[203.172,281.172]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 3 App - 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Spotify","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[153.953,281.172]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 3 App - 1","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Safety app","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[400.047,231.953]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 2 App - 6","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Settings","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[350.828,231.953]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 2 App - 5","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"SoundCloud","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[301.609,231.953]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 2 App - 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Starbucks","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252.391,231.953]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 2 App - 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Snapchat","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[203.172,231.953]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 2 App - 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Spotify","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[153.953,231.953]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 2 App - 1","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Safety app","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[400.047,182.734]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 1 App - 6","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Settings","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[350.828,182.734]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 1 App - 5","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"SoundCloud","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[301.609,182.734]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 1 App - 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Starbucks","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252.391,182.734]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 1 App - 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Snapchat","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[203.172,182.734]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 1 App - 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Spotify","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[153.953,182.734]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 1 App - 1","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,-129.938,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[275.625,25.594]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980401039,0.768627464771,0.627451002598,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"560x52","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,-151.594,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[15.75,1.969]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980401039,0.768627464771,0.627451002598,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"handle","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".onSecondaryFixed","cl":"onSecondaryFixed","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":1,"k":[{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":45,"s":[277,516,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.214,"y":0.214},"t":75,"s":[277,265.422,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.999,"y":1},"o":{"x":0.3,"y":0},"t":135,"s":[277,265.422,0],"to":[0,0,0],"ti":[0,0,0]},{"t":147,"s":[277,516,0]}]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[316.969,320.906]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":13.78},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098045468,0.101960785687,0.015686275437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"all apps","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]},{"id":"comp_3","nm":"AK_LofiLauncher","fr":60,"pfr":1,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Scale Down","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":51,"s":[50]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":135,"s":[50]},{"t":144,"s":[100]}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[252,157.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":1,"k":[{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.153,0.153,0.153],"y":[0.074,0.074,0]},"t":45,"s":[100,100,100]},{"i":{"x":[0.841,0.841,0.841],"y":[1,1,1]},"o":{"x":[0.161,0.161,0.161],"y":[0,0,0]},"t":75,"s":[95,95,100]},{"i":{"x":[0,0,0],"y":[1,1,1]},"o":{"x":[0.2,0.2,0.2],"y":[0,0,0]},"t":135,"s":[95,95,100]},{"t":165,"s":[100,100,100]}]}},"ao":0,"ef":[{"ty":5,"nm":"Void","np":19,"mn":"Pseudo/250958","ix":1,"en":1,"ef":[{"ty":0,"nm":"Width","mn":"Pseudo/250958-0001","ix":1,"v":{"a":0,"k":100}},{"ty":0,"nm":"Height","mn":"Pseudo/250958-0002","ix":2,"v":{"a":0,"k":100}},{"ty":0,"nm":"Offset X","mn":"Pseudo/250958-0003","ix":3,"v":{"a":0,"k":0}},{"ty":0,"nm":"Offset Y","mn":"Pseudo/250958-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Roundness","mn":"Pseudo/250958-0005","ix":5,"v":{"a":0,"k":0}},{"ty":6,"nm":"About","mn":"Pseudo/250958-0006","ix":6,"v":0},{"ty":6,"nm":"Plague of null layers.","mn":"Pseudo/250958-0007","ix":7,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0008","ix":8,"v":0},{"ty":6,"nm":"Following projects","mn":"Pseudo/250958-0009","ix":9,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0010","ix":10,"v":0},{"ty":6,"nm":"through time.","mn":"Pseudo/250958-0011","ix":11,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0012","ix":12,"v":0},{"ty":6,"nm":"Be free of the past.","mn":"Pseudo/250958-0013","ix":13,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0014","ix":14,"v":0},{"ty":6,"nm":"Copyright 2023 Battle Axe Inc","mn":"Pseudo/250958-0015","ix":15,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0016","ix":16,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0017","ix":17,"v":0}]}],"shapes":[],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":1,"sr":1,"ks":{"o":{"k":[{"s":[100],"t":45,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[70],"t":51,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[70],"t":135,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":144,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,117.5,0]},"a":{"a":0,"k":[252,275,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[444,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 5","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[396,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[348,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[300,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 1","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[168,20]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":15},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"qsb","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[132,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"qsb","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":1,"sr":1,"ks":{"o":{"k":[{"s":[100],"t":45,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[70],"t":51,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[70],"t":135,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":144,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,-29.497,0]},"a":{"a":0,"k":[252,128.003,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[20.144,20.144],[20.144,-20.144],[0,0],[-20.144,-20.144],[-20.144,20.144],[0,0]],"o":[[-20.144,-20.144],[0,0],[-20.144,20.144],[20.144,20.144],[0,0],[20.144,-20.144]],"v":[[44.892,-44.892],[-28.057,-44.892],[-44.892,-28.057],[-44.892,44.892],[28.057,44.892],[44.892,28.057]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[108,152.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets weather","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[4.782,-2.684],[0,0],[2.63,-0.033],[0,0],[2.807,-4.716],[0,0],[2.263,-1.343],[0,0],[0.066,-5.485],[0,0],[1.292,-2.295],[0,0],[-2.683,-4.784],[0,0],[-0.033,-2.63],[0,0],[-4.716,-2.807],[0,0],[-1.338,-2.263],[0,0],[-5.483,-0.066],[0,0],[-2.296,-1.292],[0,0],[-4.782,2.683],[0,0],[-2.63,0.033],[0,0],[-2.807,4.716],[0,0],[-2.263,1.338],[0,0],[-0.066,5.483],[0,0],[-1.292,2.295],[0,0],[2.683,4.784],[0,0],[0.033,2.631],[0,0],[4.716,2.801],[0,0],[1.338,2.262],[0,0],[5.483,0.068],[0,0],[2.296,1.287]],"o":[[-4.782,-2.684],[0,0],[-2.296,1.287],[0,0],[-5.483,0.068],[0,0],[-1.338,2.262],[0,0],[-4.716,2.801],[0,0],[-0.033,2.631],[0,0],[-2.683,4.784],[0,0],[1.292,2.295],[0,0],[0.066,5.483],[0,0],[2.263,1.338],[0,0],[2.807,4.716],[0,0],[2.63,0.033],[0,0],[4.782,2.683],[0,0],[2.296,-1.292],[0,0],[5.483,-0.066],[0,0],[1.338,-2.263],[0,0],[4.716,-2.807],[0,0],[0.033,-2.63],[0,0],[2.683,-4.784],[0,0],[-1.292,-2.295],[0,0],[-0.066,-5.485],[0,0],[-2.263,-1.343],[0,0],[-2.807,-4.716],[0,0],[-2.63,-0.033],[0,0]],"v":[[7.7,-57.989],[-7.7,-57.989],[-11.019,-56.128],[-18.523,-54.117],[-22.327,-54.07],[-35.668,-46.369],[-37.609,-43.1],[-43.099,-37.605],[-46.372,-35.663],[-54.072,-22.324],[-54.118,-18.522],[-56.132,-11.016],[-57.988,-7.7],[-57.988,7.703],[-56.132,11.019],[-54.118,18.524],[-54.072,22.328],[-46.372,35.669],[-43.099,37.611],[-37.609,43.101],[-35.668,46.373],[-22.327,54.074],[-18.523,54.12],[-11.019,56.133],[-7.7,57.99],[7.7,57.99],[11.019,56.133],[18.523,54.12],[22.327,54.074],[35.668,46.373],[37.609,43.101],[43.099,37.611],[46.372,35.669],[54.072,22.328],[54.118,18.524],[56.132,11.019],[57.988,7.703],[57.988,-7.7],[56.132,-11.016],[54.118,-18.522],[54.072,-22.324],[46.372,-35.663],[43.099,-37.605],[37.609,-43.1],[35.668,-46.369],[22.327,-54.07],[18.523,-54.117],[11.019,-56.128]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[396,104.003]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets clock","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":1,"sr":1,"ks":{"o":{"k":[{"s":[100],"t":45,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[70],"t":51,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[70],"t":135,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":144,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,-29.497,0]},"a":{"a":0,"k":[252,128.003,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[444,200.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 7","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[348,200.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 6","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252,200.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 5","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252,128.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252,56.002]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[156,56.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[60,56.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 1","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"BlankButton","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[133,455.5,0]},"a":{"a":0,"k":[40,44.5,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":80,"h":89,"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"BlankButton","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[229,455.5,0]},"a":{"a":0,"k":[40,44.5,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":80,"h":89,"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":0,"nm":"BlankButton","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[421,455.5,0]},"a":{"a":0,"k":[40,44.5,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":80,"h":89,"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":0,"nm":"actionKey_themed","refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[325,455.5,0]},"a":{"a":0,"k":[40,44.5,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":80,"h":89,"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"matte","td":1,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"illustrations: action key","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":0,"nm":"AllApps_Tray_themed","tt":1,"tp":5,"refId":"comp_2","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,282,0]},"a":{"a":0,"k":[277,282,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":554,"h":564,"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":0,"nm":"AK_LofiLauncher","refId":"comp_3","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[252,157.5,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":504,"h":315,"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":50},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"illustrations: action key","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980401039,0.768627464771,0.627451002598,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"illustrations: action key","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":14},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"op","nm":"Stroke align: Outside","a":{"k":[{"s":[7],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[7],"t":180,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"lj":1,"ml":{"a":0,"k":4},"hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"frame","bm":0,"hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0}],"markers":[],"props":{}}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/action_key_success.json b/packages/SystemUI/res/raw/action_key_success.json
new file mode 100644
index 0000000..cae7344
--- /dev/null
+++ b/packages/SystemUI/res/raw/action_key_success.json
@@ -0,0 +1 @@
+{"v":"5.12.1","fr":60,"ip":0,"op":50,"w":554,"h":564,"nm":"Trackpad-JSON_ActionKey-Success","ddd":0,"assets":[{"id":"comp_0","nm":"TrackpadAK_Success_Checkmark","fr":60,"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Check Rotate","parent":2,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":1,"k":[{"i":{"x":[0.12],"y":[1]},"o":{"x":[0.44],"y":[0]},"t":2,"s":[-16]},{"t":20,"s":[6]}]},"p":{"a":0,"k":[0,0,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[95.049,95.049,100]}},"ao":0,"ip":0,"op":228,"st":-72,"bm":0},{"ddd":0,"ind":2,"ty":3,"nm":"Bounce","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":1,"k":[{"i":{"x":[0.12],"y":[1]},"o":{"x":[0.44],"y":[0]},"t":12,"s":[0]},{"t":36,"s":[-6]}]},"p":{"a":0,"k":[81,127,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":1,"k":[{"i":{"x":[0.263,0.263,0.833],"y":[1.126,1.126,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.958,0.958,0]},"t":1,"s":[80,80,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.45,0.45,0.167],"y":[0.325,0.325,0]},"t":20,"s":[105,105,100]},{"t":36,"s":[100,100,100]}]}},"ao":0,"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":-0.289},"p":{"a":0,"k":[14.364,-33.591,0]},"a":{"a":0,"k":[-0.125,0,0]},"s":{"a":0,"k":[104.744,104.744,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-1.401,-0.007],[-10.033,11.235]],"o":[[5.954,7.288],[1.401,0.007],[0,0]],"v":[[-28.591,4.149],[-10.73,26.013],[31.482,-21.255]],"c":false}},"nm":"Path 1","hd":false},{"ty":"tm","s":{"a":0,"k":0},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":3,"s":[0]},{"i":{"x":[0.22],"y":[1]},"o":{"x":[0.001],"y":[0.149]},"t":10,"s":[29]},{"t":27,"s":[100]}]},"o":{"a":0,"k":0},"m":1,"nm":"Trim Paths 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":11},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":5,"op":44,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[95,95,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":1,"k":[{"i":{"x":[0.275,0.275,0.21],"y":[1.102,1.102,1]},"o":{"x":[0.037,0.037,0.05],"y":[0.476,0.476,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.252,0.252,0.47],"y":[0.159,0.159,0]},"t":16,"s":[120,120,100]},{"t":28,"s":[100,100,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.32,0.32],"y":[0.11,0.11]},"t":16,"s":[148,148]},{"t":28,"s":[136,136]}]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":88},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Checkbox - Widget","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]},{"id":"comp_1","nm":"actionKey_themed-static","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".onSecondaryFixed","cl":"onSecondaryFixed","parent":3,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[0.288,-0.035,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0.579,-0.158],[0.605,0],[1.21,1.21],[0,1.684],[-1.184,1.184],[-1.684,0],[-1.21,-1.21],[0,-1.71],[0.184,-0.553],[0.316,-0.474],[0,0],[0,0]],"o":[[-0.474,0.316],[-0.553,0.158],[-1.684,0],[-1.184,-1.21],[0,-1.71],[1.21,-1.21],[1.71,0],[1.21,1.184],[0,0.605],[-0.158,0.553],[0,0],[0,0],[0,0]],"v":[[10.241,12.155],[8.663,12.866],[6.926,13.103],[2.585,11.287],[0.809,6.946],[2.585,2.605],[6.926,0.789],[11.307,2.605],[13.122,6.946],[12.846,8.682],[12.136,10.222],[16.911,14.997],[15.017,16.891]],"c":true}},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":0,"k":{"i":[[1.21,1.21],[0,1.684],[-1.21,1.184],[-1.684,0],[-1.184,-1.21],[0,-1.736],[1.21,-1.21],[1.736,0]],"o":[[-1.21,-1.21],[0,-1.736],[1.21,-1.21],[1.736,0],[1.21,1.184],[0,1.684],[-1.184,1.21],[-1.684,0]],"v":[[-15.096,11.327],[-16.911,6.985],[-15.096,2.605],[-10.754,0.789],[-6.374,2.605],[-4.558,6.985],[-6.374,11.327],[-10.754,13.142]],"c":true}},"nm":"Path 2","hd":false},{"ind":2,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.684,0.658],[0,0.947],[0.684,0.684],[0.973,0],[0.684,-0.684],[0,-0.973],[-0.658,-0.684],[-0.947,0]],"o":[[0.684,-0.684],[0,-0.973],[-0.684,-0.684],[-0.947,0],[-0.658,0.684],[0,0.947],[0.684,0.658],[0.973,0]],"v":[[-8.268,9.432],[-7.242,6.985],[-8.268,4.499],[-10.754,3.473],[-13.201,4.499],[-14.188,6.985],[-13.201,9.432],[-10.754,10.419]],"c":true}},"nm":"Path 3","hd":false},{"ind":3,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.684,0.658],[0,0.947],[0.684,0.684],[0.973,0],[0.684,-0.684],[0,-0.973],[-0.684,-0.684],[-0.947,0]],"o":[[0.684,-0.684],[0,-0.973],[-0.684,-0.684],[-0.947,0],[-0.684,0.684],[0,0.947],[0.684,0.658],[0.973,0]],"v":[[9.413,9.432],[10.439,6.985],[9.413,4.499],[6.926,3.473],[4.479,4.499],[3.453,6.985],[4.479,9.432],[6.926,10.419]],"c":true}},"nm":"Path 4","hd":false},{"ind":4,"ty":"sh","ks":{"a":0,"k":{"i":[[1.21,1.21],[0,1.684],[-1.21,1.21],[-1.684,0],[-1.184,-1.21],[0,-1.71],[1.21,-1.21],[1.736,0]],"o":[[-1.21,-1.21],[0,-1.71],[1.21,-1.21],[1.736,0],[1.21,1.21],[0,1.684],[-1.184,1.21],[-1.684,0]],"v":[[-15.096,-6.354],[-16.911,-10.695],[-15.096,-15.076],[-10.754,-16.891],[-6.374,-15.076],[-4.558,-10.695],[-6.374,-6.354],[-10.754,-4.539]],"c":true}},"nm":"Path 5","hd":false},{"ind":5,"ty":"sh","ks":{"a":0,"k":{"i":[[1.21,1.21],[0,1.684],[-1.21,1.21],[-1.684,0],[-1.21,-1.21],[0,-1.71],[1.21,-1.21],[1.71,0]],"o":[[-1.21,-1.21],[0,-1.71],[1.21,-1.21],[1.71,0],[1.21,1.21],[0,1.684],[-1.21,1.21],[-1.684,0]],"v":[[2.585,-6.354],[0.77,-10.695],[2.585,-15.076],[6.926,-16.891],[11.307,-15.076],[13.122,-10.695],[11.307,-6.354],[6.926,-4.539]],"c":true}},"nm":"Path 6","hd":false},{"ind":6,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.684,0.684],[0,0.947],[0.684,0.684],[0.973,0],[0.684,-0.684],[0,-0.973],[-0.658,-0.684],[-0.947,0]],"o":[[0.684,-0.684],[0,-0.973],[-0.684,-0.684],[-0.947,0],[-0.658,0.684],[0,0.947],[0.684,0.684],[0.973,0]],"v":[[-8.268,-8.248],[-7.242,-10.695],[-8.268,-13.182],[-10.754,-14.208],[-13.201,-13.182],[-14.188,-10.695],[-13.201,-8.248],[-10.754,-7.222]],"c":true}},"nm":"Path 7","hd":false},{"ind":7,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.684,0.684],[0,0.947],[0.684,0.684],[0.973,0],[0.684,-0.684],[0,-0.973],[-0.684,-0.684],[-0.947,0]],"o":[[0.684,-0.684],[0,-0.973],[-0.684,-0.684],[-0.947,0],[-0.684,0.684],[0,0.947],[0.684,0.684],[0.973,0]],"v":[[9.413,-8.248],[10.439,-10.695],[9.413,-13.182],[6.926,-14.208],[4.479,-13.182],[3.453,-10.695],[4.479,-8.248],[6.926,-7.222]],"c":true}},"nm":"Path 8","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098045468,0.101960785687,0.015686275437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"icon","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".primaryFixedDim","cl":"primaryFixedDim","parent":3,"sr":1,"ks":{"o":{"a":0,"k":0},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,0,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[80,79.581]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14.032},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.92549020052,0.752941191196,0.423529416323,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"shortcut symbols","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[40,39.79,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[80,79.581]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14.032},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"shortcut symbols","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":80},"r":{"a":0,"k":0},"p":{"a":0,"k":[40,49.21,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[80,79.581]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14.032},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705890417,0.258823543787,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"shadow","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]},{"id":"comp_2","nm":"BlankButton","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[40,39.79,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[80,79.581]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14.032},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980401039,0.768627464771,0.627451002598,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"shortcut symbols","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":80},"r":{"a":0,"k":0},"p":{"a":0,"k":[40,49.21,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[80,79.581]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14.032},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705890417,0.258823543787,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"shadow","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]},{"id":"comp_3","nm":"AK_LofiLauncher","fr":60,"pfr":1,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Scale Down","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":51,"s":[70]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":135,"s":[70]},{"t":144,"s":[100]}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[252,157.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":1,"k":[{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.153,0.153,0.153],"y":[0.074,0.074,0]},"t":45,"s":[100,100,100]},{"i":{"x":[0.841,0.841,0.841],"y":[1,1,1]},"o":{"x":[0.161,0.161,0.161],"y":[0,0,0]},"t":75,"s":[95,95,100]},{"i":{"x":[0,0,0],"y":[1,1,1]},"o":{"x":[0.2,0.2,0.2],"y":[0,0,0]},"t":135,"s":[95,95,100]},{"t":165,"s":[100,100,100]}]}},"ao":0,"ef":[{"ty":5,"nm":"Void","np":19,"mn":"Pseudo/250958","ix":1,"en":1,"ef":[{"ty":0,"nm":"Width","mn":"Pseudo/250958-0001","ix":1,"v":{"a":0,"k":100}},{"ty":0,"nm":"Height","mn":"Pseudo/250958-0002","ix":2,"v":{"a":0,"k":100}},{"ty":0,"nm":"Offset X","mn":"Pseudo/250958-0003","ix":3,"v":{"a":0,"k":0}},{"ty":0,"nm":"Offset Y","mn":"Pseudo/250958-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Roundness","mn":"Pseudo/250958-0005","ix":5,"v":{"a":0,"k":0}},{"ty":6,"nm":"About","mn":"Pseudo/250958-0006","ix":6,"v":0},{"ty":6,"nm":"Plague of null layers.","mn":"Pseudo/250958-0007","ix":7,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0008","ix":8,"v":0},{"ty":6,"nm":"Following projects","mn":"Pseudo/250958-0009","ix":9,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0010","ix":10,"v":0},{"ty":6,"nm":"through time.","mn":"Pseudo/250958-0011","ix":11,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0012","ix":12,"v":0},{"ty":6,"nm":"Be free of the past.","mn":"Pseudo/250958-0013","ix":13,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0014","ix":14,"v":0},{"ty":6,"nm":"Copyright 2023 Battle Axe Inc","mn":"Pseudo/250958-0015","ix":15,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0016","ix":16,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0017","ix":17,"v":0}]}],"shapes":[],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":1,"sr":1,"ks":{"o":{"k":[{"s":[100],"t":45,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[80],"t":49,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,117.5,0]},"a":{"a":0,"k":[252,275,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[444,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 5","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[396,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[348,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[300,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 1","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[168,20]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":15},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"qsb","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[132,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"qsb","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":1,"sr":1,"ks":{"o":{"k":[{"s":[100],"t":45,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[80],"t":49,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,-29.497,0]},"a":{"a":0,"k":[252,128.003,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[20.144,20.144],[20.144,-20.144],[0,0],[-20.144,-20.144],[-20.144,20.144],[0,0]],"o":[[-20.144,-20.144],[0,0],[-20.144,20.144],[20.144,20.144],[0,0],[20.144,-20.144]],"v":[[44.892,-44.892],[-28.057,-44.892],[-44.892,-28.057],[-44.892,44.892],[28.057,44.892],[44.892,28.057]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[108,152.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets weather","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[4.782,-2.684],[0,0],[2.63,-0.033],[0,0],[2.807,-4.716],[0,0],[2.263,-1.343],[0,0],[0.066,-5.485],[0,0],[1.292,-2.295],[0,0],[-2.683,-4.784],[0,0],[-0.033,-2.63],[0,0],[-4.716,-2.807],[0,0],[-1.338,-2.263],[0,0],[-5.483,-0.066],[0,0],[-2.296,-1.292],[0,0],[-4.782,2.683],[0,0],[-2.63,0.033],[0,0],[-2.807,4.716],[0,0],[-2.263,1.338],[0,0],[-0.066,5.483],[0,0],[-1.292,2.295],[0,0],[2.683,4.784],[0,0],[0.033,2.631],[0,0],[4.716,2.801],[0,0],[1.338,2.262],[0,0],[5.483,0.068],[0,0],[2.296,1.287]],"o":[[-4.782,-2.684],[0,0],[-2.296,1.287],[0,0],[-5.483,0.068],[0,0],[-1.338,2.262],[0,0],[-4.716,2.801],[0,0],[-0.033,2.631],[0,0],[-2.683,4.784],[0,0],[1.292,2.295],[0,0],[0.066,5.483],[0,0],[2.263,1.338],[0,0],[2.807,4.716],[0,0],[2.63,0.033],[0,0],[4.782,2.683],[0,0],[2.296,-1.292],[0,0],[5.483,-0.066],[0,0],[1.338,-2.263],[0,0],[4.716,-2.807],[0,0],[0.033,-2.63],[0,0],[2.683,-4.784],[0,0],[-1.292,-2.295],[0,0],[-0.066,-5.485],[0,0],[-2.263,-1.343],[0,0],[-2.807,-4.716],[0,0],[-2.63,-0.033],[0,0]],"v":[[7.7,-57.989],[-7.7,-57.989],[-11.019,-56.128],[-18.523,-54.117],[-22.327,-54.07],[-35.668,-46.369],[-37.609,-43.1],[-43.099,-37.605],[-46.372,-35.663],[-54.072,-22.324],[-54.118,-18.522],[-56.132,-11.016],[-57.988,-7.7],[-57.988,7.703],[-56.132,11.019],[-54.118,18.524],[-54.072,22.328],[-46.372,35.669],[-43.099,37.611],[-37.609,43.101],[-35.668,46.373],[-22.327,54.074],[-18.523,54.12],[-11.019,56.133],[-7.7,57.99],[7.7,57.99],[11.019,56.133],[18.523,54.12],[22.327,54.074],[35.668,46.373],[37.609,43.101],[43.099,37.611],[46.372,35.669],[54.072,22.328],[54.118,18.524],[56.132,11.019],[57.988,7.703],[57.988,-7.7],[56.132,-11.016],[54.118,-18.522],[54.072,-22.324],[46.372,-35.663],[43.099,-37.605],[37.609,-43.1],[35.668,-46.369],[22.327,-54.07],[18.523,-54.117],[11.019,-56.128]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[396,104.003]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets clock","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":1,"sr":1,"ks":{"o":{"k":[{"s":[100],"t":45,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[80],"t":49,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,-29.497,0]},"a":{"a":0,"k":[252,128.003,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[444,200.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 7","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[348,200.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 6","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252,200.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 5","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252,128.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252,56.002]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[156,56.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[60,56.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 1","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"TrackpadAK_Success_Checkmark","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,198.5,0]},"a":{"a":0,"k":[95,95,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":190,"h":190,"ip":6,"op":50,"st":6,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onSecondaryFixed","cl":"onSecondaryFixed","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":1,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":7,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":389,"s":[100]},{"t":392,"s":[0]}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":5,"nm":"Global Position","np":4,"mn":"Pseudo/88900","ix":1,"en":1,"ef":[{"ty":10,"nm":"Master Parent","mn":"Pseudo/88900-0001","ix":1,"v":{"a":0,"k":2}},{"ty":3,"nm":"Global Position","mn":"Pseudo/88900-0002","ix":2,"v":{"k":[{"s":[277,197.5],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,197.5],"t":49,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]}}]}],"shapes":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098039216,0.101960784314,0.01568627451,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false}],"ip":0,"op":50,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":0,"nm":"actionKey_themed-static","refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[325,455.5,0]},"a":{"a":0,"k":[40,44.5,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":80,"h":89,"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":0,"nm":"BlankButton","refId":"comp_2","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[421,455.5,0]},"a":{"a":0,"k":[40,44.5,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":80,"h":89,"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":0,"nm":"BlankButton","refId":"comp_2","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[229,455.5,0]},"a":{"a":0,"k":[40,44.5,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":80,"h":89,"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":0,"nm":"BlankButton","refId":"comp_2","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[133,455.5,0]},"a":{"a":0,"k":[40,44.5,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":80,"h":89,"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":0,"nm":"AK_LofiLauncher","refId":"comp_3","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[252,157.5,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":504,"h":315,"ip":0,"op":50,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":5,"nm":"Global Position","np":4,"mn":"Pseudo/88900","ix":1,"en":1,"ef":[{"ty":10,"nm":"Master Parent","mn":"Pseudo/88900-0001","ix":1,"v":{"a":0,"k":2}},{"ty":3,"nm":"Global Position","mn":"Pseudo/88900-0002","ix":2,"v":{"k":[{"s":[277,197.5],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,197.5],"t":49,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]}}]}],"shapes":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":50},"r":1,"bm":0,"nm":"Fill 1","hd":false}],"ip":0,"op":50,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980401039,0.768627464771,0.627451002598,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"illustrations: action key","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":14},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"op","nm":"Stroke align: Outside","a":{"k":[{"s":[7],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[7],"t":49,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"lj":1,"ml":{"a":0,"k":4},"hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"frame","bm":0,"hd":false}],"ip":0,"op":50,"st":0,"ct":1,"bm":0}],"markers":[],"props":{}}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/trackpad_home_edu.json b/packages/SystemUI/res/raw/trackpad_home_edu.json
new file mode 100644
index 0000000..27db9fd
--- /dev/null
+++ b/packages/SystemUI/res/raw/trackpad_home_edu.json
@@ -0,0 +1 @@
+{"v":"5.12.1","fr":60,"ip":0,"op":426,"w":554,"h":564,"nm":"Trackpad-JSON_HomeGesture-EDU","ddd":0,"assets":[{"id":"comp_0","nm":"Home_Dismiss","fr":60,"pfr":1,"layers":[{"ddd":0,"ind":2,"ty":3,"nm":"gesture:scale","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"k":[{"s":[277,197.321,0],"t":148,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,197.13,0],"t":151,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,197.036,0],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.921,0],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.779,0],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.606,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.39,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.122,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,195.786,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,195.354,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,194.78,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,193.975,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,192.883,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,191.652,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,190.304,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,188.897,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,187.507,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,186.208,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,185.036,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,183.998,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,183.082,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,182.274,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,181.557,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,180.918,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,180.344,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,179.824,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,179.353,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,178.924,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,178.532,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,178.174,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,177.843,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,177.538,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,177.256,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,176.995,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,176.752,0],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,176.527,0],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,176.319,0],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,176.124,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,175.943,0],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,175.776,0],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,175.619,0],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,175.474,0],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,175.339,0],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,175.213,0],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,175.095,0],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,174.985,0],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,174.884,0],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,174.789,0],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,174.62,0],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,174.476,0],"t":201,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,174.353,0],"t":203,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,174.209,0],"t":206,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,174.039,0],"t":212,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,174,0],"t":380,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,174.212,0],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,174.896,0],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,176.197,0],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,178.536,0],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,183.4,0],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,188.939,0],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,191.375,0],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,192.791,0],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,193.751,0],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,194.459,0],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,195.006,0],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,195.442,0],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,195.798,0],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.092,0],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.339,0],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.546,0],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.721,0],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.87,0],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.995,0],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,197.191,0],"t":401,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,197.378,0],"t":404,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"a":0,"k":[0,0,0]},"s":{"k":[{"s":[99.914,99.914,100],"t":146,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.848,99.848,100],"t":148,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.751,99.751,100],"t":150,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.685,99.685,100],"t":151,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.605,99.605,100],"t":152,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.507,99.507,100],"t":153,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.387,99.387,100],"t":154,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.239,99.239,100],"t":155,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.056,99.056,100],"t":156,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.829,98.829,100],"t":157,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.542,98.542,100],"t":158,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.174,98.174,100],"t":159,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.686,97.686,100],"t":160,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97,97,100],"t":161,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.071,96.071,100],"t":162,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.025,95.025,100],"t":163,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[93.878,93.878,100],"t":164,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.678,92.678,100],"t":165,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[91.495,91.495,100],"t":166,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.39,90.39,100],"t":167,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[89.393,89.393,100],"t":168,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88.508,88.508,100],"t":169,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[87.729,87.729,100],"t":170,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[87.041,87.041,100],"t":171,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[86.43,86.43,100],"t":172,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.886,85.886,100],"t":173,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.397,85.397,100],"t":174,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[84.956,84.956,100],"t":175,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[84.555,84.555,100],"t":176,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[84.191,84.191,100],"t":177,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.857,83.857,100],"t":178,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.552,83.552,100],"t":179,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.271,83.271,100],"t":180,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.011,83.011,100],"t":181,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.771,82.771,100],"t":182,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.549,82.549,100],"t":183,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.342,82.342,100],"t":184,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.151,82.151,100],"t":185,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.973,81.973,100],"t":186,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.807,81.807,100],"t":187,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.653,81.653,100],"t":188,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.51,81.51,100],"t":189,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.376,81.376,100],"t":190,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.251,81.251,100],"t":191,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.135,81.135,100],"t":192,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.027,81.027,100],"t":193,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.926,80.926,100],"t":194,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.833,80.833,100],"t":195,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.746,80.746,100],"t":196,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.665,80.665,100],"t":197,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.591,80.591,100],"t":198,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.522,80.522,100],"t":199,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.458,80.458,100],"t":200,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.4,80.4,100],"t":201,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.346,80.346,100],"t":202,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.298,80.298,100],"t":203,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.253,80.253,100],"t":204,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.176,80.176,100],"t":206,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.115,80.115,100],"t":208,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.049,80.049,100],"t":211,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80,80,100],"t":380,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.179,80.179,100],"t":381,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.757,80.757,100],"t":382,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.87,81.87,100],"t":383,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.86,83.86,100],"t":384,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88,88,100],"t":385,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.714,92.714,100],"t":386,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[94.789,94.789,100],"t":387,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.992,95.992,100],"t":388,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.809,96.809,100],"t":389,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.412,97.412,100],"t":390,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.878,97.878,100],"t":391,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.249,98.249,100],"t":392,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.553,98.553,100],"t":393,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.803,98.803,100],"t":394,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.012,99.012,100],"t":395,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.188,99.188,100],"t":396,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.337,99.337,100],"t":397,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.464,99.464,100],"t":398,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.57,99.57,100],"t":399,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.661,99.661,100],"t":400,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.737,99.737,100],"t":401,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.8,99.8,100],"t":402,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.896,99.896,100],"t":404,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.99,99.99,100],"t":408,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}]}},"ao":0,"ef":[{"ty":5,"nm":"Void","np":19,"mn":"Pseudo/250958","ix":1,"en":1,"ef":[{"ty":0,"nm":"Width","mn":"Pseudo/250958-0001","ix":1,"v":{"a":0,"k":100}},{"ty":0,"nm":"Height","mn":"Pseudo/250958-0002","ix":2,"v":{"a":0,"k":100}},{"ty":0,"nm":"Offset X","mn":"Pseudo/250958-0003","ix":3,"v":{"a":0,"k":0}},{"ty":0,"nm":"Offset Y","mn":"Pseudo/250958-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Roundness","mn":"Pseudo/250958-0005","ix":5,"v":{"a":0,"k":0}},{"ty":6,"nm":"About","mn":"Pseudo/250958-0006","ix":6,"v":0},{"ty":6,"nm":"Plague of null layers.","mn":"Pseudo/250958-0007","ix":7,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0008","ix":8,"v":0},{"ty":6,"nm":"Following projects","mn":"Pseudo/250958-0009","ix":9,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0010","ix":10,"v":0},{"ty":6,"nm":"through time.","mn":"Pseudo/250958-0011","ix":11,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0012","ix":12,"v":0},{"ty":6,"nm":"Be free of the past.","mn":"Pseudo/250958-0013","ix":13,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0014","ix":14,"v":0},{"ty":6,"nm":"Copyright 2023 Battle Axe Inc","mn":"Pseudo/250958-0015","ix":15,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0016","ix":16,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0017","ix":17,"v":0}]}],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".primaryFixedDim","cl":"primaryFixedDim","parent":4,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":37,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":47,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":197,"s":[100]},{"t":203,"s":[0]}]},"r":{"a":0,"k":0},"p":{"k":[{"s":[0,29.984,0],"t":127,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.965,0],"t":128,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.936,0],"t":129,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.894,0],"t":130,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.84,0],"t":131,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.77,0],"t":132,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.682,0],"t":133,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.574,0],"t":134,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.445,0],"t":135,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.294,0],"t":136,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.121,0],"t":137,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,28.925,0],"t":138,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,28.746,0],"t":139,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,28.548,0],"t":140,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,28.33,0],"t":141,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,28.092,0],"t":142,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,27.832,0],"t":143,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,27.548,0],"t":144,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,27.239,0],"t":145,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,26.903,0],"t":146,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,26.536,0],"t":147,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,26.14,0],"t":148,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,25.709,0],"t":149,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,25.241,0],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,24.73,0],"t":151,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,24.171,0],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,23.563,0],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,22.898,0],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,22.171,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,21.373,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,20.496,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,19.524,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,18.451,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,17.263,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,15.943,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,14.475,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,12.841,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,11.018,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,9.023,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,6.87,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,4.614,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,2.333,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,0.106,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-1.975,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-3.877,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-5.591,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-7.125,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-8.492,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-9.714,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-10.799,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-11.771,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-12.643,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-13.428,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-14.138,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-14.777,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-15.355,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-15.879,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-16.354,0],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-16.784,0],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-17.177,0],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-17.532,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-17.854,0],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-18.146,0],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-18.409,0],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-18.645,0],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-18.858,0],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.048,0],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.217,0],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.366,0],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.496,0],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.61,0],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.707,0],"t":198,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.788,0],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.856,0],"t":200,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.911,0],"t":201,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.954,0],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.984,0],"t":203,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":5,"nm":"Super Slider","np":3,"mn":"ADBE Slider Control","ix":1,"en":1,"ef":[{"ty":0,"nm":"Slider","mn":"ADBE Slider Control-0001","ix":1,"v":{"a":1,"k":[{"i":{"x":[0.64],"y":[0.48]},"o":{"x":[0.36],"y":[0]},"t":121,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":138,"s":[17.5]},{"t":205,"s":[100]}]}}]}],"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":62,"s":[36,36]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":72,"s":[28,28]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":195,"s":[28,28]},{"t":205,"s":[36,36]}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.925490196078,0.752941176471,0.423529411765,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":62,"s":[41,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":0.56},"o":{"x":0.44,"y":0.44},"t":72,"s":[33,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":195,"s":[33,0],"to":[0,0],"ti":[0,0]},{"t":205,"s":[41,0]}]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"right circle","bm":0,"hd":false},{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":62,"s":[36,36]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":72,"s":[28,28]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":195,"s":[28,28]},{"t":205,"s":[36,36]}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.925490196078,0.752941176471,0.423529411765,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":62,"s":[-41,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":0.56},"o":{"x":0.44,"y":0.44},"t":72,"s":[-33,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":195,"s":[-33,0],"to":[0,0],"ti":[0,0]},{"t":205,"s":[-41,0]}]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"left circle","bm":0,"hd":false},{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":62,"s":[36,36]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":72,"s":[28,28]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":195,"s":[28,28]},{"t":205,"s":[36,36]}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.925490196078,0.752941176471,0.423529411765,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"size","bm":0,"hd":false}],"ip":37,"op":345,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".onPrimaryFixedVariant","cl":"onPrimaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,459,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[200,128]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":18},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Frame 1321317559","bm":0,"hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".onPrimaryFixedVariant","cl":"onPrimaryFixedVariant","parent":6,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":198,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":201,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":389,"s":[100]},{"t":392,"s":[0]}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,0,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":5,"nm":"Global Position","np":4,"mn":"Pseudo/88900","ix":1,"en":1,"ef":[{"ty":10,"nm":"Master Parent","mn":"Pseudo/88900-0001","ix":1,"v":{"a":0,"k":2}},{"ty":3,"nm":"Global Position","mn":"Pseudo/88900-0002","ix":2,"v":{"k":[{"s":[277,197.321],"t":148,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,197.13],"t":151,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,197.036],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.921],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.779],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.606],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.39],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.122],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,195.786],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,195.354],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,194.781],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,193.975],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,192.883],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,191.652],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,190.304],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,188.897],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,187.507],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,186.208],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,185.036],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,183.998],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,183.082],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,182.274],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,181.557],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,180.918],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,180.344],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,179.824],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,179.353],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,178.924],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,178.532],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,178.174],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,177.843],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,177.538],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,177.256],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,176.995],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,176.752],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,176.528],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,176.319],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,176.124],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,175.943],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,175.776],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,175.619],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,175.474],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,175.339],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,175.213],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,175.095],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,174.985],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,175.638],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,178.587],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,185.09],"t":198,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,193.793],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,201.516],"t":200,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,207.702],"t":201,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,212.767],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,217.041],"t":203,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,220.728],"t":204,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,223.965],"t":205,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,226.837],"t":206,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,229.392],"t":207,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,231.662],"t":208,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,233.68],"t":209,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,235.467],"t":210,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,237.042],"t":211,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,238.421],"t":212,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,239.622],"t":213,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,240.66],"t":214,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,241.55],"t":215,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,242.299],"t":216,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,242.916],"t":217,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,243.407],"t":218,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,243.572],"t":220,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,243.29],"t":221,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,242.866],"t":222,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,242.351],"t":223,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,241.782],"t":224,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,241.175],"t":225,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,240.597],"t":226,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,240.08],"t":227,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,239.638],"t":228,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,239.281],"t":229,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,239.017],"t":230,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,239.165],"t":238,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,239.365],"t":240,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,239.555],"t":242,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,239.785],"t":245,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,239.579],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,239.389],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,240.278],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,234.833],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,221.896],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,215.604],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,211.894],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,209.347],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,207.439],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,205.933],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,204.711],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,203.696],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,202.839],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,202.106],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,201.474],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,200.925],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,200.444],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,200.022],"t":400,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,199.649],"t":401,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,199.32],"t":402,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,199.03],"t":403,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,198.776],"t":404,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,198.552],"t":405,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,198.355],"t":406,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,198.183],"t":407,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,198.034],"t":408,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,197.902],"t":409,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,197.787],"t":410,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,197.62],"t":412,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]}}]}],"shapes":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.2,0.2],"y":[0,0]},"t":195,"s":[504,315]},{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0]},"t":225,"s":[30,30]},{"i":{"x":[0.8,0.8],"y":[0.15,0.15]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":380,"s":[30,30]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.05,0.05],"y":[0.7,0.7]},"t":386,"s":[219.6,144]},{"t":416,"s":[504,315]}]},"p":{"a":0,"k":[0,0]},"r":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.2],"y":[0]},"t":195,"s":[28]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":225,"s":[30]},{"i":{"x":[0.8],"y":[0.15]},"o":{"x":[0.3],"y":[0]},"t":380,"s":[30]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.05],"y":[0.7]},"t":386,"s":[29.2]},{"t":416,"s":[28]}]},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"matte","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.2,"y":0},"t":195,"s":[0,0,0],"to":[0,0,0],"ti":[0,0,0]},{"t":225,"s":[0,82.5,0],"h":1},{"i":{"x":0.8,"y":0.15},"o":{"x":0.3,"y":0},"t":380,"s":[0,82.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":386,"s":[0,49.5,0],"to":[0,0,0],"ti":[0,0,0]},{"t":416,"s":[0,0,0]}]},"a":{"a":1,"k":[{"i":{"x":0.5,"y":1},"o":{"x":0.28,"y":0},"t":200,"s":[0,0,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.573,"y":1},"o":{"x":0.236,"y":0},"t":218,"s":[0,-6,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.5,"y":1},"o":{"x":0.28,"y":0},"t":232,"s":[0,1.5,0],"to":[0,0,0],"ti":[0,0,0]},{"t":252,"s":[0,0,0]}]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.2,0.2],"y":[0,0]},"t":195,"s":[504,315]},{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0]},"t":225,"s":[30,30]},{"i":{"x":[0.8,0.8],"y":[0.15,0.15]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":380,"s":[30,30]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.05,0.05],"y":[0.7,0.7]},"t":386,"s":[219.6,144]},{"t":416,"s":[504,315]}]},"p":{"a":0,"k":[0,0]},"r":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.2],"y":[0]},"t":195,"s":[28]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":225,"s":[30]},{"i":{"x":[0.8],"y":[0.15]},"o":{"x":[0.3],"y":[0]},"t":380,"s":[30]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.05],"y":[0.7]},"t":386,"s":[29.2]},{"t":416,"s":[28]}]},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":0,"nm":"Home_LofiApp","parent":6,"tt":1,"tp":6,"refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,0,0]},"a":{"a":0,"k":[252,157.5,0]},"s":{"a":1,"k":[{"i":{"x":[0,0,0],"y":[1,1,1]},"o":{"x":[0.2,0.2,0.2],"y":[0,0,0]},"t":195,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":225,"s":[10,10,100]},{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":380,"s":[10,10,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":386,"s":[46,46,100]},{"t":416,"s":[100,100,100]}]}},"ao":0,"w":504,"h":315,"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".onPrimaryFixedVariant","cl":"onPrimaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[503.5,314.5]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705890417,0.258823543787,0,1]},"o":{"a":0,"k":50},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"frame","bm":0,"hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":0,"nm":"Home_LofiLauncher","refId":"comp_2","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[252,157.5,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":504,"h":315,"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".primaryFixedDim","cl":"primaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.925490196078,0.752941176471,0.423529411765,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":14},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"op","nm":"Stroke align: Outside","a":{"k":[{"s":[7],"t":25,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[7],"t":450,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"lj":1,"ml":{"a":0,"k":4},"hd":false},{"ty":"fl","c":{"a":0,"k":[0.925490196078,0.752941176471,0.423529411765,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"frame","bm":0,"hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0}]},{"id":"comp_1","nm":"Home_LofiApp","fr":60,"pfr":1,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".onPrimaryFixedVariant","cl":"onPrimaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[339.937,151.75,0]},"a":{"a":0,"k":[339.937,151.75,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[1.021,-1.766],[0,0],[-2.043,0],[0,0],[1.022,1.767]],"o":[[-1.021,-1.766],[0,0],[-1.022,1.767],[0,0],[2.043,0],[0,0]],"v":[[2.297,-7.675],[-2.297,-7.675],[-9.64,5.025],[-7.343,9],[7.343,9],[9.64,5.025]],"c":true}},"nm":"Path 1","hd":false},{"ty":"rd","nm":"Round Corners 1","r":{"a":0,"k":9},"hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Triangle","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[481.874,21]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Triangle","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[18,18]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Rectangle","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[457.874,21]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Rectangle","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[292,25]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Text field","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[334,279]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Text field","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[109,28]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":12},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Sent","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[425.5,208.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Sent","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[160,56]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Sent","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[400,158.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Sent","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[126,40]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Received","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[251,78.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Received","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onPrimaryFixed","cl":"onPrimaryFixed","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[334,157.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[340,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":16},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Message","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".onPrimaryFixedVariant","cl":"onPrimaryFixedVariant","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[82,171.125,0]},"a":{"a":0,"k":[82,171.125,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[64,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":39.375},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 2","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[80,177.125]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[92,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":39.375},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 1","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[94,165.125]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":39.375},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Avatar","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[34,171.125]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"circle 2","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".onPrimaryFixed","cl":"onPrimaryFixed","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[82,140,0]},"a":{"a":0,"k":[82,140.938,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[132,22]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":39.375},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Search","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[82,31.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"header","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[64,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 2","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[80,257.375]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 6","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[92,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 1","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[94,245.375]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 5","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Avatar","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[34,251.375]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"circle 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[132,64]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":12},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Message","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[82,171]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"block","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[64,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 2","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[80,96.875]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[92,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 1","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[94,84.875]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 1","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Avatar","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[34,90.875]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"circle 1","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".onPrimaryFixedVariant","cl":"onPrimaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[252,157.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app only","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]},{"id":"comp_2","nm":"Home_LofiLauncher","fr":60,"pfr":1,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".onPrimaryFixedVariant","cl":"onPrimaryFixedVariant","parent":4,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":195,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":204,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":383,"s":[100]},{"t":389,"s":[0]}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,117.5,0]},"a":{"a":0,"k":[252,275,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[444,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 5","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[396,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[348,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[300,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 1","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[168,20]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":15},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"qsb","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[132,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"qsb","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onPrimaryFixedVariant","cl":"onPrimaryFixedVariant","parent":4,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":195,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":204,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":383,"s":[100]},{"t":389,"s":[0]}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,-29.497,0]},"a":{"a":0,"k":[252,128.003,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[20.144,20.144],[20.144,-20.144],[0,0],[-20.144,-20.144],[-20.144,20.144],[0,0]],"o":[[-20.144,-20.144],[0,0],[-20.144,20.144],[20.144,20.144],[0,0],[20.144,-20.144]],"v":[[44.892,-44.892],[-28.057,-44.892],[-44.892,-28.057],[-44.892,44.892],[28.057,44.892],[44.892,28.057]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[108,152.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets weather","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[4.782,-2.684],[0,0],[2.63,-0.033],[0,0],[2.807,-4.716],[0,0],[2.263,-1.343],[0,0],[0.066,-5.485],[0,0],[1.292,-2.295],[0,0],[-2.683,-4.784],[0,0],[-0.033,-2.63],[0,0],[-4.716,-2.807],[0,0],[-1.338,-2.263],[0,0],[-5.483,-0.066],[0,0],[-2.296,-1.292],[0,0],[-4.782,2.683],[0,0],[-2.63,0.033],[0,0],[-2.807,4.716],[0,0],[-2.263,1.338],[0,0],[-0.066,5.483],[0,0],[-1.292,2.295],[0,0],[2.683,4.784],[0,0],[0.033,2.631],[0,0],[4.716,2.801],[0,0],[1.338,2.262],[0,0],[5.483,0.068],[0,0],[2.296,1.287]],"o":[[-4.782,-2.684],[0,0],[-2.296,1.287],[0,0],[-5.483,0.068],[0,0],[-1.338,2.262],[0,0],[-4.716,2.801],[0,0],[-0.033,2.631],[0,0],[-2.683,4.784],[0,0],[1.292,2.295],[0,0],[0.066,5.483],[0,0],[2.263,1.338],[0,0],[2.807,4.716],[0,0],[2.63,0.033],[0,0],[4.782,2.683],[0,0],[2.296,-1.292],[0,0],[5.483,-0.066],[0,0],[1.338,-2.263],[0,0],[4.716,-2.807],[0,0],[0.033,-2.63],[0,0],[2.683,-4.784],[0,0],[-1.292,-2.295],[0,0],[-0.066,-5.485],[0,0],[-2.263,-1.343],[0,0],[-2.807,-4.716],[0,0],[-2.63,-0.033],[0,0]],"v":[[7.7,-57.989],[-7.7,-57.989],[-11.019,-56.128],[-18.523,-54.117],[-22.327,-54.07],[-35.668,-46.369],[-37.609,-43.1],[-43.099,-37.605],[-46.372,-35.663],[-54.072,-22.324],[-54.118,-18.522],[-56.132,-11.016],[-57.988,-7.7],[-57.988,7.703],[-56.132,11.019],[-54.118,18.524],[-54.072,22.328],[-46.372,35.669],[-43.099,37.611],[-37.609,43.101],[-35.668,46.373],[-22.327,54.074],[-18.523,54.12],[-11.019,56.133],[-7.7,57.99],[7.7,57.99],[11.019,56.133],[18.523,54.12],[22.327,54.074],[35.668,46.373],[37.609,43.101],[43.099,37.611],[46.372,35.669],[54.072,22.328],[54.118,18.524],[56.132,11.019],[57.988,7.703],[57.988,-7.7],[56.132,-11.016],[54.118,-18.522],[54.072,-22.324],[46.372,-35.663],[43.099,-37.605],[37.609,-43.1],[35.668,-46.369],[22.327,-54.07],[18.523,-54.117],[11.019,-56.128]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[396,104.003]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets clock","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".onPrimaryFixedVariant","cl":"onPrimaryFixedVariant","parent":4,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":195,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":204,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":383,"s":[100]},{"t":389,"s":[0]}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,-29.497,0]},"a":{"a":0,"k":[252,128.003,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[444,200.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 7","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[348,200.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 6","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252,128.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252,56.002]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[156,56.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[60,56.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 1","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":3,"nm":"Scale Up","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[252,157.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":1,"k":[{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":195,"s":[85,85,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":201,"s":[91,91,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":231,"s":[100,100,100]},{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":380,"s":[100,100,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":386,"s":[96,96,100]},{"t":416,"s":[90,90,100]}]}},"ao":0,"ef":[{"ty":5,"nm":"Void","np":19,"mn":"Pseudo/250958","ix":1,"en":1,"ef":[{"ty":0,"nm":"Width","mn":"Pseudo/250958-0001","ix":1,"v":{"a":0,"k":100}},{"ty":0,"nm":"Height","mn":"Pseudo/250958-0002","ix":2,"v":{"a":0,"k":100}},{"ty":0,"nm":"Offset X","mn":"Pseudo/250958-0003","ix":3,"v":{"a":0,"k":0}},{"ty":0,"nm":"Offset Y","mn":"Pseudo/250958-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Roundness","mn":"Pseudo/250958-0005","ix":5,"v":{"a":0,"k":0}},{"ty":6,"nm":"About","mn":"Pseudo/250958-0006","ix":6,"v":0},{"ty":6,"nm":"Plague of null layers.","mn":"Pseudo/250958-0007","ix":7,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0008","ix":8,"v":0},{"ty":6,"nm":"Following projects","mn":"Pseudo/250958-0009","ix":9,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0010","ix":10,"v":0},{"ty":6,"nm":"through time.","mn":"Pseudo/250958-0011","ix":11,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0012","ix":12,"v":0},{"ty":6,"nm":"Be free of the past.","mn":"Pseudo/250958-0013","ix":13,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0014","ix":14,"v":0},{"ty":6,"nm":"Copyright 2023 Battle Axe Inc","mn":"Pseudo/250958-0015","ix":15,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0016","ix":16,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0017","ix":17,"v":0}]}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".primaryFixedDim","cl":"primaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[252,157.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.925490196078,0.752941176471,0.423529411765,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"illustrations: action key","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"Home_Dismiss","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,282,0]},"a":{"a":0,"k":[277,282,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":554,"h":564,"ip":0,"op":426,"st":-25,"ct":1,"bm":0}],"markers":[],"props":{}}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/trackpad_home_success.json b/packages/SystemUI/res/raw/trackpad_home_success.json
new file mode 100644
index 0000000..f14fde5
--- /dev/null
+++ b/packages/SystemUI/res/raw/trackpad_home_success.json
@@ -0,0 +1 @@
+{"v":"5.12.1","fr":60,"ip":0,"op":50,"w":554,"h":564,"nm":"Trackpad-JSON_HomeGesture-Success","ddd":0,"assets":[{"id":"comp_0","nm":"TrackpadHome_Success_Checkmark","fr":60,"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Check Rotate","parent":2,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":1,"k":[{"i":{"x":[0.12],"y":[1]},"o":{"x":[0.44],"y":[0]},"t":2,"s":[-16]},{"t":20,"s":[6]}]},"p":{"a":0,"k":[0,0,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[95.049,95.049,100]}},"ao":0,"ip":0,"op":228,"st":-72,"bm":0},{"ddd":0,"ind":2,"ty":3,"nm":"Bounce","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":1,"k":[{"i":{"x":[0.12],"y":[1]},"o":{"x":[0.44],"y":[0]},"t":12,"s":[0]},{"t":36,"s":[-6]}]},"p":{"a":0,"k":[81,127,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":1,"k":[{"i":{"x":[0.263,0.263,0.833],"y":[1.126,1.126,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.958,0.958,0]},"t":1,"s":[80,80,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.45,0.45,0.167],"y":[0.325,0.325,0]},"t":20,"s":[105,105,100]},{"t":36,"s":[100,100,100]}]}},"ao":0,"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".primaryFixedDim","cl":"primaryFixedDim","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":-0.289},"p":{"a":0,"k":[14.364,-33.591,0]},"a":{"a":0,"k":[-0.125,0,0]},"s":{"a":0,"k":[104.744,104.744,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-1.401,-0.007],[-10.033,11.235]],"o":[[5.954,7.288],[1.401,0.007],[0,0]],"v":[[-28.591,4.149],[-10.73,26.013],[31.482,-21.255]],"c":false}},"nm":"Path 1","hd":false},{"ty":"tm","s":{"a":0,"k":0},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":3,"s":[0]},{"i":{"x":[0.22],"y":[1]},"o":{"x":[0.001],"y":[0.149]},"t":10,"s":[29]},{"t":27,"s":[100]}]},"o":{"a":0,"k":0},"m":1,"nm":"Trim Paths 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.925490196078,0.752941176471,0.423529411765,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":11},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":5,"op":44,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".onPrimaryFixedVariant","cl":"onPrimaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[95,95,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":1,"k":[{"i":{"x":[0.275,0.275,0.21],"y":[1.102,1.102,1]},"o":{"x":[0.037,0.037,0.05],"y":[0.476,0.476,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.252,0.252,0.47],"y":[0.159,0.159,0]},"t":16,"s":[120,120,100]},{"t":28,"s":[100,100,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.32,0.32],"y":[0.11,0.11]},"t":16,"s":[148,148]},{"t":28,"s":[136,136]}]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":88},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Checkbox - Widget","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]},{"id":"comp_1","nm":"Home_LofiApp","fr":60,"pfr":1,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".onPrimaryFixedVariant","cl":"onPrimaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[339.937,151.75,0]},"a":{"a":0,"k":[339.937,151.75,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[1.021,-1.766],[0,0],[-2.043,0],[0,0],[1.022,1.767]],"o":[[-1.021,-1.766],[0,0],[-1.022,1.767],[0,0],[2.043,0],[0,0]],"v":[[2.297,-7.675],[-2.297,-7.675],[-9.64,5.025],[-7.343,9],[7.343,9],[9.64,5.025]],"c":true}},"nm":"Path 1","hd":false},{"ty":"rd","nm":"Round Corners 1","r":{"a":0,"k":9},"hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Triangle","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[481.874,21]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Triangle","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[18,18]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Rectangle","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[457.874,21]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Rectangle","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[292,25]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Text field","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[334,279]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Text field","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[109,28]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":12},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Sent","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[425.5,208.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Sent","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[160,56]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Sent","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[400,158.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Sent","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[126,40]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Received","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[251,78.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Received","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onPrimaryFixed","cl":"onPrimaryFixed","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[334,157.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[340,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":16},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Message","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".onPrimaryFixedVariant","cl":"onPrimaryFixedVariant","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[82,171.125,0]},"a":{"a":0,"k":[82,171.125,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[64,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":39.375},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 2","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[80,177.125]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[92,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":39.375},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 1","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[94,165.125]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":39.375},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Avatar","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[34,171.125]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"circle 2","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".onPrimaryFixed","cl":"onPrimaryFixed","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[82.5,140.5,0]},"a":{"a":0,"k":[82,140.938,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[132,22]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":39.375},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Search","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[82,31.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"header","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[64,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 2","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[80,257.375]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 6","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[92,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 1","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[94,245.375]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 5","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Avatar","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[34,251.375]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"circle 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[132,64]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":12},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Message","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[82,171]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"block","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[64,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 2","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[80,96.875]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[92,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 1","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[94,84.875]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 1","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Avatar","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[34,90.875]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"circle 1","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".onPrimaryFixedVariant","cl":"onPrimaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[252,157.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app only","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".onPrimaryFixedVariant","cl":"onPrimaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,459,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[200,128]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":18},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Frame 1321317559","bm":0,"hd":false}],"ip":0,"op":50,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"TrackpadHome_Success_Checkmark","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,198.5,0]},"a":{"a":0,"k":[95,95,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":190,"h":190,"ip":6,"op":50,"st":6,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".onPrimaryFixed","cl":"onPrimaryFixed","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":1,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":7,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":389,"s":[100]},{"t":392,"s":[0]}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":5,"nm":"Global Position","np":4,"mn":"Pseudo/88900","ix":1,"en":1,"ef":[{"ty":10,"nm":"Master Parent","mn":"Pseudo/88900-0001","ix":1,"v":{"a":0,"k":3}},{"ty":3,"nm":"Global Position","mn":"Pseudo/88900-0002","ix":2,"v":{"k":[{"s":[277,197.5],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,197.5],"t":49,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]}}]}],"shapes":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false}],"ip":0,"op":50,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"matte","td":1,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":5,"nm":"Global Position","np":4,"mn":"Pseudo/88900","ix":1,"en":1,"ef":[{"ty":10,"nm":"Master Parent","mn":"Pseudo/88900-0001","ix":1,"v":{"a":0,"k":3}},{"ty":3,"nm":"Global Position","mn":"Pseudo/88900-0002","ix":2,"v":{"k":[{"s":[277,197.5],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,197.5],"t":49,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]}}]}],"shapes":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false}],"ip":0,"op":50,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":0,"nm":"Home_LofiApp","tt":1,"tp":4,"refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[252,157.5,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":504,"h":315,"ip":0,"op":50,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".primaryFixedDim","cl":"primaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.925490196078,0.752941176471,0.423529411765,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":14},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"op","nm":"Stroke align: Outside","a":{"k":[{"s":[7],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[7],"t":49,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"lj":1,"ml":{"a":0,"k":4},"hd":false},{"ty":"fl","c":{"a":0,"k":[0.925490196078,0.752941176471,0.423529411765,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"frame","bm":0,"hd":false}],"ip":0,"op":50,"st":0,"ct":1,"bm":0}],"markers":[],"props":{}}
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 0350cd7..8cf0fb2 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -160,8 +160,8 @@
 
     <color name="GM2_red_300">#F28B82</color>
     <color name="GM2_red_500">#EA4335</color>
-    <color name="GM2_red_600">#B3261E</color>
     <color name="GM2_red_700">#C5221F</color>
+    <color name="GM2_red_800">#B3261E</color>
 
     <color name="GM2_blue_300">#8AB4F8</color>
 
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index e4f900d..25bca25 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -278,4 +278,7 @@
     <!-- Id for the udfps accessibility overlay -->
     <item type="id" name="udfps_accessibility_overlay" />
     <item type="id" name="udfps_accessibility_overlay_top_guideline" />
+
+    <!-- Ids for communal hub widgets -->
+    <item type="id" name="communal_widget_disposable_tag"/>
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 8bad971..f8303ea 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -280,13 +280,19 @@
 
     <!-- For updated Screen Recording permission dialog (i.e. with PSS)-->
     <!-- Title for the screen prompting the user to begin recording their screen [CHAR LIMIT=NONE]-->
-    <string name="screenrecord_permission_dialog_title">Start Recording?</string>
+    <string name="screenrecord_permission_dialog_title">Record your screen?</string>
+    <!-- Screen recording permission option for recording just a single app [CHAR LIMIT=50] -->
+    <string name="screenrecord_permission_dialog_option_text_single_app">Record one app</string>
+    <!-- Screen recording permission option for recording the whole screen [CHAR LIMIT=50] -->
+    <string name="screenrecord_permission_dialog_option_text_entire_screen">Record entire screen</string>
     <!-- Message reminding the user that sensitive information may be captured during a full screen recording for the updated dialog that includes partial screen sharing option [CHAR_LIMIT=350]-->
-    <string name="screenrecord_permission_dialog_warning_entire_screen">While you’re recording, Android has access to anything visible on your screen or played on your device. So be careful with things like passwords, payment details, messages, photos, and audio and video.</string>
+    <string name="screenrecord_permission_dialog_warning_entire_screen">When you’re recording your entire screen, anything shown on your screen is recorded. So be careful with things like passwords, payment details, messages, photos, and audio and video.</string>
     <!-- Message reminding the user that sensitive information may be captured during a single app screen recording for the updated dialog that includes partial screen sharing option [CHAR_LIMIT=350]-->
-    <string name="screenrecord_permission_dialog_warning_single_app">While you’re recording an app, Android has access to anything shown or played on that app. So be careful with things like passwords, payment details, messages, photos, and audio and video.</string>
-    <!-- Button to start a screen recording in the updated screen record dialog that allows to select an app to record [CHAR LIMIT=50]-->
-    <string name="screenrecord_permission_dialog_continue">Start recording</string>
+    <string name="screenrecord_permission_dialog_warning_single_app">When you’re recording an app, anything shown or played in that app is recorded. So be careful with things like passwords, payment details, messages, photos, and audio and video.</string>
+    <!-- Button to start a screen recording of the entire screen in the updated screen record dialog that allows to select an app to record [CHAR LIMIT=50]-->
+    <string name="screenrecord_permission_dialog_continue_entire_screen">Record screen</string>
+    <!-- Title of the activity that allows to select an app to screen record [CHAR LIMIT=70] -->
+    <string name="screenrecord_app_selector_title">Choose app to record</string>
 
     <!-- Label for a switch to enable recording audio [CHAR LIMIT=NONE]-->
     <string name="screenrecord_audio_label">Record audio</string>
@@ -971,8 +977,8 @@
     <string name="hearing_devices_presets_error">Couldn\'t update preset</string>
     <!-- QuickSettings: Title for hearing aids presets. Preset is a set of hearing aid settings. User can apply different settings in different environments (e.g. Outdoor, Restaurant, Home) [CHAR LIMIT=40]-->
     <string name="hearing_devices_preset_label">Preset</string>
-    <!-- QuickSettings: Tool name for hearing devices dialog related tools [CHAR LIMIT=40]-->
-    <string name="live_caption_title">Live Caption</string>
+    <!-- QuickSettings: Tool name for hearing devices dialog related tools [CHAR LIMIT=40] [BACKUP_MESSAGE_ID=8916875614623730005]-->
+    <string name="quick_settings_hearing_devices_live_caption_title">Live Caption</string>
 
     <!--- Title of dialog triggered if the microphone is disabled but an app tried to access it. [CHAR LIMIT=150] -->
     <string name="sensor_privacy_start_use_mic_dialog_title">Unblock device microphone?</string>
@@ -1358,12 +1364,8 @@
     <!-- Media projection permission dialog warning text for system services. [CHAR LIMIT=NONE] -->
     <string name="media_projection_sys_service_dialog_warning">The service providing this function will have access to all of the information that is visible on your screen or played from your device while recording or casting. This includes information such as passwords, payment details, photos, messages, and audio that you play.</string>
 
-    <!-- Permission dropdown option for sharing or recording the whole screen. [CHAR LIMIT=30] -->
-    <string name="screen_share_permission_dialog_option_entire_screen">Entire screen</string>
-    <!-- Permission dropdown option for sharing or recording single app. [CHAR LIMIT=30] -->
-    <string name="screen_share_permission_dialog_option_single_app">A single app</string>
-    <!-- Title of the dialog that allows to select an app to share or record [CHAR LIMIT=NONE] -->
-    <string name="screen_share_permission_app_selector_title">Share or record an app</string>
+    <!-- Title of the activity that allows users to select an app to share or record [CHAR LIMIT=NONE] -->
+    <string name="screen_share_generic_app_selector_title">Share or record an app</string>
 
     <!-- Media projection that launched from 1P/3P apps -->
     <!-- 1P/3P app media projection permission dialog title. [CHAR LIMIT=NONE] -->
@@ -1381,6 +1383,8 @@
     <string name="media_projection_entry_app_permission_dialog_continue_entire_screen">Share screen</string>
     <!-- 1P/3P apps disabled the single app projection option. [CHAR LIMIT=NONE] -->
     <string name="media_projection_entry_app_permission_dialog_single_app_disabled"><xliff:g id="app_name" example="Meet">%1$s</xliff:g> has disabled this option</string>
+    <!-- Title of the activity that allows users to select an app to share to a 1P/3P app [CHAR LIMIT=70] -->
+    <string name="media_projection_entry_share_app_selector_title">Choose app to share</string>
 
     <!-- Casting that launched by SysUI (i.e. when there is no app name) -->
     <!-- System casting media projection permission dialog title. [CHAR LIMIT=100] -->
@@ -1395,6 +1399,8 @@
     <string name="media_projection_entry_cast_permission_dialog_warning_single_app">When you’re casting an app, anything shown or played in that app is visible. So be careful with things like passwords, payment details, messages, photos, and audio and video.</string>
     <!-- System casting media projection permission button to continue for SysUI casting. [CHAR LIMIT=60] -->
     <string name="media_projection_entry_cast_permission_dialog_continue_entire_screen">Cast screen</string>
+    <!-- Title of the activity that allows users to select an app to cast [CHAR LIMIT=70] -->
+    <string name="media_projection_entry_cast_app_selector_title">Choose app to cast</string>
 
     <!-- Other sharing (not recording nor casting) that launched by SysUI (currently not in use) -->
     <!-- System sharing media projection permission dialog title. [CHAR LIMIT=100] -->
@@ -3679,6 +3685,7 @@
           -->
     <string name="shortcut_helper_key_combinations_or_separator">or</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>
     <!-- Label for button opening tutorial for back gesture on touchpad [CHAR LIMIT=NONE] -->
@@ -3687,17 +3694,35 @@
     <string name="touchpad_tutorial_action_key_button">Action key</string>
     <!-- Label for button finishing touchpad tutorial [CHAR LIMIT=NONE] -->
     <string name="touchpad_tutorial_done_button">Done</string>
-    <!-- Screen title after gesture was done successfully [CHAR LIMIT=NONE] -->
-    <string name="touchpad_tutorial_gesture_done">Great job!</string>
+    <!-- BACK GESTURE -->
     <!-- Touchpad back gesture action name in tutorial [CHAR LIMIT=NONE] -->
     <string name="touchpad_back_gesture_action_title">Go back</string>
     <!-- Touchpad back gesture guidance in gestures tutorial [CHAR LIMIT=NONE] -->
     <string name="touchpad_back_gesture_guidance">To go back, swipe left or right using three fingers anywhere on the touchpad.\n\nYou can also use the keyboard shortcut
 Action + ESC for this.</string>
+    <!-- Screen title after back gesture was done successfully [CHAR LIMIT=NONE] -->
+    <string name="touchpad_back_gesture_success_title">Great job!</string>
     <!-- Text shown to the user after they complete back gesture tutorial [CHAR LIMIT=NONE] -->
-    <string name="touchpad_back_gesture_finished">You completed the go back gesture.</string>
-    <string name="touchpad_back_gesture_animation_content_description">Touchpad showing three fingers moving right and left</string>
-    <string name="touchpad_back_gesture_screen_animation_content_description">Device screen showing animation for back gesture</string>
+    <string name="touchpad_back_gesture_success_body">You completed the go back gesture.</string>
+    <!-- HOME GESTURE -->
+    <!-- Touchpad home gesture action name in tutorial [CHAR LIMIT=NONE] -->
+    <string name="touchpad_home_gesture_action_title">Go home</string>
+    <!-- Touchpad home gesture guidance in gestures tutorial [CHAR LIMIT=NONE] -->
+    <string name="touchpad_home_gesture_guidance">To go to your home screen at any time, swipe up with three fingers from the bottom of your screen.</string>
+    <!-- Screen title after home gesture was done successfully [CHAR LIMIT=NONE] -->
+    <string name="touchpad_home_gesture_success_title">Nice!</string>
+    <!-- Text shown to the user after they complete home gesture tutorial [CHAR LIMIT=NONE] -->
+    <string name="touchpad_home_gesture_success_body">You completed the go home gesture.</string>
+
+    <!-- KEYBOARD TUTORIAL-->
+    <!-- Action key tutorial title [CHAR LIMIT=NONE] -->
+    <string name="tutorial_action_key_title">Action key</string>
+    <!-- Action key tutorial guidance[CHAR LIMIT=NONE] -->
+    <string name="tutorial_action_key_guidance">To access your apps, press the action key on your keyboard.</string>
+    <!-- Screen title after action key pressed successfully [CHAR LIMIT=NONE] -->
+    <string name="tutorial_action_key_success_title">Congratulations!</string>
+    <!-- Text shown to the user after they complete action key tutorial [CHAR LIMIT=NONE] -->
+    <string name="tutorial_action_key_success_body">You completed the action key gesture.\n\nAction + / shows all the shortcuts you have available.</string>
 
     <!-- Content description for keyboard backlight brightness dialog [CHAR LIMIT=NONE] -->
     <string name="keyboard_backlight_dialog_title">Keyboard backlight</string>
diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt
index 12d881b..c0b6acf 100644
--- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt
@@ -16,6 +16,7 @@
 package com.android.systemui.biometrics
 
 import android.Manifest
+import android.app.ActivityTaskManager
 import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC
 import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC
 import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX
@@ -35,6 +36,7 @@
 import android.hardware.biometrics.SensorPropertiesInternal
 import android.os.UserManager
 import android.util.DisplayMetrics
+import android.util.Log
 import android.view.ViewGroup
 import android.view.WindowInsets
 import android.view.WindowManager
@@ -45,6 +47,8 @@
 import com.android.systemui.biometrics.shared.model.PromptKind
 
 object Utils {
+    private const val TAG = "SysUIBiometricUtils"
+
     /** Base set of layout flags for fingerprint overlay widgets. */
     const val FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS =
         (WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or
@@ -148,4 +152,39 @@
         draw(canvas)
         return bitmap
     }
+
+    // LINT.IfChange
+    @JvmStatic
+    /**
+     * Checks if a client package is running in the background or it's a system app.
+     *
+     * @param clientPackage The name of the package to be checked.
+     * @param clientClassNameIfItIsConfirmDeviceCredentialActivity The class name of
+     *   ConfirmDeviceCredentialActivity.
+     * @return Whether the client package is running in background
+     */
+    fun ActivityTaskManager.isSystemAppOrInBackground(
+        context: Context,
+        clientPackage: String,
+        clientClassNameIfItIsConfirmDeviceCredentialActivity: String?
+    ): Boolean {
+        Log.v(TAG, "Checking if the authenticating is in background, clientPackage:$clientPackage")
+        val tasks = getTasks(Int.MAX_VALUE)
+        if (tasks == null || tasks.isEmpty()) {
+            Log.w(TAG, "No running tasks reported")
+            return false
+        }
+
+        val topActivity = tasks[0].topActivity
+        val isSystemApp = isSystem(context, clientPackage)
+        val topPackageEqualsToClient = topActivity!!.packageName == clientPackage
+        val isClientConfirmDeviceCredentialActivity =
+            clientClassNameIfItIsConfirmDeviceCredentialActivity != null
+        // b/339532378: If it's ConfirmDeviceCredentialActivity, we need to check further on
+        // class name.
+        return !(isSystemApp || topPackageEqualsToClient) ||
+            (isClientConfirmDeviceCredentialActivity &&
+                topActivity.className != clientClassNameIfItIsConfirmDeviceCredentialActivity)
+    }
+    // LINT.ThenChange(frameworks/base/services/core/java/com/android/server/biometrics/Utils.java)
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
index 68d2eb3..41ad437 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
@@ -26,6 +26,7 @@
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.inputmethodservice.InputMethodService;
+import android.inputmethodservice.InputMethodService.BackDispositionMode;
 import android.os.Build;
 import android.os.Handler;
 import android.os.Message;
@@ -105,8 +106,8 @@
      * @return updated set of flags from InputMethodService based off {@param oldHints}
      *          Leaves original hints unmodified
      */
-    public static int calculateBackDispositionHints(int oldHints, int backDisposition,
-            boolean imeShown, boolean showImeSwitcher) {
+    public static int calculateBackDispositionHints(int oldHints,
+            @BackDispositionMode int backDisposition, boolean imeShown, boolean showImeSwitcher) {
         int hints = oldHints;
         switch (backDisposition) {
             case InputMethodService.BACK_DISPOSITION_DEFAULT:
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java
index 317201d..f358ba2 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java
@@ -125,6 +125,7 @@
                 taskbarMarginLeft, taskbarMarginBottom, floatingRotationButtonPositionLeft);
 
         final int diameter = res.getDimensionPixelSize(mButtonDiameterResource);
+        mKeyButtonView.setDiameter(diameter);
         mContainerSize = diameter + Math.max(defaultMargin, Math.max(taskbarMarginLeft,
                 taskbarMarginBottom));
     }
@@ -195,6 +196,7 @@
     public void updateIcon(int lightIconColor, int darkIconColor) {
         mAnimatedDrawable = (AnimatedVectorDrawable) mKeyButtonView.getContext()
                 .getDrawable(mRotationButtonController.getIconResId());
+        mAnimatedDrawable.setBounds(0, 0, mKeyButtonView.getWidth(), mKeyButtonView.getHeight());
         mKeyButtonView.setImageDrawable(mAnimatedDrawable);
         mKeyButtonView.setColors(lightIconColor, darkIconColor);
     }
@@ -248,8 +250,14 @@
             updateDimensionResources();
 
             if (mIsShowing) {
+                updateIcon(mRotationButtonController.getLightIconColor(),
+                        mRotationButtonController.getDarkIconColor());
                 final LayoutParams layoutParams = adjustViewPositionAndCreateLayoutParams();
                 mWindowManager.updateViewLayout(mKeyButtonContainer, layoutParams);
+                if (mAnimatedDrawable != null) {
+                    mAnimatedDrawable.reset();
+                    mAnimatedDrawable.start();
+                }
             }
         }
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java
index 2145166..75412f9 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java
@@ -37,6 +37,7 @@
     private static final float BACKGROUND_ALPHA = 0.92f;
 
     private KeyButtonRipple mRipple;
+    private int mDiameter;
     private final Paint mOvalBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
 
     private final Configuration mLastConfiguration;
@@ -93,10 +94,25 @@
         mRipple.setDarkIntensity(darkIntensity);
     }
 
+    /**
+     * Sets the view's diameter.
+     *
+     * @param diameter the diameter value for the view
+     */
+    void setDiameter(int diameter) {
+        mDiameter = diameter;
+    }
+
     @Override
     public void draw(Canvas canvas) {
         int d = Math.min(getWidth(), getHeight());
         canvas.drawOval(0, 0, d, d, mOvalBgPaint);
         super.draw(canvas);
     }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        setMeasuredDimension(mDiameter, mDiameter);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index f688d4f..d468f2f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -60,9 +60,7 @@
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerAlwaysOnDisplayViewBinder;
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
-import com.android.systemui.statusbar.phone.NotificationIconAreaController;
 import com.android.systemui.statusbar.phone.NotificationIconContainer;
 import com.android.systemui.util.ViewController;
 import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -87,7 +85,6 @@
     private final StatusBarStateController mStatusBarStateController;
     private final ClockRegistry mClockRegistry;
     private final KeyguardSliceViewController mKeyguardSliceViewController;
-    private final NotificationIconAreaController mNotificationIconAreaController;
     private final LockscreenSmartspaceController mSmartspaceController;
     private final SecureSettings mSecureSettings;
     private final DumpManager mDumpManager;
@@ -165,7 +162,6 @@
             StatusBarStateController statusBarStateController,
             ClockRegistry clockRegistry,
             KeyguardSliceViewController keyguardSliceViewController,
-            NotificationIconAreaController notificationIconAreaController,
             LockscreenSmartspaceController smartspaceController,
             NotificationIconContainerAlwaysOnDisplayViewBinder nicViewBinder,
             KeyguardUnlockAnimationController keyguardUnlockAnimationController,
@@ -183,7 +179,6 @@
         mStatusBarStateController = statusBarStateController;
         mClockRegistry = clockRegistry;
         mKeyguardSliceViewController = keyguardSliceViewController;
-        mNotificationIconAreaController = notificationIconAreaController;
         mSmartspaceController = smartspaceController;
         mNicViewBinder = nicViewBinder;
         mSecureSettings = secureSettings;
@@ -351,10 +346,8 @@
     int getNotificationIconAreaHeight() {
         if (MigrateClocksToBlueprint.isEnabled()) {
             return 0;
-        } else if (NotificationIconContainerRefactor.isEnabled()) {
-            return mAodIconContainer != null ? mAodIconContainer.getHeight() : 0;
         } else {
-            return mNotificationIconAreaController.getHeight();
+            return mAodIconContainer != null ? mAodIconContainer.getHeight() : 0;
         }
     }
 
@@ -603,16 +596,11 @@
             NotificationIconContainer nic = (NotificationIconContainer)
                     mView.findViewById(
                             com.android.systemui.res.R.id.left_aligned_notification_icon_container);
-            if (NotificationIconContainerRefactor.isEnabled()) {
-                if (mAodIconsBindHandle != null) {
-                    mAodIconsBindHandle.dispose();
-                }
-                if (nic != null) {
-                    mAodIconsBindHandle = mNicViewBinder.bindWhileAttached(nic);
-                    mAodIconContainer = nic;
-                }
-            } else {
-                mNotificationIconAreaController.setupAodIcons(nic);
+            if (mAodIconsBindHandle != null) {
+                mAodIconsBindHandle.dispose();
+            }
+            if (nic != null) {
+                mAodIconsBindHandle = mNicViewBinder.bindWhileAttached(nic);
                 mAodIconContainer = nic;
             }
         }
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
index 781f6dd..831543d 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
@@ -74,7 +74,6 @@
                 clockBuffers,
                 /* keepAllLoaded = */ false,
                 /* subTag = */ "System",
-                /* isTransitClockEnabled = */ featureFlags.isEnabled(Flags.TRANSIT_CLOCK),
                 new ThreadAssert());
         registry.registerListeners();
         return registry;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
index 93c4630..d81a686 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
@@ -46,6 +46,7 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.systemui.Flags;
@@ -193,15 +194,18 @@
         private final Context mContext;
         private final MagnificationSettingsController.Callback mSettingsControllerCallback;
         private final SecureSettings mSecureSettings;
+        private final ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
 
         SettingsSupplier(Context context,
                 MagnificationSettingsController.Callback settingsControllerCallback,
                 DisplayManager displayManager,
-                SecureSettings secureSettings) {
+                SecureSettings secureSettings,
+                ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
             super(displayManager);
             mContext = context;
             mSettingsControllerCallback = settingsControllerCallback;
             mSecureSettings = secureSettings;
+            mViewCaptureAwareWindowManager = viewCaptureAwareWindowManager;
         }
 
         @Override
@@ -213,7 +217,8 @@
                     windowContext,
                     new SfVsyncFrameCallbackProvider(),
                     mSettingsControllerCallback,
-                    mSecureSettings);
+                    mSecureSettings,
+                    mViewCaptureAwareWindowManager);
         }
     }
 
@@ -227,10 +232,12 @@
             SysUiState sysUiState, OverviewProxyService overviewProxyService,
             SecureSettings secureSettings, DisplayTracker displayTracker,
             DisplayManager displayManager, AccessibilityLogger a11yLogger,
-            IWindowManager iWindowManager, AccessibilityManager accessibilityManager) {
+            IWindowManager iWindowManager, AccessibilityManager accessibilityManager,
+            ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
         this(context, mainHandler.getLooper(), executor, commandQueue,
                 modeSwitchesController, sysUiState, overviewProxyService, secureSettings,
-                displayTracker, displayManager, a11yLogger, iWindowManager, accessibilityManager);
+                displayTracker, displayManager, a11yLogger, iWindowManager, accessibilityManager,
+                viewCaptureAwareWindowManager);
     }
 
     @VisibleForTesting
@@ -240,7 +247,8 @@
             SecureSettings secureSettings, DisplayTracker displayTracker,
             DisplayManager displayManager, AccessibilityLogger a11yLogger,
             IWindowManager iWindowManager,
-            AccessibilityManager accessibilityManager) {
+            AccessibilityManager accessibilityManager,
+            ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
         mHandler = new Handler(looper) {
             @Override
             public void handleMessage(@NonNull Message msg) {
@@ -263,7 +271,8 @@
         mFullscreenMagnificationControllerSupplier = new FullscreenMagnificationControllerSupplier(
                 context, displayManager, mHandler, mExecutor, iWindowManager);
         mMagnificationSettingsSupplier = new SettingsSupplier(context,
-                mMagnificationSettingsControllerCallback, displayManager, secureSettings);
+                mMagnificationSettingsControllerCallback, displayManager, secureSettings,
+                viewCaptureAwareWindowManager);
 
         mModeSwitchesController.setClickListenerDelegate(
                 displayId -> mHandler.post(() -> {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
index d9d9e37..e91bb6a 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
@@ -46,6 +46,7 @@
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 import android.widget.ImageView;
 
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.systemui.res.R;
@@ -76,6 +77,7 @@
     private final Context mContext;
     private final AccessibilityManager mAccessibilityManager;
     private final WindowManager mWindowManager;
+    private final ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
     private final ImageView mImageView;
     private final Runnable mWindowInsetChangeRunnable;
     private final SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
@@ -99,17 +101,21 @@
         void onClick(int displayId);
     }
 
-    MagnificationModeSwitch(@UiContext Context context, ClickListener clickListener) {
-        this(context, createView(context), new SfVsyncFrameCallbackProvider(), clickListener);
+    MagnificationModeSwitch(@UiContext Context context, ClickListener clickListener,
+            ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
+        this(context, createView(context), new SfVsyncFrameCallbackProvider(), clickListener,
+                viewCaptureAwareWindowManager);
     }
 
     @VisibleForTesting
     MagnificationModeSwitch(Context context, @NonNull ImageView imageView,
-            SfVsyncFrameCallbackProvider sfVsyncFrameProvider, ClickListener clickListener) {
+            SfVsyncFrameCallbackProvider sfVsyncFrameProvider, ClickListener clickListener,
+            ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
         mContext = context;
         mConfiguration = new Configuration(context.getResources().getConfiguration());
         mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
         mWindowManager = mContext.getSystemService(WindowManager.class);
+        mViewCaptureAwareWindowManager = viewCaptureAwareWindowManager;
         mSfVsyncFrameProvider = sfVsyncFrameProvider;
         mClickListener = clickListener;
         mParams = createLayoutParams(context);
@@ -276,7 +282,7 @@
         mImageView.animate().cancel();
         mIsFadeOutAnimating = false;
         mImageView.setAlpha(0f);
-        mWindowManager.removeView(mImageView);
+        mViewCaptureAwareWindowManager.removeView(mImageView);
         mContext.unregisterComponentCallbacks(this);
         mIsVisible = false;
     }
@@ -310,7 +316,7 @@
                 mParams.y = mDraggableWindowBounds.bottom;
                 mToLeftScreenEdge = false;
             }
-            mWindowManager.addView(mImageView, mParams);
+            mViewCaptureAwareWindowManager.addView(mImageView, mParams);
             // Exclude magnification switch button from system gesture area.
             setSystemGestureExclusion();
             mIsVisible = true;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java
index caf5517..fc7535a 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java
@@ -26,6 +26,7 @@
 import android.util.Range;
 import android.view.WindowManager;
 
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.internal.accessibility.common.MagnificationConstants;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
@@ -60,8 +61,10 @@
             @UiContext Context context,
             SfVsyncFrameCallbackProvider sfVsyncFrameProvider,
             @NonNull Callback settingsControllerCallback,
-            SecureSettings secureSettings) {
-        this(context, sfVsyncFrameProvider, settingsControllerCallback,  secureSettings, null);
+            SecureSettings secureSettings,
+            ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
+        this(context, sfVsyncFrameProvider, settingsControllerCallback,  secureSettings, null,
+                viewCaptureAwareWindowManager);
     }
 
     @VisibleForTesting
@@ -70,7 +73,8 @@
             SfVsyncFrameCallbackProvider sfVsyncFrameProvider,
             @NonNull Callback settingsControllerCallback,
             SecureSettings secureSettings,
-            WindowMagnificationSettings windowMagnificationSettings) {
+            WindowMagnificationSettings windowMagnificationSettings,
+            ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
         mContext = context.createWindowContext(
                 context.getDisplay(),
                 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
@@ -84,7 +88,7 @@
         } else {
             mWindowMagnificationSettings = new WindowMagnificationSettings(mContext,
                     mWindowMagnificationSettingsCallback,
-                    sfVsyncFrameProvider, secureSettings);
+                    sfVsyncFrameProvider, secureSettings, viewCaptureAwareWindowManager);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java b/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java
index 63f9cc2..53827e6 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java
@@ -25,6 +25,7 @@
 import android.hardware.display.DisplayManager;
 import android.view.Display;
 
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.dagger.SysUISingleton;
 
@@ -47,8 +48,10 @@
     private ClickListener mClickListenerDelegate;
 
     @Inject
-    public ModeSwitchesController(Context context, DisplayManager displayManager) {
-        mSwitchSupplier = new SwitchSupplier(context, displayManager, this::onClick);
+    public ModeSwitchesController(Context context, DisplayManager displayManager,
+            ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
+        mSwitchSupplier = new SwitchSupplier(context, displayManager, this::onClick,
+                viewCaptureAwareWindowManager);
     }
 
     @VisibleForTesting
@@ -115,6 +118,7 @@
 
         private final Context mContext;
         private final ClickListener mClickListener;
+        private final ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
 
         /**
          * Supplies the switch for the given display.
@@ -124,17 +128,20 @@
          * @param clickListener The callback that will run when the switch is clicked
          */
         SwitchSupplier(Context context, DisplayManager displayManager,
-                ClickListener clickListener) {
+                ClickListener clickListener,
+                ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
             super(displayManager);
             mContext = context;
             mClickListener = clickListener;
+            mViewCaptureAwareWindowManager = viewCaptureAwareWindowManager;
         }
 
         @Override
         protected MagnificationModeSwitch createInstance(Display display) {
             final Context uiContext = mContext.createWindowContext(display,
                     TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, /* options */ null);
-            return new MagnificationModeSwitch(uiContext, mClickListener);
+            return new MagnificationModeSwitch(uiContext, mClickListener,
+                    mViewCaptureAwareWindowManager);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
index 99d966d..9b6501e 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
@@ -56,6 +56,7 @@
 import android.widget.SeekBar;
 import android.widget.Switch;
 
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.systemui.Flags;
@@ -75,6 +76,7 @@
     private final Context mContext;
     private final AccessibilityManager mAccessibilityManager;
     private final WindowManager mWindowManager;
+    private final ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
     private final SecureSettings mSecureSettings;
 
     private final Runnable mWindowInsetChangeRunnable;
@@ -135,10 +137,12 @@
 
     @VisibleForTesting
     WindowMagnificationSettings(Context context, WindowMagnificationSettingsCallback callback,
-            SfVsyncFrameCallbackProvider sfVsyncFrameProvider, SecureSettings secureSettings) {
+            SfVsyncFrameCallbackProvider sfVsyncFrameProvider, SecureSettings secureSettings,
+            ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
         mContext = context;
         mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
         mWindowManager = mContext.getSystemService(WindowManager.class);
+        mViewCaptureAwareWindowManager = viewCaptureAwareWindowManager;
         mSfVsyncFrameProvider = sfVsyncFrameProvider;
         mCallback = callback;
         mSecureSettings = secureSettings;
@@ -320,7 +324,7 @@
 
         // Unregister observer before removing view
         mSecureSettings.unregisterContentObserverSync(mMagnificationCapabilityObserver);
-        mWindowManager.removeView(mSettingView);
+        mViewCaptureAwareWindowManager.removeView(mSettingView);
         mIsVisible = false;
         if (resetPosition) {
             mParams.x = 0;
@@ -378,7 +382,7 @@
                 mParams.y = mDraggableWindowBounds.bottom;
             }
 
-            mWindowManager.addView(mSettingView, mParams);
+            mViewCaptureAwareWindowManager.addView(mSettingView, mParams);
 
             mSecureSettings.registerContentObserverForUserSync(
                     Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY,
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
index 083f1db..d08653c3 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
@@ -228,7 +228,7 @@
         mHearingDeviceItemList = getHearingDevicesList();
         if (mPresetsController != null) {
             activeHearingDevice = getActiveHearingDevice(mHearingDeviceItemList);
-            mPresetsController.setActiveHearingDevice(activeHearingDevice);
+            mPresetsController.setHearingDeviceIfSupportHap(activeHearingDevice);
         } else {
             activeHearingDevice = null;
         }
@@ -336,7 +336,7 @@
         }
         final CachedBluetoothDevice activeHearingDevice = getActiveHearingDevice(
                 mHearingDeviceItemList);
-        mPresetsController.setActiveHearingDevice(activeHearingDevice);
+        mPresetsController.setHearingDeviceIfSupportHap(activeHearingDevice);
 
         mPresetInfoAdapter = new ArrayAdapter<>(dialog.getContext(),
                 R.layout.hearing_devices_preset_spinner_selected,
@@ -499,7 +499,8 @@
         final List<ResolveInfo> resolved = packageManager.queryIntentActivities(LIVE_CAPTION_INTENT,
                 /* flags= */ 0);
         if (!resolved.isEmpty()) {
-            return new ToolItem(context.getString(R.string.live_caption_title),
+            return new ToolItem(
+                    context.getString(R.string.quick_settings_hearing_devices_live_caption_title),
                     context.getDrawable(R.drawable.ic_volume_odi_captions),
                     LIVE_CAPTION_INTENT);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java
index b46b8fe..664f3f8 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java
@@ -27,6 +27,7 @@
 import androidx.annotation.NonNull;
 import androidx.recyclerview.widget.RecyclerView;
 
+import com.android.settingslib.Utils;
 import com.android.systemui.bluetooth.qsdialog.DeviceItem;
 import com.android.systemui.res.R;
 
@@ -105,6 +106,7 @@
         private final TextView mNameView;
         private final TextView mSummaryView;
         private final ImageView mIconView;
+        private final ImageView mGearIcon;
         private final View mGearView;
 
         DeviceItemViewHolder(@NonNull View itemView, Context context) {
@@ -114,6 +116,7 @@
             mNameView = itemView.requireViewById(R.id.bluetooth_device_name);
             mSummaryView = itemView.requireViewById(R.id.bluetooth_device_summary);
             mIconView = itemView.requireViewById(R.id.bluetooth_device_icon);
+            mGearIcon = itemView.requireViewById(R.id.gear_icon_image);
             mGearView = itemView.requireViewById(R.id.gear_icon);
         }
 
@@ -124,13 +127,31 @@
             if (backgroundResId != null) {
                 mContainer.setBackground(mContext.getDrawable(item.getBackground()));
             }
-            mNameView.setText(item.getDeviceName());
-            mSummaryView.setText(item.getConnectionSummary());
+
+            // tint different color in different state for bad color contrast problem
+            int tintColor = item.isActive() ? Utils.getColorAttr(mContext,
+                    com.android.internal.R.attr.materialColorOnPrimaryContainer).getDefaultColor()
+                    : Utils.getColorAttr(mContext,
+                            com.android.internal.R.attr.materialColorOnSurface).getDefaultColor();
+
             Pair<Drawable, String> iconPair = item.getIconWithDescription();
             if (iconPair != null) {
-                mIconView.setImageDrawable(iconPair.getFirst());
+                Drawable drawable = iconPair.getFirst().mutate();
+                drawable.setTint(tintColor);
+                mIconView.setImageDrawable(drawable);
                 mIconView.setContentDescription(iconPair.getSecond());
             }
+
+            mNameView.setTextAppearance(
+                    item.isActive() ? R.style.BluetoothTileDialog_DeviceName_Active
+                            : R.style.BluetoothTileDialog_DeviceName);
+            mNameView.setText(item.getDeviceName());
+            mSummaryView.setTextAppearance(
+                    item.isActive() ? R.style.BluetoothTileDialog_DeviceSummary_Active
+                            : R.style.BluetoothTileDialog_DeviceSummary);
+            mSummaryView.setText(item.getConnectionSummary());
+
+            mGearIcon.getDrawable().mutate().setTint(tintColor);
             mGearView.setOnClickListener(view -> callback.onDeviceItemGearClicked(item, view));
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsController.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsController.java
index f81124e..aa95fd0 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsController.java
@@ -113,7 +113,7 @@
 
     @Override
     public void onPresetSelectionForGroupFailed(int hapGroupId, int reason) {
-        if (mActiveHearingDevice == null) {
+        if (mActiveHearingDevice == null || mHapClientProfile == null) {
             return;
         }
         if (hapGroupId == mHapClientProfile.getHapGroup(mActiveHearingDevice.getDevice())) {
@@ -137,7 +137,7 @@
 
     @Override
     public void onSetPresetNameForGroupFailed(int hapGroupId, int reason) {
-        if (mActiveHearingDevice == null) {
+        if (mActiveHearingDevice == null || mHapClientProfile == null) {
             return;
         }
         if (hapGroupId == mHapClientProfile.getHapGroup(mActiveHearingDevice.getDevice())) {
@@ -177,22 +177,33 @@
     }
 
     /**
-     * Sets the hearing device for this controller to control the preset.
+     * Sets the hearing device for this controller to control the preset if it supports
+     * {@link HapClientProfile}.
      *
      * @param activeHearingDevice the {@link CachedBluetoothDevice} need to be hearing aid device
+     *                            and support {@link HapClientProfile}.
      */
-    public void setActiveHearingDevice(CachedBluetoothDevice activeHearingDevice) {
-        mActiveHearingDevice = activeHearingDevice;
+    public void setHearingDeviceIfSupportHap(CachedBluetoothDevice activeHearingDevice) {
+        if (mHapClientProfile == null || activeHearingDevice == null) {
+            mActiveHearingDevice = null;
+            return;
+        }
+        if (activeHearingDevice.getProfiles().stream().anyMatch(
+                profile -> profile instanceof HapClientProfile)) {
+            mActiveHearingDevice = activeHearingDevice;
+        } else {
+            mActiveHearingDevice = null;
+        }
     }
 
     /**
      * Selects the currently active preset for {@code mActiveHearingDevice} individual device or
-     * the device group accoridng to whether it supports synchronized presets or not.
+     * the device group according to whether it supports synchronized presets or not.
      *
      * @param presetIndex an index of one of the available presets
      */
     public void selectPreset(int presetIndex) {
-        if (mActiveHearingDevice == null) {
+        if (mActiveHearingDevice == null || mHapClientProfile == null) {
             return;
         }
         mSelectedPresetIndex = presetIndex;
@@ -217,7 +228,7 @@
      * @return a list of all known preset info
      */
     public List<BluetoothHapPresetInfo> getAllPresetInfo() {
-        if (mActiveHearingDevice == null) {
+        if (mActiveHearingDevice == null || mHapClientProfile == null) {
             return emptyList();
         }
         return mHapClientProfile.getAllPresetInfo(mActiveHearingDevice.getDevice()).stream().filter(
@@ -230,14 +241,14 @@
      * @return active preset index
      */
     public int getActivePresetIndex() {
-        if (mActiveHearingDevice == null) {
+        if (mActiveHearingDevice == null || mHapClientProfile == null) {
             return BluetoothHapClient.PRESET_INDEX_UNAVAILABLE;
         }
         return mHapClientProfile.getActivePresetIndex(mActiveHearingDevice.getDevice());
     }
 
     private void selectPresetSynchronously(int groupId, int presetIndex) {
-        if (mActiveHearingDevice == null) {
+        if (mActiveHearingDevice == null || mHapClientProfile == null) {
             return;
         }
         if (DEBUG) {
@@ -250,7 +261,7 @@
     }
 
     private void selectPresetIndependently(int presetIndex) {
-        if (mActiveHearingDevice == null) {
+        if (mActiveHearingDevice == null || mHapClientProfile == null) {
             return;
         }
         if (DEBUG) {
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 d5790a4..a093f58 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt
@@ -118,7 +118,8 @@
                         if (Flags.dreamOverlayBouncerSwipeDirectionFiltering()) {
                             (abs(distanceY.toDouble()) > abs(distanceX.toDouble()) &&
                                 distanceY > 0) &&
-                                if (Flags.hubmodeFullscreenVerticalSwipe()) touchAvailable else true
+                                if (Flags.hubmodeFullscreenVerticalSwipeFix()) touchAvailable
+                                else true
                         } else {
                             // If the user scrolling favors a vertical direction, begin capturing
                             // scrolls.
@@ -175,7 +176,7 @@
         }
 
     init {
-        if (Flags.hubmodeFullscreenVerticalSwipe()) {
+        if (Flags.hubmodeFullscreenVerticalSwipeFix()) {
             scope.launch {
                 communalViewModel.glanceableTouchAvailable.collect {
                     onGlanceableTouchAvailable(it)
@@ -218,7 +219,7 @@
         val normalRegion =
             Rect(0, Math.round(height * (1 - bouncerZoneScreenPercentage)), width, height)
 
-        if (Flags.hubmodeFullscreenVerticalSwipe()) {
+        if (Flags.hubmodeFullscreenVerticalSwipeFix()) {
             region.op(bounds, Region.Op.UNION)
             exclusionRect?.apply { region.op(this, Region.Op.DIFFERENCE) }
         }
@@ -265,7 +266,7 @@
         when (motionEvent.action) {
             MotionEvent.ACTION_CANCEL,
             MotionEvent.ACTION_UP -> {
-                if (Flags.hubmodeFullscreenVerticalSwipe() && capture == true) {
+                if (Flags.hubmodeFullscreenVerticalSwipeFix() && capture == true) {
                     communalViewModel.onResetTouchState()
                 }
                 touchSession?.apply { pop() }
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.kt b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.kt
index 06b41de..9da9a3a 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.kt
@@ -61,7 +61,7 @@
     private var touchAvailable = false
 
     init {
-        if (Flags.hubmodeFullscreenVerticalSwipe()) {
+        if (Flags.hubmodeFullscreenVerticalSwipeFix()) {
             scope.launch {
                 communalViewModel.glanceableTouchAvailable.collect {
                     onGlanceableTouchAvailable(it)
@@ -107,7 +107,8 @@
                         capture =
                             abs(distanceY.toDouble()) > abs(distanceX.toDouble()) &&
                                 distanceY < 0 &&
-                                if (Flags.hubmodeFullscreenVerticalSwipe()) touchAvailable else true
+                                if (Flags.hubmodeFullscreenVerticalSwipeFix()) touchAvailable
+                                else true
                         if (capture == true) {
                             // Send the initial touches over, as the input listener has already
                             // processed these touches.
@@ -144,7 +145,7 @@
 
     override fun getTouchInitiationRegion(bounds: Rect, region: Region, exclusionRect: Rect?) {
         // If fullscreen swipe, use entire space minus exclusion region
-        if (Flags.hubmodeFullscreenVerticalSwipe()) {
+        if (Flags.hubmodeFullscreenVerticalSwipeFix()) {
             region.op(bounds, Region.Op.UNION)
 
             exclusionRect?.apply { region.op(this, Region.Op.DIFFERENCE) }
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..1be6f9e 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
@@ -581,6 +581,10 @@
             mBoundsFlow.cancel(new CancellationException());
         }
 
+        for (TouchHandler handler : mHandlers) {
+            handler.onDestroy();
+        }
+
         mInitialized = false;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 9521be1..723587e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -17,11 +17,9 @@
 package com.android.systemui.biometrics;
 
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
-import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON;
 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.constraintBp;
 
 import android.animation.Animator;
 import android.annotation.IntDef;
@@ -30,8 +28,6 @@
 import android.app.AlertDialog;
 import android.content.Context;
 import android.content.res.Configuration;
-import android.content.res.TypedArray;
-import android.graphics.Color;
 import android.graphics.PixelFormat;
 import android.hardware.biometrics.BiometricAuthenticator.Modality;
 import android.hardware.biometrics.BiometricConstants;
@@ -41,17 +37,11 @@
 import android.hardware.face.FaceSensorPropertiesInternal;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.os.Binder;
-import android.os.Handler;
 import android.os.IBinder;
-import android.os.Looper;
 import android.os.UserManager;
 import android.util.Log;
-import android.view.Display;
-import android.view.DisplayInfo;
-import android.view.Gravity;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
-import android.view.Surface;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowInsets;
@@ -60,7 +50,6 @@
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
-import android.widget.ScrollView;
 import android.window.OnBackInvokedCallback;
 import android.window.OnBackInvokedDispatcher;
 
@@ -74,7 +63,6 @@
 import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor;
 import com.android.systemui.biometrics.shared.model.BiometricModalities;
 import com.android.systemui.biometrics.shared.model.PromptKind;
-import com.android.systemui.biometrics.ui.BiometricPromptLayout;
 import com.android.systemui.biometrics.ui.CredentialView;
 import com.android.systemui.biometrics.ui.binder.BiometricViewBinder;
 import com.android.systemui.biometrics.ui.binder.BiometricViewSizeBinder;
@@ -111,7 +99,6 @@
 
     private static final int ANIMATION_DURATION_SHOW_MS = 250;
     private static final int ANIMATION_DURATION_AWAY_MS = 350;
-    private static final int ANIMATE_CREDENTIAL_START_DELAY_MS = 300;
 
     private static final int STATE_UNKNOWN = 0;
     private static final int STATE_ANIMATING_IN = 1;
@@ -136,13 +123,11 @@
 
     private final Config mConfig;
     private final int mEffectiveUserId;
-    private final Handler mHandler;
     private final IBinder mWindowToken = new Binder();
     private final WindowManager mWindowManager;
     private final Interpolator mLinearOutSlowIn;
     private final LockPatternUtils mLockPatternUtils;
     private final WakefulnessLifecycle mWakefulnessLifecycle;
-    private final AuthDialogPanelInteractionDetector mPanelInteractionDetector;
     private final InteractionJankMonitor mInteractionJankMonitor;
     private final CoroutineScope mApplicationCoroutineScope;
 
@@ -159,10 +144,7 @@
     private final AuthPanelController mPanelController;
     private final ViewGroup mLayout;
     private final ImageView mBackgroundView;
-    private final ScrollView mBiometricScrollView;
     private final View mPanelView;
-    private final List<FingerprintSensorPropertiesInternal> mFpProps;
-    private final List<FaceSensorPropertiesInternal> mFaceProps;
     private final float mTranslationY;
     @VisibleForTesting @ContainerState int mContainerState = STATE_UNKNOWN;
     private final Set<Integer> mFailedModalities = new HashSet<Integer>();
@@ -229,13 +211,7 @@
         @Override
         public void onUseDeviceCredential() {
             mConfig.mCallback.onDeviceCredentialPressed(getRequestId());
-            if (constraintBp()) {
-                addCredentialView(false /* animatePanel */, true /* animateContents */);
-            } else {
-                mHandler.postDelayed(() -> {
-                    addCredentialView(false /* animatePanel */, true /* animateContents */);
-                }, mConfig.mSkipAnimation ? 0 : ANIMATE_CREDENTIAL_START_DELAY_MS);
-            }
+            addCredentialView(false /* animatePanel */, true /* animateContents */);
 
             // TODO(b/313469218): Remove Config
             mConfig.mPromptInfo.setAuthenticators(Authenticators.DEVICE_CREDENTIAL);
@@ -303,36 +279,12 @@
             @Nullable List<FingerprintSensorPropertiesInternal> fpProps,
             @Nullable List<FaceSensorPropertiesInternal> faceProps,
             @NonNull WakefulnessLifecycle wakefulnessLifecycle,
-            @NonNull AuthDialogPanelInteractionDetector panelInteractionDetector,
-            @NonNull UserManager userManager,
-            @NonNull LockPatternUtils lockPatternUtils,
-            @NonNull InteractionJankMonitor jankMonitor,
-            @NonNull Provider<PromptSelectorInteractor> promptSelectorInteractor,
-            @NonNull PromptViewModel promptViewModel,
-            @NonNull Provider<CredentialViewModel> credentialViewModelProvider,
-            @NonNull @Background DelayableExecutor bgExecutor,
-            @NonNull VibratorHelper vibratorHelper) {
-        this(config, applicationCoroutineScope, fpProps, faceProps,
-                wakefulnessLifecycle, panelInteractionDetector, userManager, lockPatternUtils,
-                jankMonitor, promptSelectorInteractor, promptViewModel,
-                credentialViewModelProvider, new Handler(Looper.getMainLooper()), bgExecutor,
-                vibratorHelper);
-    }
-
-    @VisibleForTesting
-    AuthContainerView(@NonNull Config config,
-            @NonNull CoroutineScope applicationCoroutineScope,
-            @Nullable List<FingerprintSensorPropertiesInternal> fpProps,
-            @Nullable List<FaceSensorPropertiesInternal> faceProps,
-            @NonNull WakefulnessLifecycle wakefulnessLifecycle,
-            @NonNull AuthDialogPanelInteractionDetector panelInteractionDetector,
             @NonNull UserManager userManager,
             @NonNull LockPatternUtils lockPatternUtils,
             @NonNull InteractionJankMonitor jankMonitor,
             @NonNull Provider<PromptSelectorInteractor> promptSelectorInteractorProvider,
             @NonNull PromptViewModel promptViewModel,
             @NonNull Provider<CredentialViewModel> credentialViewModelProvider,
-            @NonNull Handler mainHandler,
             @NonNull @Background DelayableExecutor bgExecutor,
             @NonNull VibratorHelper vibratorHelper) {
         super(config.mContext);
@@ -340,10 +292,8 @@
         mConfig = config;
         mLockPatternUtils = lockPatternUtils;
         mEffectiveUserId = userManager.getCredentialOwnerProfile(mConfig.mUserId);
-        mHandler = mainHandler;
         mWindowManager = mContext.getSystemService(WindowManager.class);
         mWakefulnessLifecycle = wakefulnessLifecycle;
-        mPanelInteractionDetector = panelInteractionDetector;
         mApplicationCoroutineScope = applicationCoroutineScope;
 
         mPromptViewModel = promptViewModel;
@@ -352,8 +302,6 @@
         mLinearOutSlowIn = Interpolators.LINEAR_OUT_SLOW_IN;
         mBiometricCallback = new BiometricCallback();
 
-        mFpProps = fpProps;
-        mFaceProps = faceProps;
         final BiometricModalities biometricModalities = new BiometricModalities(
                 Utils.findFirstSensorProperties(fpProps, mConfig.mSensorIds),
                 Utils.findFirstSensorProperties(faceProps, mConfig.mSensorIds));
@@ -367,7 +315,7 @@
 
         final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
         final PromptKind kind = mPromptViewModel.getPromptKind().getValue();
-        if (constraintBp() && kind.isBiometric()) {
+        if (kind.isBiometric()) {
             if (kind.isTwoPaneLandscapeBiometric()) {
                 mLayout = (ConstraintLayout) layoutInflater.inflate(
                         R.layout.biometric_prompt_two_pane_layout, this, false /* attachToRoot */);
@@ -379,26 +327,16 @@
             mLayout = (FrameLayout) layoutInflater.inflate(
                     R.layout.auth_container_view, this, false /* attachToRoot */);
         }
-        mBiometricScrollView = mLayout.findViewById(R.id.biometric_scrollview);
         addView(mLayout);
         mBackgroundView = mLayout.findViewById(R.id.background);
 
         mPanelView = mLayout.findViewById(R.id.panel);
-        if (!constraintBp()) {
-            final TypedArray ta = mContext.obtainStyledAttributes(new int[]{
-                    android.R.attr.colorBackgroundFloating});
-            mPanelView.setBackgroundColor(ta.getColor(0, Color.WHITE));
-            ta.recycle();
-        }
         mPanelController = new AuthPanelController(mContext, mPanelView);
         mBackgroundExecutor = bgExecutor;
         mInteractionJankMonitor = jankMonitor;
         mCredentialViewModelProvider = credentialViewModelProvider;
 
-        showPrompt(config, layoutInflater, promptViewModel,
-                Utils.findFirstSensorProperties(fpProps, mConfig.mSensorIds),
-                Utils.findFirstSensorProperties(faceProps, mConfig.mSensorIds),
-                vibratorHelper);
+        showPrompt(promptViewModel, vibratorHelper);
 
         // TODO: De-dupe the logic with AuthCredentialPasswordView
         setOnKeyListener((v, keyCode, event) -> {
@@ -415,52 +353,25 @@
         requestFocus();
     }
 
-    private void showPrompt(@NonNull Config config, @NonNull LayoutInflater layoutInflater,
-            @NonNull PromptViewModel viewModel,
-            @Nullable FingerprintSensorPropertiesInternal fpProps,
-            @Nullable FaceSensorPropertiesInternal faceProps,
-            @NonNull VibratorHelper vibratorHelper
-    ) {
+    private void showPrompt(@NonNull PromptViewModel viewModel,
+            @NonNull VibratorHelper vibratorHelper) {
         if (mPromptViewModel.getPromptKind().getValue().isBiometric()) {
-            addBiometricView(config, layoutInflater, viewModel, fpProps, faceProps, vibratorHelper);
+            addBiometricView(viewModel, vibratorHelper);
         } else if (mPromptViewModel.getPromptKind().getValue().isCredential()) {
-            if (constraintBp()) {
-                addCredentialView(true, false);
-            }
+            addCredentialView(true, false);
         } else {
             mPromptSelectorInteractorProvider.get().resetPrompt(getRequestId());
         }
     }
 
-    private void addBiometricView(@NonNull Config config, @NonNull LayoutInflater layoutInflater,
-            @NonNull PromptViewModel viewModel,
-            @Nullable FingerprintSensorPropertiesInternal fpProps,
-            @Nullable FaceSensorPropertiesInternal faceProps,
+    private void addBiometricView(@NonNull PromptViewModel viewModel,
             @NonNull VibratorHelper vibratorHelper) {
-
-        if (constraintBp()) {
-            mBiometricView = BiometricViewBinder.bind(mLayout, viewModel, null,
-                    // TODO(b/201510778): This uses the wrong timeout in some cases
-                    getJankListener(mLayout, TRANSIT,
-                            BiometricViewSizeBinder.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS),
-                    mBackgroundView, mBiometricCallback, mApplicationCoroutineScope,
-                    vibratorHelper);
-        } else {
-            final BiometricPromptLayout view = (BiometricPromptLayout) layoutInflater.inflate(
-                    R.layout.biometric_prompt_layout, null, false);
-            mBiometricView = BiometricViewBinder.bind(view, viewModel, mPanelController,
-                    // TODO(b/201510778): This uses the wrong timeout in some cases
-                    getJankListener(view, TRANSIT,
-                            BiometricViewSizeBinder.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS),
-                    mBackgroundView, mBiometricCallback, mApplicationCoroutineScope,
-                    vibratorHelper);
-
-            // TODO(b/251476085): migrate these dependencies
-            if (fpProps != null && fpProps.isAnyUdfpsType()) {
-                view.setUdfpsAdapter(new UdfpsDialogMeasureAdapter(view, fpProps),
-                        config.mScaleProvider);
-            }
-        }
+        mBiometricView = BiometricViewBinder.bind(mLayout, viewModel,
+                // TODO(b/201510778): This uses the wrong timeout in some cases
+                getJankListener(mLayout, TRANSIT,
+                        BiometricViewSizeBinder.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS),
+                mBackgroundView, mBiometricCallback, mApplicationCoroutineScope,
+                vibratorHelper);
     }
 
     @VisibleForTesting
@@ -524,9 +435,6 @@
 
     @Override
     public void onOrientationChanged() {
-        if (!constraintBp()) {
-            updatePositionByCapability(true /* invalidate */);
-        }
     }
 
     @Override
@@ -538,23 +446,6 @@
         }
 
         mWakefulnessLifecycle.addObserver(this);
-        if (constraintBp()) {
-            // Do nothing on attachment with constraintLayout
-        } else if (mPromptViewModel.getPromptKind().getValue().isBiometric()) {
-            mBiometricScrollView.addView(mBiometricView.asView());
-        } else if (mPromptViewModel.getPromptKind().getValue().isCredential()) {
-            addCredentialView(true /* animatePanel */, false /* animateContents */);
-        } else {
-            throw new IllegalStateException("Unknown configuration: "
-                    + mConfig.mPromptInfo.getAuthenticators());
-        }
-
-        if (!constraintBp()) {
-            mPanelInteractionDetector.enable(
-                    () -> animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED));
-            updatePositionByCapability(false /* invalidate */);
-        }
-
         if (mConfig.mSkipIntro) {
             mContainerState = STATE_SHOWING;
         } else {
@@ -618,120 +509,8 @@
         };
     }
 
-    private void updatePositionByCapability(boolean forceInvalidate) {
-        final FingerprintSensorPropertiesInternal fpProp = Utils.findFirstSensorProperties(
-                mFpProps, mConfig.mSensorIds);
-        final FaceSensorPropertiesInternal faceProp = Utils.findFirstSensorProperties(
-                mFaceProps, mConfig.mSensorIds);
-        if (fpProp != null && fpProp.isAnyUdfpsType()) {
-            maybeUpdatePositionForUdfps(forceInvalidate /* invalidate */);
-        }
-        if (faceProp != null && mBiometricView != null && mBiometricView.isFaceOnly()) {
-            alwaysUpdatePositionAtScreenBottom(forceInvalidate /* invalidate */);
-        }
-        if (fpProp != null && fpProp.sensorType == TYPE_POWER_BUTTON) {
-            alwaysUpdatePositionAtScreenBottom(forceInvalidate /* invalidate */);
-        }
-    }
-
-    private static boolean shouldUpdatePositionForUdfps(@NonNull View view) {
-        if (view instanceof BiometricPromptLayout) {
-            // this will force the prompt to align itself on the edge of the screen
-            // instead of centering (temporary workaround to prevent small implicit view
-            // from breaking due to the way gravity / margins are set in the legacy
-            // AuthPanelController
-            return true;
-        }
-
-        return false;
-    }
-
-    private boolean maybeUpdatePositionForUdfps(boolean invalidate) {
-        final Display display = getDisplay();
-        if (display == null) {
-            return false;
-        }
-
-        final DisplayInfo cachedDisplayInfo = new DisplayInfo();
-        display.getDisplayInfo(cachedDisplayInfo);
-        if (mBiometricView == null || !shouldUpdatePositionForUdfps(mBiometricView.asView())) {
-            return false;
-        }
-
-        final int displayRotation = cachedDisplayInfo.rotation;
-        switch (displayRotation) {
-            case Surface.ROTATION_0:
-                mPanelController.setPosition(AuthPanelController.POSITION_BOTTOM);
-                setScrollViewGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
-                break;
-
-            case Surface.ROTATION_90:
-                mPanelController.setPosition(AuthPanelController.POSITION_RIGHT);
-                setScrollViewGravity(Gravity.CENTER_VERTICAL | Gravity.RIGHT);
-                break;
-
-            case Surface.ROTATION_270:
-                mPanelController.setPosition(AuthPanelController.POSITION_LEFT);
-                setScrollViewGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT);
-                break;
-
-            case Surface.ROTATION_180:
-            default:
-                Log.e(TAG, "Unsupported display rotation: " + displayRotation);
-                mPanelController.setPosition(AuthPanelController.POSITION_BOTTOM);
-                setScrollViewGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
-                break;
-        }
-
-        if (invalidate) {
-            mPanelView.invalidateOutline();
-        }
-
-        return true;
-    }
-
-    private boolean alwaysUpdatePositionAtScreenBottom(boolean invalidate) {
-        final Display display = getDisplay();
-        if (display == null) {
-            return false;
-        }
-        if (mBiometricView == null || !shouldUpdatePositionForUdfps(mBiometricView.asView())) {
-            return false;
-        }
-
-        final int displayRotation = display.getRotation();
-        switch (displayRotation) {
-            case Surface.ROTATION_0:
-            case Surface.ROTATION_90:
-            case Surface.ROTATION_270:
-            case Surface.ROTATION_180:
-                mPanelController.setPosition(AuthPanelController.POSITION_BOTTOM);
-                setScrollViewGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
-                break;
-            default:
-                Log.e(TAG, "Unsupported display rotation: " + displayRotation);
-                mPanelController.setPosition(AuthPanelController.POSITION_BOTTOM);
-                setScrollViewGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
-                break;
-        }
-
-        if (invalidate) {
-            mPanelView.invalidateOutline();
-        }
-
-        return true;
-    }
-
-    private void setScrollViewGravity(int gravity) {
-        final FrameLayout.LayoutParams params =
-                (FrameLayout.LayoutParams) mBiometricScrollView.getLayoutParams();
-        params.gravity = gravity;
-        mBiometricScrollView.setLayoutParams(params);
-    }
-
     @Override
     public void onDetachedFromWindow() {
-        mPanelInteractionDetector.disable();
         OnBackInvokedDispatcher dispatcher = findOnBackInvokedDispatcher();
         if (dispatcher != null) {
             findOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mBackCallback);
@@ -834,6 +613,11 @@
     }
 
     @Override
+    public String getClassNameIfItIsConfirmDeviceCredentialActivity() {
+        return  mConfig.mPromptInfo.getClassNameIfItIsConfirmDeviceCredentialActivity();
+    }
+
+    @Override
     public long getRequestId() {
         return mConfig.mRequestId;
     }
@@ -878,7 +662,7 @@
 
         final Runnable endActionRunnable = () -> {
             setVisibility(View.INVISIBLE);
-            if (Flags.customBiometricPrompt() && constraintBp()) {
+            if (Flags.customBiometricPrompt()) {
                 // TODO(b/288175645): resetPrompt calls should be lifecycle aware
                 mPromptSelectorInteractorProvider.get().resetPrompt(getRequestId());
             }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index b466f31..037f5b7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -22,7 +22,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.app.TaskStackListener;
 import android.content.BroadcastReceiver;
@@ -173,7 +172,6 @@
     @NonNull private final SparseBooleanArray mSfpsEnrolledForUser;
     @NonNull private final SensorPrivacyManager mSensorPrivacyManager;
     private final WakefulnessLifecycle mWakefulnessLifecycle;
-    private final AuthDialogPanelInteractionDetector mPanelInteractionDetector;
     private boolean mAllFingerprintAuthenticatorsRegistered;
     @NonNull private final UserManager mUserManager;
     @NonNull private final LockPatternUtils mLockPatternUtils;
@@ -187,7 +185,7 @@
     final TaskStackListener mTaskStackListener = new TaskStackListener() {
         @Override
         public void onTaskStackChanged() {
-            if (!isOwnerInForeground()) {
+            if (isOwnerInBackground()) {
                 mHandler.post(AuthController.this::cancelIfOwnerIsNotInForeground);
             }
         }
@@ -227,21 +225,20 @@
         }
     }
 
-    private boolean isOwnerInForeground() {
+    private boolean isOwnerInBackground() {
         if (mCurrentDialog != null) {
             final String clientPackage = mCurrentDialog.getOpPackageName();
-            final List<ActivityManager.RunningTaskInfo> runningTasks =
-                    mActivityTaskManager.getTasks(1);
-            if (!runningTasks.isEmpty()) {
-                final String topPackage = runningTasks.get(0).topActivity.getPackageName();
-                if (!topPackage.contentEquals(clientPackage)
-                        && !Utils.isSystem(mContext, clientPackage)) {
-                    Log.w(TAG, "Evicting client due to: " + topPackage);
-                    return false;
-                }
+            final String clientClassNameIfItIsConfirmDeviceCredentialActivity =
+                    mCurrentDialog.getClassNameIfItIsConfirmDeviceCredentialActivity();
+            final boolean isInBackground = Utils.isSystemAppOrInBackground(mActivityTaskManager,
+                    mContext, clientPackage,
+                    clientClassNameIfItIsConfirmDeviceCredentialActivity);
+            if (isInBackground) {
+                Log.w(TAG, "Evicting client due to top activity is not : " + clientPackage);
             }
+            return isInBackground;
         }
-        return true;
+        return false;
     }
 
     private void cancelIfOwnerIsNotInForeground() {
@@ -728,7 +725,6 @@
             Provider<UdfpsController> udfpsControllerFactory,
             @NonNull DisplayManager displayManager,
             @NonNull WakefulnessLifecycle wakefulnessLifecycle,
-            @NonNull AuthDialogPanelInteractionDetector panelInteractionDetector,
             @NonNull UserManager userManager,
             @NonNull LockPatternUtils lockPatternUtils,
             @NonNull Lazy<UdfpsLogger> udfpsLogger,
@@ -779,7 +775,6 @@
                 });
 
         mWakefulnessLifecycle = wakefulnessLifecycle;
-        mPanelInteractionDetector = panelInteractionDetector;
 
         mFaceProps = mFaceManager != null ? mFaceManager.getSensorPropertiesInternal() : null;
 
@@ -1229,7 +1224,6 @@
                 operationId,
                 requestId,
                 mWakefulnessLifecycle,
-                mPanelInteractionDetector,
                 mUserManager,
                 mLockPatternUtils,
                 viewModel);
@@ -1259,9 +1253,9 @@
         }
         mCurrentDialog = newDialog;
 
-        // TODO(b/339532378): We should check whether |allowBackgroundAuthentication| should be
+        // TODO(b/353597496): We should check whether |allowBackgroundAuthentication| should be
         //  removed.
-        if (!promptInfo.isAllowBackgroundAuthentication() && !isOwnerInForeground()) {
+        if (!promptInfo.isAllowBackgroundAuthentication() && isOwnerInBackground()) {
             cancelIfOwnerIsNotInForeground();
         } else {
             mCurrentDialog.show(mWindowManager);
@@ -1306,7 +1300,6 @@
             PromptInfo promptInfo, boolean requireConfirmation, int userId, int[] sensorIds,
             String opPackageName, boolean skipIntro, long operationId, long requestId,
             @NonNull WakefulnessLifecycle wakefulnessLifecycle,
-            @NonNull AuthDialogPanelInteractionDetector panelInteractionDetector,
             @NonNull UserManager userManager,
             @NonNull LockPatternUtils lockPatternUtils,
             @NonNull PromptViewModel viewModel) {
@@ -1323,7 +1316,7 @@
         config.mSensorIds = sensorIds;
         config.mScaleProvider = this::getScaleFactor;
         return new AuthContainerView(config, mApplicationCoroutineScope, mFpProps, mFaceProps,
-                wakefulnessLifecycle, panelInteractionDetector, userManager, lockPatternUtils,
+                wakefulnessLifecycle, userManager, lockPatternUtils,
                 mInteractionJankMonitor, mPromptSelectorInteractor, viewModel,
                 mCredentialViewModelProvider, bgExecutor, mVibratorHelper);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
index 3fd488c..8611916 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
@@ -94,6 +94,12 @@
      */
     String getOpPackageName();
 
+    /**
+     * Get the class name of ConfirmDeviceCredentialActivity. Returns null if the direct caller is
+     * not ConfirmDeviceCredentialActivity.
+     */
+    String getClassNameIfItIsConfirmDeviceCredentialActivity();
+
     /** The requestId of the underlying operation within the framework. */
     long getRequestId();
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
deleted file mode 100644
index 04c2351..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
+++ /dev/null
@@ -1,68 +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.biometrics
-
-import android.annotation.MainThread
-import android.util.Log
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.shade.domain.interactor.ShadeInteractor
-import dagger.Lazy
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.launch
-
-class AuthDialogPanelInteractionDetector
-@Inject
-constructor(
-    @Application private val scope: CoroutineScope,
-    private val shadeInteractorLazy: Lazy<ShadeInteractor>,
-) {
-    private var shadeExpansionCollectorJob: Job? = null
-
-    @MainThread
-    fun enable(onShadeInteraction: Runnable) {
-        if (shadeExpansionCollectorJob != null) {
-            Log.e(TAG, "Already enabled")
-            return
-        }
-        //TODO(b/313957306) delete this check
-        if (shadeInteractorLazy.get().isUserInteracting.value) {
-            // Workaround for b/311266890. This flow is in an error state that breaks this.
-            Log.e(TAG, "isUserInteracting already true, skipping enable")
-            return
-        }
-        shadeExpansionCollectorJob =
-            scope.launch {
-                Log.i(TAG, "Enable detector")
-                // wait for it to emit true once
-                shadeInteractorLazy.get().isUserInteracting.first { it }
-                Log.i(TAG, "Detector detected shade interaction")
-                onShadeInteraction.run()
-            }
-        shadeExpansionCollectorJob?.invokeOnCompletion { shadeExpansionCollectorJob = null }
-    }
-
-    @MainThread
-    fun disable() {
-        Log.i(TAG, "Disable detector")
-        shadeExpansionCollectorJob?.cancel()
-    }
-}
-
-private const val TAG = "AuthDialogPanelInteractionDetector"
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java
deleted file mode 100644
index 02eae9ced..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java
+++ /dev/null
@@ -1,386 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics;
-
-import android.annotation.IdRes;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.graphics.Insets;
-import android.graphics.Rect;
-import android.hardware.biometrics.SensorLocationInternal;
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
-import android.os.Build;
-import android.util.Log;
-import android.view.Surface;
-import android.view.View;
-import android.view.View.MeasureSpec;
-import android.view.ViewGroup;
-import android.view.WindowInsets;
-import android.view.WindowManager;
-import android.view.WindowMetrics;
-import android.widget.FrameLayout;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.res.R;
-
-/**
- * Adapter that remeasures an auth dialog view to ensure that it matches the location of a physical
- * under-display fingerprint sensor (UDFPS).
- */
-public class UdfpsDialogMeasureAdapter {
-    private static final String TAG = "UdfpsDialogMeasurementAdapter";
-    private static final boolean DEBUG = Build.IS_USERDEBUG || Build.IS_ENG;
-
-    @NonNull private final ViewGroup mView;
-    @NonNull private final FingerprintSensorPropertiesInternal mSensorProps;
-    @Nullable private WindowManager mWindowManager;
-    private int mBottomSpacerHeight;
-
-    public UdfpsDialogMeasureAdapter(
-            @NonNull ViewGroup view, @NonNull FingerprintSensorPropertiesInternal sensorProps) {
-        mView = view;
-        mSensorProps = sensorProps;
-        mWindowManager = mView.getContext().getSystemService(WindowManager.class);
-    }
-
-    @NonNull
-    FingerprintSensorPropertiesInternal getSensorProps() {
-        return mSensorProps;
-    }
-
-    @NonNull
-    public AuthDialog.LayoutParams onMeasureInternal(
-            int width, int height, @NonNull AuthDialog.LayoutParams layoutParams,
-            float scaleFactor) {
-
-        final int displayRotation = mView.getDisplay().getRotation();
-        switch (displayRotation) {
-            case Surface.ROTATION_0:
-                return onMeasureInternalPortrait(width, height, scaleFactor);
-            case Surface.ROTATION_90:
-            case Surface.ROTATION_270:
-                return onMeasureInternalLandscape(width, height, scaleFactor);
-            default:
-                Log.e(TAG, "Unsupported display rotation: " + displayRotation);
-                return layoutParams;
-        }
-    }
-
-    /**
-     * @return the actual (and possibly negative) bottom spacer height. If negative, this indicates
-     * that the UDFPS sensor is too low. Our current xml and custom measurement logic is very hard
-     * too cleanly support this case. So, let's have the onLayout code translate the sensor location
-     * instead.
-     */
-    public int getBottomSpacerHeight() {
-        return mBottomSpacerHeight;
-    }
-
-    /**
-     * @return sensor diameter size as scaleFactor
-     */
-    public int getSensorDiameter(float scaleFactor) {
-        return (int) (scaleFactor * mSensorProps.getLocation().sensorRadius * 2);
-    }
-
-    @NonNull
-    private AuthDialog.LayoutParams onMeasureInternalPortrait(int width, int height,
-            float scaleFactor) {
-        final WindowMetrics windowMetrics = mWindowManager.getMaximumWindowMetrics();
-
-        // Figure out where the bottom of the sensor anim should be.
-        final int textIndicatorHeight = getViewHeightPx(R.id.indicator);
-        final int buttonBarHeight = getViewHeightPx(R.id.button_bar);
-        final int dialogMargin = getDialogMarginPx();
-        final int displayHeight = getMaximumWindowBounds(windowMetrics).height();
-        final Insets navbarInsets = getNavbarInsets(windowMetrics);
-        mBottomSpacerHeight = calculateBottomSpacerHeightForPortrait(
-                mSensorProps, displayHeight, textIndicatorHeight, buttonBarHeight,
-                dialogMargin, navbarInsets.bottom, scaleFactor);
-
-        // Go through each of the children and do the custom measurement.
-        int totalHeight = 0;
-        final int numChildren = mView.getChildCount();
-        final int sensorDiameter = getSensorDiameter(scaleFactor);
-        for (int i = 0; i < numChildren; i++) {
-            final View child = mView.getChildAt(i);
-            if (child.getId() == R.id.biometric_icon_frame) {
-                final FrameLayout iconFrame = (FrameLayout) child;
-                final View icon = iconFrame.getChildAt(0);
-                // Create a frame that's exactly the height of the sensor circle.
-                iconFrame.measure(
-                        MeasureSpec.makeMeasureSpec(
-                                child.getLayoutParams().width, MeasureSpec.EXACTLY),
-                        MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.EXACTLY));
-
-                // Ensure that the icon is never larger than the sensor.
-                icon.measure(
-                        MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.AT_MOST),
-                        MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.AT_MOST));
-            } else if (child.getId() == R.id.space_above_icon
-                    || child.getId() == R.id.space_above_content
-                    || child.getId() == R.id.button_bar) {
-                child.measure(
-                        MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
-                        MeasureSpec.makeMeasureSpec(
-                                child.getLayoutParams().height, MeasureSpec.EXACTLY));
-            } else if (child.getId() == R.id.space_below_icon) {
-                // Set the spacer height so the fingerprint icon is on the physical sensor area
-                final int clampedSpacerHeight = Math.max(mBottomSpacerHeight, 0);
-                child.measure(
-                        MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
-                        MeasureSpec.makeMeasureSpec(clampedSpacerHeight, MeasureSpec.EXACTLY));
-            } else if (child.getId() == R.id.description
-                    || child.getId() == R.id.customized_view_container) {
-                //skip description view and compute later
-                continue;
-            } else if (child.getId() == R.id.logo) {
-                child.measure(
-                        MeasureSpec.makeMeasureSpec(child.getLayoutParams().width,
-                                MeasureSpec.EXACTLY),
-                        MeasureSpec.makeMeasureSpec(child.getLayoutParams().height,
-                                MeasureSpec.EXACTLY));
-            } else {
-                child.measure(
-                        MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
-                        MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
-            }
-
-            if (child.getVisibility() != View.GONE) {
-                totalHeight += child.getMeasuredHeight();
-            }
-        }
-
-        //re-calculate the height of body content
-        View description = mView.findViewById(R.id.description);
-        View contentView = mView.findViewById(R.id.customized_view_container);
-        if (description != null && description.getVisibility() != View.GONE) {
-            totalHeight += measureDescription(description, displayHeight, width, totalHeight);
-        } else if (contentView != null && contentView.getVisibility() != View.GONE) {
-            totalHeight += measureDescription(contentView, displayHeight, width, totalHeight);
-        }
-
-        return new AuthDialog.LayoutParams(width, totalHeight);
-    }
-
-    private int measureDescription(View bodyContent, int displayHeight, int currWidth,
-                                   int currHeight) {
-        int newHeight = bodyContent.getMeasuredHeight() + currHeight;
-        int limit = (int) (displayHeight * 0.75);
-        if (newHeight > limit) {
-            bodyContent.measure(
-                    MeasureSpec.makeMeasureSpec(currWidth, MeasureSpec.EXACTLY),
-                    MeasureSpec.makeMeasureSpec(limit - currHeight, MeasureSpec.EXACTLY));
-        }
-        return bodyContent.getMeasuredHeight();
-    }
-
-    @NonNull
-    private AuthDialog.LayoutParams onMeasureInternalLandscape(int width, int height,
-            float scaleFactor) {
-        final WindowMetrics windowMetrics = mWindowManager.getMaximumWindowMetrics();
-
-        // Find the spacer height needed to vertically align the icon with the sensor.
-        final int titleHeight = getViewHeightPx(R.id.title);
-        final int subtitleHeight = getViewHeightPx(R.id.subtitle);
-        final int descriptionHeight = getViewHeightPx(R.id.description);
-        final int topSpacerHeight = getViewHeightPx(R.id.space_above_icon);
-        final int textIndicatorHeight = getViewHeightPx(R.id.indicator);
-        final int buttonBarHeight = getViewHeightPx(R.id.button_bar);
-
-        final Insets navbarInsets = getNavbarInsets(windowMetrics);
-        final int bottomSpacerHeight = calculateBottomSpacerHeightForLandscape(titleHeight,
-                subtitleHeight, descriptionHeight, topSpacerHeight, textIndicatorHeight,
-                buttonBarHeight, navbarInsets.bottom);
-
-        // Find the spacer width needed to horizontally align the icon with the sensor.
-        final int displayWidth = getMaximumWindowBounds(windowMetrics).width();
-        final int dialogMargin = getDialogMarginPx();
-        final int horizontalInset = navbarInsets.left + navbarInsets.right;
-        final int horizontalSpacerWidth = calculateHorizontalSpacerWidthForLandscape(
-                mSensorProps, displayWidth, dialogMargin, horizontalInset, scaleFactor);
-
-        final int sensorDiameter = getSensorDiameter(scaleFactor);
-        final int remeasuredWidth = sensorDiameter + 2 * horizontalSpacerWidth;
-
-        int remeasuredHeight = 0;
-        final int numChildren = mView.getChildCount();
-        for (int i = 0; i < numChildren; i++) {
-            final View child = mView.getChildAt(i);
-            if (child.getId() == R.id.biometric_icon_frame) {
-                final FrameLayout iconFrame = (FrameLayout) child;
-                final View icon = iconFrame.getChildAt(0);
-                // Create a frame that's exactly the height of the sensor circle.
-                iconFrame.measure(
-                        MeasureSpec.makeMeasureSpec(remeasuredWidth, MeasureSpec.EXACTLY),
-                        MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.EXACTLY));
-
-                // Ensure that the icon is never larger than the sensor.
-                icon.measure(
-                        MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.AT_MOST),
-                        MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.AT_MOST));
-            } else if (child.getId() == R.id.space_above_icon) {
-                // Adjust the width and height of the top spacer if necessary.
-                final int newTopSpacerHeight = child.getLayoutParams().height
-                        - Math.min(bottomSpacerHeight, 0);
-                child.measure(
-                        MeasureSpec.makeMeasureSpec(remeasuredWidth, MeasureSpec.EXACTLY),
-                        MeasureSpec.makeMeasureSpec(newTopSpacerHeight, MeasureSpec.EXACTLY));
-            } else if (child.getId() == R.id.button_bar) {
-                // Adjust the width of the button bar while preserving its height.
-                child.measure(
-                        MeasureSpec.makeMeasureSpec(remeasuredWidth, MeasureSpec.EXACTLY),
-                        MeasureSpec.makeMeasureSpec(
-                                child.getLayoutParams().height, MeasureSpec.EXACTLY));
-            } else if (child.getId() == R.id.space_below_icon) {
-                // Adjust the bottom spacer height to align the fingerprint icon with the sensor.
-                final int newBottomSpacerHeight = Math.max(bottomSpacerHeight, 0);
-                child.measure(
-                        MeasureSpec.makeMeasureSpec(remeasuredWidth, MeasureSpec.EXACTLY),
-                        MeasureSpec.makeMeasureSpec(newBottomSpacerHeight, MeasureSpec.EXACTLY));
-            } else {
-                // Use the remeasured width for all other child views.
-                child.measure(
-                        MeasureSpec.makeMeasureSpec(remeasuredWidth, MeasureSpec.EXACTLY),
-                        MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
-            }
-
-            if (child.getVisibility() != View.GONE) {
-                remeasuredHeight += child.getMeasuredHeight();
-            }
-        }
-
-        return new AuthDialog.LayoutParams(remeasuredWidth, remeasuredHeight);
-    }
-
-    private int getViewHeightPx(@IdRes int viewId) {
-        final View view = mView.findViewById(viewId);
-        return view != null && view.getVisibility() != View.GONE ? view.getMeasuredHeight() : 0;
-    }
-
-    private int getDialogMarginPx() {
-        return mView.getResources().getDimensionPixelSize(R.dimen.biometric_dialog_border_padding);
-    }
-
-    @NonNull
-    private static Insets getNavbarInsets(@Nullable WindowMetrics windowMetrics) {
-        return windowMetrics != null
-                ? windowMetrics.getWindowInsets().getInsets(WindowInsets.Type.navigationBars())
-                : Insets.NONE;
-    }
-
-    @NonNull
-    private static Rect getMaximumWindowBounds(@Nullable WindowMetrics windowMetrics) {
-        return windowMetrics != null ? windowMetrics.getBounds() : new Rect();
-    }
-
-    /**
-     * For devices in portrait orientation where the sensor is too high up, calculates the amount of
-     * padding necessary to center the biometric icon within the sensor's physical location.
-     */
-    @VisibleForTesting
-    static int calculateBottomSpacerHeightForPortrait(
-            @NonNull FingerprintSensorPropertiesInternal sensorProperties, int displayHeightPx,
-            int textIndicatorHeightPx, int buttonBarHeightPx, int dialogMarginPx,
-            int navbarBottomInsetPx, float scaleFactor) {
-        final SensorLocationInternal location = sensorProperties.getLocation();
-        final int sensorDistanceFromBottom = displayHeightPx
-                - (int) (scaleFactor * location.sensorLocationY)
-                - (int) (scaleFactor * location.sensorRadius);
-
-        final int spacerHeight = sensorDistanceFromBottom
-                - textIndicatorHeightPx
-                - buttonBarHeightPx
-                - dialogMarginPx
-                - navbarBottomInsetPx;
-
-        if (DEBUG) {
-            Log.d(TAG, "Display height: " + displayHeightPx
-                    + ", Distance from bottom: " + sensorDistanceFromBottom
-                    + ", Bottom margin: " + dialogMarginPx
-                    + ", Navbar bottom inset: " + navbarBottomInsetPx
-                    + ", Bottom spacer height (portrait): " + spacerHeight
-                    + ", Scale Factor: " + scaleFactor);
-        }
-
-        return spacerHeight;
-    }
-
-    /**
-     * For devices in landscape orientation where the sensor is too high up, calculates the amount
-     * of padding necessary to center the biometric icon within the sensor's physical location.
-     */
-    @VisibleForTesting
-    static int calculateBottomSpacerHeightForLandscape(int titleHeightPx, int subtitleHeightPx,
-            int descriptionHeightPx, int topSpacerHeightPx, int textIndicatorHeightPx,
-            int buttonBarHeightPx, int navbarBottomInsetPx) {
-
-        final int dialogHeightAboveIcon = titleHeightPx
-                + subtitleHeightPx
-                + descriptionHeightPx
-                + topSpacerHeightPx;
-
-        final int dialogHeightBelowIcon = textIndicatorHeightPx + buttonBarHeightPx;
-
-        final int bottomSpacerHeight = dialogHeightAboveIcon
-                - dialogHeightBelowIcon
-                - navbarBottomInsetPx;
-
-        if (DEBUG) {
-            Log.d(TAG, "Title height: " + titleHeightPx
-                    + ", Subtitle height: " + subtitleHeightPx
-                    + ", Description height: " + descriptionHeightPx
-                    + ", Top spacer height: " + topSpacerHeightPx
-                    + ", Text indicator height: " + textIndicatorHeightPx
-                    + ", Button bar height: " + buttonBarHeightPx
-                    + ", Navbar bottom inset: " + navbarBottomInsetPx
-                    + ", Bottom spacer height (landscape): " + bottomSpacerHeight);
-        }
-
-        return bottomSpacerHeight;
-    }
-
-    /**
-     * For devices in landscape orientation where the sensor is too left/right, calculates the
-     * amount of padding necessary to center the biometric icon within the sensor's physical
-     * location.
-     */
-    @VisibleForTesting
-    static int calculateHorizontalSpacerWidthForLandscape(
-            @NonNull FingerprintSensorPropertiesInternal sensorProperties, int displayWidthPx,
-            int dialogMarginPx, int navbarHorizontalInsetPx, float scaleFactor) {
-        final SensorLocationInternal location = sensorProperties.getLocation();
-        final int sensorDistanceFromEdge = displayWidthPx
-                - (int) (scaleFactor * location.sensorLocationY)
-                - (int) (scaleFactor * location.sensorRadius);
-
-        final int horizontalPadding = sensorDistanceFromEdge
-                - dialogMarginPx
-                - navbarHorizontalInsetPx;
-
-        if (DEBUG) {
-            Log.d(TAG, "Display width: " + displayWidthPx
-                    + ", Distance from edge: " + sensorDistanceFromEdge
-                    + ", Dialog margin: " + dialogMarginPx
-                    + ", Navbar horizontal inset: " + navbarHorizontalInsetPx
-                    + ", Horizontal spacer width (landscape): " + horizontalPadding
-                    + ", Scale Factor: " + scaleFactor);
-        }
-
-        return horizontalPadding;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
index 5e2b5ff..6da5e42 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
@@ -188,7 +188,6 @@
         val hasCredentialViewShown = promptKind.value.isCredential()
         val showBpForCredential =
             Flags.customBiometricPrompt() &&
-                com.android.systemui.Flags.constraintBp() &&
                 !Utils.isBiometricAllowed(promptInfo) &&
                 isDeviceCredentialAllowed(promptInfo) &&
                 promptInfo.contentView != null &&
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
index 348b423..695707d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
@@ -44,7 +44,7 @@
         val logoDescription: String? = info.logoDescription
         val negativeButtonText: String = info.negativeButtonText?.toString() ?: ""
         val componentNameForConfirmDeviceCredentialActivity: ComponentName? =
-            info.componentNameForConfirmDeviceCredentialActivity
+            info.realCallerForConfirmDeviceCredentialActivity
         val allowBackgroundAuthentication = info.isAllowBackgroundAuthentication
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java b/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java
deleted file mode 100644
index b450896..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java
+++ /dev/null
@@ -1,188 +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.biometrics.ui;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.graphics.Insets;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.View;
-import android.view.WindowInsets;
-import android.view.WindowManager;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.systemui.biometrics.AuthController;
-import com.android.systemui.biometrics.AuthDialog;
-import com.android.systemui.biometrics.UdfpsDialogMeasureAdapter;
-import com.android.systemui.res.R;
-
-import kotlin.Pair;
-
-/**
- * Contains the Biometric views (title, subtitle, icon, buttons, etc.).
- *
- * TODO(b/251476085): get the udfps junk out of here, at a minimum. Likely can be replaced with a
- * normal LinearLayout.
- */
-public class BiometricPromptLayout extends LinearLayout {
-
-    private static final String TAG = "BiometricPromptLayout";
-
-    @NonNull
-    private final WindowManager mWindowManager;
-    @Nullable
-    private AuthController.ScaleFactorProvider mScaleFactorProvider;
-    @Nullable
-    private UdfpsDialogMeasureAdapter mUdfpsAdapter;
-
-    private final boolean mUseCustomBpSize;
-    private final int mCustomBpWidth;
-    private final int mCustomBpHeight;
-
-    public BiometricPromptLayout(Context context) {
-        this(context, null);
-    }
-
-    public BiometricPromptLayout(Context context, AttributeSet attrs) {
-        super(context, attrs);
-
-        mWindowManager = context.getSystemService(WindowManager.class);
-
-        mUseCustomBpSize = getResources().getBoolean(R.bool.use_custom_bp_size);
-        mCustomBpWidth = getResources().getDimensionPixelSize(R.dimen.biometric_dialog_width);
-        mCustomBpHeight = getResources().getDimensionPixelSize(R.dimen.biometric_dialog_height);
-    }
-
-    @Deprecated
-    public void setUdfpsAdapter(@NonNull UdfpsDialogMeasureAdapter adapter,
-            @NonNull AuthController.ScaleFactorProvider scaleProvider) {
-        mUdfpsAdapter = adapter;
-        mScaleFactorProvider = scaleProvider != null ? scaleProvider : () -> 1.0f;
-    }
-
-    @Deprecated
-    public boolean isUdfps() {
-        return mUdfpsAdapter != null;
-    }
-
-    @Deprecated
-    public Pair<Integer, Integer> getUpdatedFingerprintAffordanceSize() {
-        if (mUdfpsAdapter != null) {
-            final int sensorDiameter = mUdfpsAdapter.getSensorDiameter(
-                    mScaleFactorProvider.provide());
-            return new Pair(sensorDiameter, sensorDiameter);
-        }
-        return null;
-    }
-
-    @NonNull
-    private AuthDialog.LayoutParams onMeasureInternal(int width, int height) {
-        int totalHeight = 0;
-        final int numChildren = getChildCount();
-        for (int i = 0; i < numChildren; i++) {
-            final View child = getChildAt(i);
-
-            if (child.getId() == R.id.space_above_icon
-                    || child.getId() == R.id.space_above_content
-                    || child.getId() == R.id.space_below_icon
-                    || child.getId() == R.id.button_bar) {
-                child.measure(
-                        MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
-                        MeasureSpec.makeMeasureSpec(child.getLayoutParams().height,
-                                MeasureSpec.EXACTLY));
-            } else if (child.getId() == R.id.biometric_icon_frame) {
-                final View iconView = findViewById(R.id.biometric_icon);
-                child.measure(
-                        MeasureSpec.makeMeasureSpec(iconView.getLayoutParams().width,
-                                MeasureSpec.EXACTLY),
-                        MeasureSpec.makeMeasureSpec(iconView.getLayoutParams().height,
-                                MeasureSpec.EXACTLY));
-            } else if (child.getId() == R.id.logo) {
-                child.measure(
-                        MeasureSpec.makeMeasureSpec(child.getLayoutParams().width,
-                                MeasureSpec.EXACTLY),
-                        MeasureSpec.makeMeasureSpec(child.getLayoutParams().height,
-                                MeasureSpec.EXACTLY));
-            } else if (child.getId() == R.id.biometric_icon) {
-                child.measure(
-                        MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
-                        MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
-            } else {
-                child.measure(
-                        MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
-                        MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
-            }
-
-            if (child.getVisibility() != View.GONE) {
-                totalHeight += child.getMeasuredHeight();
-            }
-        }
-
-        final AuthDialog.LayoutParams params = new AuthDialog.LayoutParams(width, totalHeight);
-        if (mUdfpsAdapter != null) {
-            return mUdfpsAdapter.onMeasureInternal(width, height, params,
-                    (mScaleFactorProvider != null) ? mScaleFactorProvider.provide() : 1.0f);
-        } else {
-            return params;
-        }
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        int width = MeasureSpec.getSize(widthMeasureSpec);
-        int height = MeasureSpec.getSize(heightMeasureSpec);
-
-        if (mUseCustomBpSize) {
-            width = mCustomBpWidth;
-            height = mCustomBpHeight;
-        } else {
-            width = Math.min(width, height);
-        }
-
-        // add nav bar insets since the parent AuthContainerView
-        // uses LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
-        final Insets insets = mWindowManager.getMaximumWindowMetrics().getWindowInsets()
-                .getInsets(WindowInsets.Type.navigationBars());
-        final AuthDialog.LayoutParams params = onMeasureInternal(width, height);
-        setMeasuredDimension(params.mMediumWidth + insets.left + insets.right,
-                params.mMediumHeight + insets.bottom);
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        super.onLayout(changed, left, top, right, bottom);
-
-        if (mUdfpsAdapter != null) {
-            // Move the UDFPS icon and indicator text if necessary. This probably only needs to
-            // happen for devices where the UDFPS sensor is too low.
-            // TODO(b/201510778): Update this logic to support cases where the sensor or text
-            // overlap the button bar area.
-            final float bottomSpacerHeight = mUdfpsAdapter.getBottomSpacerHeight();
-            Log.w(TAG, "bottomSpacerHeight: " + bottomSpacerHeight);
-            if (bottomSpacerHeight < 0) {
-                final FrameLayout iconFrame = findViewById(R.id.biometric_icon_frame);
-                iconFrame.setTranslationY(-bottomSpacerHeight);
-                final TextView indicator = findViewById(R.id.indicator);
-                indicator.setTranslationY(-bottomSpacerHeight);
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt
index 7ccac03..0b474f8 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt
@@ -38,14 +38,13 @@
 import android.widget.Space
 import android.widget.TextView
 import com.android.settingslib.Utils
-import com.android.systemui.biometrics.ui.BiometricPromptLayout
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.res.R
 import kotlin.math.ceil
 
 private const val TAG = "BiometricCustomizedViewBinder"
 
-/** Sub-binder for [BiometricPromptLayout.customized_view_container]. */
+/** Sub-binder for Biometric Prompt Customized View */
 object BiometricCustomizedViewBinder {
     fun bind(
         customizedViewContainer: LinearLayout,
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 43ba097..a20a17f 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,13 +45,10 @@
 import androidx.lifecycle.repeatOnLifecycle
 import com.airbnb.lottie.LottieAnimationView
 import com.airbnb.lottie.LottieCompositionFactory
-import com.android.systemui.Flags.constraintBp
-import com.android.systemui.biometrics.AuthPanelController
 import com.android.systemui.biometrics.shared.model.BiometricModalities
 import com.android.systemui.biometrics.shared.model.BiometricModality
 import com.android.systemui.biometrics.shared.model.PromptKind
 import com.android.systemui.biometrics.shared.model.asBiometricModality
-import com.android.systemui.biometrics.ui.BiometricPromptLayout
 import com.android.systemui.biometrics.ui.viewmodel.FingerprintStartMode
 import com.android.systemui.biometrics.ui.viewmodel.PromptMessage
 import com.android.systemui.biometrics.ui.viewmodel.PromptSize
@@ -72,28 +69,18 @@
 /** Top-most view binder for BiometricPrompt views. */
 object BiometricViewBinder {
 
-    /** Binds a [BiometricPromptLayout] to a [PromptViewModel]. */
+    /** Binds a Biometric Prompt View to a [PromptViewModel]. */
     @SuppressLint("ClickableViewAccessibility")
     @JvmStatic
     fun bind(
         view: View,
         viewModel: PromptViewModel,
-        panelViewController: AuthPanelController?,
         jankListener: BiometricJankListener,
         backgroundView: View,
         legacyCallback: Spaghetti.Callback,
         applicationScope: CoroutineScope,
         vibratorHelper: VibratorHelper,
     ): Spaghetti {
-        /**
-         * View is only set visible in BiometricViewSizeBinder once PromptSize is determined that
-         * accounts for iconView size, to prevent prompt resizing being visible to the user.
-         *
-         * TODO(b/288175072): May be able to remove this once constraint layout is implemented
-         */
-        if (!constraintBp()) {
-            view.visibility = View.INVISIBLE
-        }
         val accessibilityManager = view.context.getSystemService(AccessibilityManager::class.java)!!
 
         val textColorError =
@@ -104,9 +91,7 @@
                 R.style.TextAppearance_AuthCredential_Indicator,
                 intArrayOf(android.R.attr.textColor)
             )
-        val textColorHint =
-            if (constraintBp()) attributes.getColor(0, 0)
-            else view.resources.getColor(R.color.biometric_dialog_gray, view.context.theme)
+        val textColorHint = attributes.getColor(0, 0)
         attributes.recycle()
 
         val logoView = view.requireViewById<ImageView>(R.id.logo)
@@ -116,12 +101,7 @@
         val descriptionView = view.requireViewById<TextView>(R.id.description)
         val customizedViewContainer =
             view.requireViewById<LinearLayout>(R.id.customized_view_container)
-        val udfpsGuidanceView =
-            if (constraintBp()) {
-                view.requireViewById<View>(R.id.panel)
-            } else {
-                backgroundView
-            }
+        val udfpsGuidanceView = view.requireViewById<View>(R.id.panel)
 
         // set selected to enable marquee unless a screen reader is enabled
         titleView.isSelected =
@@ -130,14 +110,6 @@
             !accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled
 
         val iconView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon)
-
-        val iconSizeOverride =
-            if (constraintBp()) {
-                null
-            } else {
-                (view as BiometricPromptLayout).updatedFingerprintAffordanceSize
-            }
-
         val indicatorMessageView = view.requireViewById<TextView>(R.id.indicator)
 
         // Negative-side (left) buttons
@@ -213,7 +185,7 @@
             subtitleView.text = viewModel.subtitle.first()
             descriptionView.text = viewModel.description.first()
 
-            if (Flags.customBiometricPrompt() && constraintBp()) {
+            if (Flags.customBiometricPrompt()) {
                 BiometricCustomizedViewBinder.bind(
                     customizedViewContainer,
                     viewModel.contentView.first(),
@@ -250,22 +222,6 @@
                             descriptionView,
                             customizedViewContainer,
                         ),
-                    viewsToFadeInOnSizeChange =
-                        listOf(
-                            logoView,
-                            logoDescriptionView,
-                            titleView,
-                            subtitleView,
-                            descriptionView,
-                            customizedViewContainer,
-                            indicatorMessageView,
-                            negativeButton,
-                            cancelButton,
-                            retryButton,
-                            confirmationButton,
-                            credentialFallbackButton,
-                        ),
-                    panelViewController = panelViewController,
                     jankListener = jankListener,
                 )
             }
@@ -275,7 +231,6 @@
                     if (!showWithoutIcon) {
                         PromptIconViewBinder.bind(
                             iconView,
-                            iconSizeOverride,
                             viewModel,
                         )
                     }
@@ -329,20 +284,6 @@
                     }
                 }
 
-                // set padding
-                launch {
-                    viewModel.promptPadding.collect { promptPadding ->
-                        if (!constraintBp()) {
-                            view.setPadding(
-                                promptPadding.left,
-                                promptPadding.top,
-                                promptPadding.right,
-                                promptPadding.bottom
-                            )
-                        }
-                    }
-                }
-
                 // configure & hide/disable buttons
                 launch {
                     viewModel.credentialKind
@@ -546,24 +487,6 @@
         fun onAuthenticatedAndConfirmed()
     }
 
-    @Deprecated("TODO(b/330788871): remove after replacing AuthContainerView")
-    enum class BiometricState {
-        /** Authentication hardware idle. */
-        STATE_IDLE,
-        /** UI animating in, authentication hardware active. */
-        STATE_AUTHENTICATING_ANIMATING_IN,
-        /** UI animated in, authentication hardware active. */
-        STATE_AUTHENTICATING,
-        /** UI animated in, authentication hardware active. */
-        STATE_HELP,
-        /** Hard error, e.g. ERROR_TIMEOUT. Authentication hardware idle. */
-        STATE_ERROR,
-        /** Authenticated, waiting for user confirmation. Authentication hardware idle. */
-        STATE_PENDING_CONFIRMATION,
-        /** Authenticated, dialog animating away soon. */
-        STATE_AUTHENTICATED,
-    }
-
     private var lifecycleScope: CoroutineScope? = null
     private var modalities: BiometricModalities = BiometricModalities()
     private var legacyCallback: Callback? = null
@@ -699,15 +622,8 @@
         }
 
     fun startTransitionToCredentialUI(isError: Boolean) {
-        if (!constraintBp()) {
-            applicationScope.launch {
-                viewModel.onSwitchToCredential()
-                legacyCallback?.onUseDeviceCredential()
-            }
-        } else {
-            viewModel.onSwitchToCredential()
-            legacyCallback?.onUseDeviceCredential()
-        }
+        viewModel.onSwitchToCredential()
+        legacyCallback?.onUseDeviceCredential()
     }
 
     fun cancelAnimation() {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index b9ec2de..85c3ae3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -38,10 +38,7 @@
 import androidx.constraintlayout.widget.Guideline
 import androidx.core.animation.addListener
 import androidx.core.view.doOnLayout
-import androidx.core.view.isGone
 import androidx.lifecycle.lifecycleScope
-import com.android.systemui.Flags.constraintBp
-import com.android.systemui.biometrics.AuthPanelController
 import com.android.systemui.biometrics.Utils
 import com.android.systemui.biometrics.ui.viewmodel.PromptPosition
 import com.android.systemui.biometrics.ui.viewmodel.PromptSize
@@ -49,7 +46,6 @@
 import com.android.systemui.biometrics.ui.viewmodel.isLarge
 import com.android.systemui.biometrics.ui.viewmodel.isLeft
 import com.android.systemui.biometrics.ui.viewmodel.isMedium
-import com.android.systemui.biometrics.ui.viewmodel.isNullOrNotSmall
 import com.android.systemui.biometrics.ui.viewmodel.isSmall
 import com.android.systemui.biometrics.ui.viewmodel.isTop
 import com.android.systemui.lifecycle.repeatWhenAttached
@@ -71,8 +67,6 @@
         view: View,
         viewModel: PromptViewModel,
         viewsToHideWhenSmall: List<View>,
-        viewsToFadeInOnSizeChange: List<View>,
-        panelViewController: AuthPanelController?,
         jankListener: BiometricJankListener,
     ) {
         val windowManager = requireNotNull(view.context.getSystemService(WindowManager::class.java))
@@ -92,553 +86,366 @@
             }
         }
 
-        if (constraintBp()) {
-            val leftGuideline = view.requireViewById<Guideline>(R.id.leftGuideline)
-            val topGuideline = view.requireViewById<Guideline>(R.id.topGuideline)
-            val rightGuideline = view.requireViewById<Guideline>(R.id.rightGuideline)
-            val midGuideline = view.findViewById<Guideline>(R.id.midGuideline)
+        val leftGuideline = view.requireViewById<Guideline>(R.id.leftGuideline)
+        val topGuideline = view.requireViewById<Guideline>(R.id.topGuideline)
+        val rightGuideline = view.requireViewById<Guideline>(R.id.rightGuideline)
+        val midGuideline = view.findViewById<Guideline>(R.id.midGuideline)
 
-            val iconHolderView = view.requireViewById<View>(R.id.biometric_icon)
-            val panelView = view.requireViewById<View>(R.id.panel)
-            val cornerRadius = view.resources.getDimension(R.dimen.biometric_dialog_corner_size)
-            val pxToDp =
-                TypedValue.applyDimension(
-                    TypedValue.COMPLEX_UNIT_DIP,
-                    1f,
-                    view.resources.displayMetrics
-                )
-            val cornerRadiusPx = (pxToDp * cornerRadius).toInt()
+        val iconHolderView = view.requireViewById<View>(R.id.biometric_icon)
+        val panelView = view.requireViewById<View>(R.id.panel)
+        val cornerRadius = view.resources.getDimension(R.dimen.biometric_dialog_corner_size)
+        val pxToDp =
+            TypedValue.applyDimension(
+                TypedValue.COMPLEX_UNIT_DIP,
+                1f,
+                view.resources.displayMetrics
+            )
+        val cornerRadiusPx = (pxToDp * cornerRadius).toInt()
 
-            var currentSize: PromptSize? = null
-            var currentPosition: PromptPosition = PromptPosition.Bottom
-            panelView.outlineProvider =
-                object : ViewOutlineProvider() {
-                    override fun getOutline(view: View, outline: Outline) {
-                        when (currentPosition) {
-                            PromptPosition.Right -> {
-                                outline.setRoundRect(
-                                    0,
-                                    0,
-                                    view.width + cornerRadiusPx,
-                                    view.height,
-                                    cornerRadiusPx.toFloat()
-                                )
-                            }
-                            PromptPosition.Left -> {
-                                outline.setRoundRect(
-                                    -cornerRadiusPx,
-                                    0,
-                                    view.width,
-                                    view.height,
-                                    cornerRadiusPx.toFloat()
-                                )
-                            }
-                            PromptPosition.Bottom,
-                            PromptPosition.Top -> {
-                                outline.setRoundRect(
-                                    0,
-                                    0,
-                                    view.width,
-                                    view.height + cornerRadiusPx,
-                                    cornerRadiusPx.toFloat()
-                                )
-                            }
-                        }
-                    }
-                }
-
-            // ConstraintSets for animating between prompt sizes
-            val mediumConstraintSet = ConstraintSet()
-            mediumConstraintSet.clone(view as ConstraintLayout)
-
-            val smallConstraintSet = ConstraintSet()
-            smallConstraintSet.clone(mediumConstraintSet)
-
-            val largeConstraintSet = ConstraintSet()
-            largeConstraintSet.clone(mediumConstraintSet)
-            largeConstraintSet.constrainMaxWidth(R.id.panel, 0)
-            largeConstraintSet.setGuidelineBegin(R.id.leftGuideline, 0)
-            largeConstraintSet.setGuidelineEnd(R.id.rightGuideline, 0)
-
-            // TODO: Investigate better way to handle 180 rotations
-            val flipConstraintSet = ConstraintSet()
-
-            view.doOnLayout {
-                fun setVisibilities(hideSensorIcon: Boolean, size: PromptSize) {
-                    viewsToHideWhenSmall.forEach { it.showContentOrHide(forceHide = size.isSmall) }
-                    largeConstraintSet.setVisibility(iconHolderView.id, View.GONE)
-                    largeConstraintSet.setVisibility(R.id.indicator, View.GONE)
-                    largeConstraintSet.setVisibility(R.id.scrollView, View.GONE)
-
-                    if (hideSensorIcon) {
-                        smallConstraintSet.setVisibility(iconHolderView.id, View.GONE)
-                        smallConstraintSet.setVisibility(R.id.indicator, View.GONE)
-                        mediumConstraintSet.setVisibility(iconHolderView.id, View.GONE)
-                        mediumConstraintSet.setVisibility(R.id.indicator, View.GONE)
-                    }
-                }
-
-                view.repeatWhenAttached {
-                    lifecycleScope.launch {
-                        viewModel.iconPosition.collect { position ->
-                            if (position != Rect()) {
-                                val iconParams =
-                                    iconHolderView.layoutParams as ConstraintLayout.LayoutParams
-
-                                if (position.left != 0) {
-                                    iconParams.endToEnd = ConstraintSet.UNSET
-                                    iconParams.leftMargin = position.left
-                                    mediumConstraintSet.clear(
-                                        R.id.biometric_icon,
-                                        ConstraintSet.RIGHT
-                                    )
-                                    mediumConstraintSet.connect(
-                                        R.id.biometric_icon,
-                                        ConstraintSet.LEFT,
-                                        ConstraintSet.PARENT_ID,
-                                        ConstraintSet.LEFT
-                                    )
-                                    mediumConstraintSet.setMargin(
-                                        R.id.biometric_icon,
-                                        ConstraintSet.LEFT,
-                                        position.left
-                                    )
-                                    smallConstraintSet.clear(
-                                        R.id.biometric_icon,
-                                        ConstraintSet.RIGHT
-                                    )
-                                    smallConstraintSet.connect(
-                                        R.id.biometric_icon,
-                                        ConstraintSet.LEFT,
-                                        ConstraintSet.PARENT_ID,
-                                        ConstraintSet.LEFT
-                                    )
-                                    smallConstraintSet.setMargin(
-                                        R.id.biometric_icon,
-                                        ConstraintSet.LEFT,
-                                        position.left
-                                    )
-                                }
-                                if (position.top != 0) {
-                                    iconParams.bottomToBottom = ConstraintSet.UNSET
-                                    iconParams.topMargin = position.top
-                                    mediumConstraintSet.clear(
-                                        R.id.biometric_icon,
-                                        ConstraintSet.BOTTOM
-                                    )
-                                    mediumConstraintSet.setMargin(
-                                        R.id.biometric_icon,
-                                        ConstraintSet.TOP,
-                                        position.top
-                                    )
-                                    smallConstraintSet.clear(
-                                        R.id.biometric_icon,
-                                        ConstraintSet.BOTTOM
-                                    )
-                                    smallConstraintSet.setMargin(
-                                        R.id.biometric_icon,
-                                        ConstraintSet.TOP,
-                                        position.top
-                                    )
-                                }
-                                if (position.right != 0) {
-                                    iconParams.startToStart = ConstraintSet.UNSET
-                                    iconParams.rightMargin = position.right
-                                    mediumConstraintSet.clear(
-                                        R.id.biometric_icon,
-                                        ConstraintSet.LEFT
-                                    )
-                                    mediumConstraintSet.connect(
-                                        R.id.biometric_icon,
-                                        ConstraintSet.RIGHT,
-                                        ConstraintSet.PARENT_ID,
-                                        ConstraintSet.RIGHT
-                                    )
-                                    mediumConstraintSet.setMargin(
-                                        R.id.biometric_icon,
-                                        ConstraintSet.RIGHT,
-                                        position.right
-                                    )
-                                    smallConstraintSet.clear(
-                                        R.id.biometric_icon,
-                                        ConstraintSet.LEFT
-                                    )
-                                    smallConstraintSet.connect(
-                                        R.id.biometric_icon,
-                                        ConstraintSet.RIGHT,
-                                        ConstraintSet.PARENT_ID,
-                                        ConstraintSet.RIGHT
-                                    )
-                                    smallConstraintSet.setMargin(
-                                        R.id.biometric_icon,
-                                        ConstraintSet.RIGHT,
-                                        position.right
-                                    )
-                                }
-                                if (position.bottom != 0) {
-                                    iconParams.topToTop = ConstraintSet.UNSET
-                                    iconParams.bottomMargin = position.bottom
-                                    mediumConstraintSet.clear(
-                                        R.id.biometric_icon,
-                                        ConstraintSet.TOP
-                                    )
-                                    mediumConstraintSet.setMargin(
-                                        R.id.biometric_icon,
-                                        ConstraintSet.BOTTOM,
-                                        position.bottom
-                                    )
-                                    smallConstraintSet.clear(R.id.biometric_icon, ConstraintSet.TOP)
-                                    smallConstraintSet.setMargin(
-                                        R.id.biometric_icon,
-                                        ConstraintSet.BOTTOM,
-                                        position.bottom
-                                    )
-                                }
-                                iconHolderView.layoutParams = iconParams
-                            }
-                        }
-                    }
-
-                    lifecycleScope.launch {
-                        viewModel.iconSize.collect { iconSize ->
-                            iconHolderView.layoutParams.width = iconSize.first
-                            iconHolderView.layoutParams.height = iconSize.second
-                            mediumConstraintSet.constrainWidth(R.id.biometric_icon, iconSize.first)
-                            mediumConstraintSet.constrainHeight(
-                                R.id.biometric_icon,
-                                iconSize.second
+        var currentSize: PromptSize? = null
+        var currentPosition: PromptPosition = PromptPosition.Bottom
+        panelView.outlineProvider =
+            object : ViewOutlineProvider() {
+                override fun getOutline(view: View, outline: Outline) {
+                    when (currentPosition) {
+                        PromptPosition.Right -> {
+                            outline.setRoundRect(
+                                0,
+                                0,
+                                view.width + cornerRadiusPx,
+                                view.height,
+                                cornerRadiusPx.toFloat()
                             )
                         }
-                    }
-
-                    lifecycleScope.launch {
-                        viewModel.guidelineBounds.collect { bounds ->
-                            val bottomInset =
-                                windowManager.maximumWindowMetrics.windowInsets
-                                    .getInsets(WindowInsets.Type.navigationBars())
-                                    .bottom
-                            mediumConstraintSet.setGuidelineEnd(R.id.bottomGuideline, bottomInset)
-
-                            if (bounds.left >= 0) {
-                                mediumConstraintSet.setGuidelineBegin(leftGuideline.id, bounds.left)
-                                smallConstraintSet.setGuidelineBegin(leftGuideline.id, bounds.left)
-                            } else if (bounds.left < 0) {
-                                mediumConstraintSet.setGuidelineEnd(
-                                    leftGuideline.id,
-                                    abs(bounds.left)
-                                )
-                                smallConstraintSet.setGuidelineEnd(
-                                    leftGuideline.id,
-                                    abs(bounds.left)
-                                )
-                            }
-
-                            if (bounds.right >= 0) {
-                                mediumConstraintSet.setGuidelineEnd(rightGuideline.id, bounds.right)
-                                smallConstraintSet.setGuidelineEnd(rightGuideline.id, bounds.right)
-                            } else if (bounds.right < 0) {
-                                mediumConstraintSet.setGuidelineBegin(
-                                    rightGuideline.id,
-                                    abs(bounds.right)
-                                )
-                                smallConstraintSet.setGuidelineBegin(
-                                    rightGuideline.id,
-                                    abs(bounds.right)
-                                )
-                            }
-
-                            if (bounds.top >= 0) {
-                                mediumConstraintSet.setGuidelineBegin(topGuideline.id, bounds.top)
-                                smallConstraintSet.setGuidelineBegin(topGuideline.id, bounds.top)
-                            } else if (bounds.top < 0) {
-                                mediumConstraintSet.setGuidelineEnd(
-                                    topGuideline.id,
-                                    abs(bounds.top)
-                                )
-                                smallConstraintSet.setGuidelineEnd(topGuideline.id, abs(bounds.top))
-                            }
-
-                            if (midGuideline != null) {
-                                val left =
-                                    if (bounds.left >= 0) {
-                                        abs(bounds.left)
-                                    } else {
-                                        view.width - abs(bounds.left)
-                                    }
-                                val right =
-                                    if (bounds.right >= 0) {
-                                        view.width - abs(bounds.right)
-                                    } else {
-                                        abs(bounds.right)
-                                    }
-                                val mid = (left + right) / 2
-                                mediumConstraintSet.setGuidelineBegin(midGuideline.id, mid)
-                            }
+                        PromptPosition.Left -> {
+                            outline.setRoundRect(
+                                -cornerRadiusPx,
+                                0,
+                                view.width,
+                                view.height,
+                                cornerRadiusPx.toFloat()
+                            )
                         }
-                    }
-
-                    lifecycleScope.launch {
-                        combine(viewModel.hideSensorIcon, viewModel.size, ::Pair).collect {
-                            (hideSensorIcon, size) ->
-                            setVisibilities(hideSensorIcon, size)
-                        }
-                    }
-
-                    lifecycleScope.launch {
-                        combine(viewModel.position, viewModel.size, ::Pair).collect {
-                            (position, size) ->
-                            if (position.isLeft) {
-                                if (size.isSmall) {
-                                    flipConstraintSet.clone(smallConstraintSet)
-                                } else {
-                                    flipConstraintSet.clone(mediumConstraintSet)
-                                }
-
-                                // Move all content to other panel
-                                flipConstraintSet.connect(
-                                    R.id.scrollView,
-                                    ConstraintSet.LEFT,
-                                    R.id.midGuideline,
-                                    ConstraintSet.LEFT
-                                )
-                                flipConstraintSet.connect(
-                                    R.id.scrollView,
-                                    ConstraintSet.RIGHT,
-                                    R.id.rightGuideline,
-                                    ConstraintSet.RIGHT
-                                )
-                            } else if (position.isTop) {
-                                // Top position is only used for 180 rotation Udfps
-                                // Requires repositioning due to sensor location at top of screen
-                                mediumConstraintSet.connect(
-                                    R.id.scrollView,
-                                    ConstraintSet.TOP,
-                                    R.id.indicator,
-                                    ConstraintSet.BOTTOM
-                                )
-                                mediumConstraintSet.connect(
-                                    R.id.scrollView,
-                                    ConstraintSet.BOTTOM,
-                                    R.id.button_bar,
-                                    ConstraintSet.TOP
-                                )
-                                mediumConstraintSet.connect(
-                                    R.id.panel,
-                                    ConstraintSet.TOP,
-                                    R.id.biometric_icon,
-                                    ConstraintSet.TOP
-                                )
-                                mediumConstraintSet.setMargin(
-                                    R.id.panel,
-                                    ConstraintSet.TOP,
-                                    (-24 * pxToDp).toInt()
-                                )
-                                mediumConstraintSet.setVerticalBias(R.id.scrollView, 0f)
-                            }
-
-                            when {
-                                size.isSmall -> {
-                                    if (position.isLeft) {
-                                        flipConstraintSet.applyTo(view)
-                                    } else {
-                                        smallConstraintSet.applyTo(view)
-                                    }
-                                }
-                                size.isMedium && currentSize.isSmall -> {
-                                    val autoTransition = AutoTransition()
-                                    autoTransition.setDuration(
-                                        ANIMATE_SMALL_TO_MEDIUM_DURATION_MS.toLong()
-                                    )
-
-                                    TransitionManager.beginDelayedTransition(view, autoTransition)
-
-                                    if (position.isLeft) {
-                                        flipConstraintSet.applyTo(view)
-                                    } else {
-                                        mediumConstraintSet.applyTo(view)
-                                    }
-                                }
-                                size.isMedium -> {
-                                    if (position.isLeft) {
-                                        flipConstraintSet.applyTo(view)
-                                    } else {
-                                        mediumConstraintSet.applyTo(view)
-                                    }
-                                }
-                                size.isLarge && currentSize.isMedium -> {
-                                    val autoTransition = AutoTransition()
-                                    autoTransition.setDuration(
-                                        ANIMATE_MEDIUM_TO_LARGE_DURATION_MS.toLong()
-                                    )
-
-                                    TransitionManager.beginDelayedTransition(view, autoTransition)
-                                    largeConstraintSet.applyTo(view)
-                                }
-                            }
-
-                            currentSize = size
-                            currentPosition = position
-                            notifyAccessibilityChanged()
-
-                            panelView.invalidateOutline()
-                            view.invalidate()
-                            view.requestLayout()
+                        PromptPosition.Bottom,
+                        PromptPosition.Top -> {
+                            outline.setRoundRect(
+                                0,
+                                0,
+                                view.width,
+                                view.height + cornerRadiusPx,
+                                cornerRadiusPx.toFloat()
+                            )
                         }
                     }
                 }
             }
-        } else if (panelViewController != null) {
-            val iconHolderView = view.requireViewById<View>(R.id.biometric_icon_frame)
-            val iconPadding = view.resources.getDimension(R.dimen.biometric_dialog_icon_padding)
-            val fullSizeYOffset =
-                view.resources.getDimension(
-                    R.dimen.biometric_dialog_medium_to_large_translation_offset
-                )
 
-            // cache the original position of the icon view (as done in legacy view)
-            // this must happen before any size changes can be made
-            view.doOnLayout {
-                // TODO(b/251476085): this old way of positioning has proven itself unreliable
-                // remove this and associated thing like (UdfpsDialogMeasureAdapter) and
-                // pin to the physical sensor
-                val iconHolderOriginalY = iconHolderView.y
+        // ConstraintSets for animating between prompt sizes
+        val mediumConstraintSet = ConstraintSet()
+        mediumConstraintSet.clone(view as ConstraintLayout)
 
-                // bind to prompt
-                // TODO(b/251476085): migrate the legacy panel controller and simplify this
-                view.repeatWhenAttached {
-                    var currentSize: PromptSize? = null
-                    lifecycleScope.launch {
-                        /**
-                         * View is only set visible in BiometricViewSizeBinder once PromptSize is
-                         * determined that accounts for iconView size, to prevent prompt resizing
-                         * being visible to the user.
-                         *
-                         * TODO(b/288175072): May be able to remove isIconViewLoaded once constraint
-                         *   layout is implemented
-                         */
-                        combine(viewModel.isIconViewLoaded, viewModel.size, ::Pair).collect {
-                            (isIconViewLoaded, size) ->
-                            if (!isIconViewLoaded) {
-                                return@collect
+        val smallConstraintSet = ConstraintSet()
+        smallConstraintSet.clone(mediumConstraintSet)
+
+        val largeConstraintSet = ConstraintSet()
+        largeConstraintSet.clone(mediumConstraintSet)
+        largeConstraintSet.constrainMaxWidth(R.id.panel, 0)
+        largeConstraintSet.setGuidelineBegin(R.id.leftGuideline, 0)
+        largeConstraintSet.setGuidelineEnd(R.id.rightGuideline, 0)
+
+        // TODO: Investigate better way to handle 180 rotations
+        val flipConstraintSet = ConstraintSet()
+
+        view.doOnLayout {
+            fun setVisibilities(hideSensorIcon: Boolean, size: PromptSize) {
+                viewsToHideWhenSmall.forEach { it.showContentOrHide(forceHide = size.isSmall) }
+                largeConstraintSet.setVisibility(iconHolderView.id, View.GONE)
+                largeConstraintSet.setVisibility(R.id.biometric_icon_overlay, View.GONE)
+                largeConstraintSet.setVisibility(R.id.indicator, View.GONE)
+                largeConstraintSet.setVisibility(R.id.scrollView, View.GONE)
+
+                if (hideSensorIcon) {
+                    smallConstraintSet.setVisibility(iconHolderView.id, View.GONE)
+                    smallConstraintSet.setVisibility(R.id.biometric_icon_overlay, View.GONE)
+                    smallConstraintSet.setVisibility(R.id.indicator, View.GONE)
+                    mediumConstraintSet.setVisibility(iconHolderView.id, View.GONE)
+                    mediumConstraintSet.setVisibility(R.id.biometric_icon_overlay, View.GONE)
+                    mediumConstraintSet.setVisibility(R.id.indicator, View.GONE)
+                }
+            }
+
+            view.repeatWhenAttached {
+                lifecycleScope.launch {
+                    viewModel.iconPosition.collect { position ->
+                        if (position != Rect()) {
+                            val iconParams =
+                                iconHolderView.layoutParams as ConstraintLayout.LayoutParams
+
+                            if (position.left != 0) {
+                                iconParams.endToEnd = ConstraintSet.UNSET
+                                iconParams.leftMargin = position.left
+                                mediumConstraintSet.clear(R.id.biometric_icon, ConstraintSet.RIGHT)
+                                mediumConstraintSet.connect(
+                                    R.id.biometric_icon,
+                                    ConstraintSet.LEFT,
+                                    ConstraintSet.PARENT_ID,
+                                    ConstraintSet.LEFT
+                                )
+                                mediumConstraintSet.setMargin(
+                                    R.id.biometric_icon,
+                                    ConstraintSet.LEFT,
+                                    position.left
+                                )
+                                smallConstraintSet.clear(R.id.biometric_icon, ConstraintSet.RIGHT)
+                                smallConstraintSet.connect(
+                                    R.id.biometric_icon,
+                                    ConstraintSet.LEFT,
+                                    ConstraintSet.PARENT_ID,
+                                    ConstraintSet.LEFT
+                                )
+                                smallConstraintSet.setMargin(
+                                    R.id.biometric_icon,
+                                    ConstraintSet.LEFT,
+                                    position.left
+                                )
                             }
-
-                            // prepare for animated size transitions
-                            for (v in viewsToHideWhenSmall) {
-                                v.showContentOrHide(forceHide = size.isSmall)
+                            if (position.top != 0) {
+                                iconParams.bottomToBottom = ConstraintSet.UNSET
+                                iconParams.topMargin = position.top
+                                mediumConstraintSet.clear(R.id.biometric_icon, ConstraintSet.BOTTOM)
+                                mediumConstraintSet.setMargin(
+                                    R.id.biometric_icon,
+                                    ConstraintSet.TOP,
+                                    position.top
+                                )
+                                smallConstraintSet.clear(R.id.biometric_icon, ConstraintSet.BOTTOM)
+                                smallConstraintSet.setMargin(
+                                    R.id.biometric_icon,
+                                    ConstraintSet.TOP,
+                                    position.top
+                                )
                             }
-
-                            if (viewModel.hideSensorIcon.first()) {
-                                iconHolderView.visibility = View.GONE
+                            if (position.right != 0) {
+                                iconParams.startToStart = ConstraintSet.UNSET
+                                iconParams.rightMargin = position.right
+                                mediumConstraintSet.clear(R.id.biometric_icon, ConstraintSet.LEFT)
+                                mediumConstraintSet.connect(
+                                    R.id.biometric_icon,
+                                    ConstraintSet.RIGHT,
+                                    ConstraintSet.PARENT_ID,
+                                    ConstraintSet.RIGHT
+                                )
+                                mediumConstraintSet.setMargin(
+                                    R.id.biometric_icon,
+                                    ConstraintSet.RIGHT,
+                                    position.right
+                                )
+                                smallConstraintSet.clear(R.id.biometric_icon, ConstraintSet.LEFT)
+                                smallConstraintSet.connect(
+                                    R.id.biometric_icon,
+                                    ConstraintSet.RIGHT,
+                                    ConstraintSet.PARENT_ID,
+                                    ConstraintSet.RIGHT
+                                )
+                                smallConstraintSet.setMargin(
+                                    R.id.biometric_icon,
+                                    ConstraintSet.RIGHT,
+                                    position.right
+                                )
                             }
-
-                            if (currentSize == null && size.isSmall) {
-                                iconHolderView.alpha = 0f
+                            if (position.bottom != 0) {
+                                iconParams.topToTop = ConstraintSet.UNSET
+                                iconParams.bottomMargin = position.bottom
+                                mediumConstraintSet.clear(R.id.biometric_icon, ConstraintSet.TOP)
+                                mediumConstraintSet.setMargin(
+                                    R.id.biometric_icon,
+                                    ConstraintSet.BOTTOM,
+                                    position.bottom
+                                )
+                                smallConstraintSet.clear(R.id.biometric_icon, ConstraintSet.TOP)
+                                smallConstraintSet.setMargin(
+                                    R.id.biometric_icon,
+                                    ConstraintSet.BOTTOM,
+                                    position.bottom
+                                )
                             }
-                            if ((currentSize.isSmall && size.isMedium) || size.isSmall) {
-                                viewsToFadeInOnSizeChange.forEach { it.alpha = 0f }
-                            }
+                            iconHolderView.layoutParams = iconParams
+                        }
+                    }
+                }
 
-                            // propagate size changes to legacy panel controller and animate
-                            // transitions
-                            view.doOnLayout {
-                                val width = view.measuredWidth
-                                val height = view.measuredHeight
+                lifecycleScope.launch {
+                    viewModel.iconSize.collect { iconSize ->
+                        iconHolderView.layoutParams.width = iconSize.first
+                        iconHolderView.layoutParams.height = iconSize.second
+                        mediumConstraintSet.constrainWidth(R.id.biometric_icon, iconSize.first)
+                        mediumConstraintSet.constrainHeight(R.id.biometric_icon, iconSize.second)
+                    }
+                }
 
-                                when {
-                                    size.isSmall -> {
-                                        iconHolderView.alpha = 1f
-                                        val bottomInset =
-                                            windowManager.maximumWindowMetrics.windowInsets
-                                                .getInsets(WindowInsets.Type.navigationBars())
-                                                .bottom
-                                        iconHolderView.y =
-                                            if (view.isLandscape()) {
-                                                (view.height -
-                                                    iconHolderView.height -
-                                                    bottomInset) / 2f
-                                            } else {
-                                                view.height -
-                                                    iconHolderView.height -
-                                                    iconPadding -
-                                                    bottomInset
-                                            }
-                                        val newHeight =
-                                            iconHolderView.height + (2 * iconPadding.toInt()) -
-                                                iconHolderView.paddingTop -
-                                                iconHolderView.paddingBottom
-                                        panelViewController.updateForContentDimensions(
-                                            width,
-                                            newHeight + bottomInset,
-                                            0, /* animateDurationMs */
-                                        )
-                                    }
-                                    size.isMedium && currentSize.isSmall -> {
-                                        val duration = ANIMATE_SMALL_TO_MEDIUM_DURATION_MS
-                                        panelViewController.updateForContentDimensions(
-                                            width,
-                                            height,
-                                            duration,
-                                        )
-                                        startMonitoredAnimation(
-                                            listOf(
-                                                iconHolderView.asVerticalAnimator(
-                                                    duration = duration.toLong(),
-                                                    toY =
-                                                        iconHolderOriginalY -
-                                                            viewsToHideWhenSmall
-                                                                .filter { it.isGone }
-                                                                .sumOf { it.height },
-                                                ),
-                                                viewsToFadeInOnSizeChange.asFadeInAnimator(
-                                                    duration = duration.toLong(),
-                                                    delay = duration.toLong(),
-                                                ),
-                                            )
-                                        )
-                                    }
-                                    size.isMedium && currentSize.isNullOrNotSmall -> {
-                                        panelViewController.updateForContentDimensions(
-                                            width,
-                                            height,
-                                            0, /* animateDurationMs */
-                                        )
-                                    }
-                                    size.isLarge -> {
-                                        val duration = ANIMATE_MEDIUM_TO_LARGE_DURATION_MS
-                                        panelViewController.setUseFullScreen(true)
-                                        panelViewController.updateForContentDimensions(
-                                            panelViewController.containerWidth,
-                                            panelViewController.containerHeight,
-                                            duration,
-                                        )
+                lifecycleScope.launch {
+                    viewModel.guidelineBounds.collect { bounds ->
+                        val bottomInset =
+                            windowManager.maximumWindowMetrics.windowInsets
+                                .getInsets(WindowInsets.Type.navigationBars())
+                                .bottom
+                        mediumConstraintSet.setGuidelineEnd(R.id.bottomGuideline, bottomInset)
 
-                                        startMonitoredAnimation(
-                                            listOf(
-                                                view.asVerticalAnimator(
-                                                    duration.toLong() * 2 / 3,
-                                                    toY = view.y - fullSizeYOffset
-                                                ),
-                                                listOf(view)
-                                                    .asFadeInAnimator(
-                                                        duration = duration.toLong() / 2,
-                                                        delay = duration.toLong(),
-                                                    ),
-                                            )
-                                        )
-                                        // TODO(b/251476085): clean up (copied from legacy)
-                                        if (view.isAttachedToWindow) {
-                                            val parent = view.parent as? ViewGroup
-                                            parent?.removeView(view)
-                                        }
-                                    }
+                        if (bounds.left >= 0) {
+                            mediumConstraintSet.setGuidelineBegin(leftGuideline.id, bounds.left)
+                            smallConstraintSet.setGuidelineBegin(leftGuideline.id, bounds.left)
+                        } else if (bounds.left < 0) {
+                            mediumConstraintSet.setGuidelineEnd(leftGuideline.id, abs(bounds.left))
+                            smallConstraintSet.setGuidelineEnd(leftGuideline.id, abs(bounds.left))
+                        }
+
+                        if (bounds.right >= 0) {
+                            mediumConstraintSet.setGuidelineEnd(rightGuideline.id, bounds.right)
+                            smallConstraintSet.setGuidelineEnd(rightGuideline.id, bounds.right)
+                        } else if (bounds.right < 0) {
+                            mediumConstraintSet.setGuidelineBegin(
+                                rightGuideline.id,
+                                abs(bounds.right)
+                            )
+                            smallConstraintSet.setGuidelineBegin(
+                                rightGuideline.id,
+                                abs(bounds.right)
+                            )
+                        }
+
+                        if (bounds.top >= 0) {
+                            mediumConstraintSet.setGuidelineBegin(topGuideline.id, bounds.top)
+                            smallConstraintSet.setGuidelineBegin(topGuideline.id, bounds.top)
+                        } else if (bounds.top < 0) {
+                            mediumConstraintSet.setGuidelineEnd(topGuideline.id, abs(bounds.top))
+                            smallConstraintSet.setGuidelineEnd(topGuideline.id, abs(bounds.top))
+                        }
+
+                        if (midGuideline != null) {
+                            val left =
+                                if (bounds.left >= 0) {
+                                    abs(bounds.left)
+                                } else {
+                                    view.width - abs(bounds.left)
                                 }
+                            val right =
+                                if (bounds.right >= 0) {
+                                    view.width - abs(bounds.right)
+                                } else {
+                                    abs(bounds.right)
+                                }
+                            val mid = (left + right) / 2
+                            mediumConstraintSet.setGuidelineBegin(midGuideline.id, mid)
+                        }
+                    }
+                }
 
-                                currentSize = size
-                                view.visibility = View.VISIBLE
-                                viewModel.setIsIconViewLoaded(false)
-                                notifyAccessibilityChanged()
+                lifecycleScope.launch {
+                    combine(viewModel.hideSensorIcon, viewModel.size, ::Pair).collect {
+                        (hideSensorIcon, size) ->
+                        setVisibilities(hideSensorIcon, size)
+                    }
+                }
+
+                lifecycleScope.launch {
+                    combine(viewModel.position, viewModel.size, ::Pair).collect { (position, size)
+                        ->
+                        if (position.isLeft) {
+                            if (size.isSmall) {
+                                flipConstraintSet.clone(smallConstraintSet)
+                            } else {
+                                flipConstraintSet.clone(mediumConstraintSet)
+                            }
+
+                            // Move all content to other panel
+                            flipConstraintSet.connect(
+                                R.id.scrollView,
+                                ConstraintSet.LEFT,
+                                R.id.midGuideline,
+                                ConstraintSet.LEFT
+                            )
+                            flipConstraintSet.connect(
+                                R.id.scrollView,
+                                ConstraintSet.RIGHT,
+                                R.id.rightGuideline,
+                                ConstraintSet.RIGHT
+                            )
+                        } else if (position.isTop) {
+                            // Top position is only used for 180 rotation Udfps
+                            // Requires repositioning due to sensor location at top of screen
+                            mediumConstraintSet.connect(
+                                R.id.scrollView,
+                                ConstraintSet.TOP,
+                                R.id.indicator,
+                                ConstraintSet.BOTTOM
+                            )
+                            mediumConstraintSet.connect(
+                                R.id.scrollView,
+                                ConstraintSet.BOTTOM,
+                                R.id.button_bar,
+                                ConstraintSet.TOP
+                            )
+                            mediumConstraintSet.connect(
+                                R.id.panel,
+                                ConstraintSet.TOP,
+                                R.id.biometric_icon,
+                                ConstraintSet.TOP
+                            )
+                            mediumConstraintSet.setMargin(
+                                R.id.panel,
+                                ConstraintSet.TOP,
+                                (-24 * pxToDp).toInt()
+                            )
+                            mediumConstraintSet.setVerticalBias(R.id.scrollView, 0f)
+                        }
+
+                        when {
+                            size.isSmall -> {
+                                if (position.isLeft) {
+                                    flipConstraintSet.applyTo(view)
+                                } else {
+                                    smallConstraintSet.applyTo(view)
+                                }
+                            }
+                            size.isMedium && currentSize.isSmall -> {
+                                val autoTransition = AutoTransition()
+                                autoTransition.setDuration(
+                                    ANIMATE_SMALL_TO_MEDIUM_DURATION_MS.toLong()
+                                )
+
+                                TransitionManager.beginDelayedTransition(view, autoTransition)
+
+                                if (position.isLeft) {
+                                    flipConstraintSet.applyTo(view)
+                                } else {
+                                    mediumConstraintSet.applyTo(view)
+                                }
+                            }
+                            size.isMedium -> {
+                                if (position.isLeft) {
+                                    flipConstraintSet.applyTo(view)
+                                } else {
+                                    mediumConstraintSet.applyTo(view)
+                                }
+                            }
+                            size.isLarge && currentSize.isMedium -> {
+                                val autoTransition = AutoTransition()
+                                autoTransition.setDuration(
+                                    ANIMATE_MEDIUM_TO_LARGE_DURATION_MS.toLong()
+                                )
+
+                                TransitionManager.beginDelayedTransition(view, autoTransition)
+                                largeConstraintSet.applyTo(view)
                             }
                         }
+
+                        currentSize = size
+                        currentPosition = position
+                        notifyAccessibilityChanged()
+
+                        panelView.invalidateOutline()
+                        view.invalidate()
+                        view.requestLayout()
                     }
                 }
             }
@@ -646,17 +453,6 @@
     }
 }
 
-private fun View.isLandscape(): Boolean {
-    val r = context.display.rotation
-    return if (
-        context.resources.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)
-    ) {
-        r == Surface.ROTATION_0 || r == Surface.ROTATION_180
-    } else {
-        r == Surface.ROTATION_90 || r == Surface.ROTATION_270
-    }
-}
-
 private fun View.showContentOrHide(forceHide: Boolean = false) {
     val isTextViewWithBlankText = this is TextView && this.text.isBlank()
     val isImageViewWithoutImage = this is ImageView && this.drawable == null
@@ -667,26 +463,3 @@
             View.VISIBLE
         }
 }
-
-private fun View.asVerticalAnimator(
-    duration: Long,
-    toY: Float,
-    fromY: Float = this.y
-): ValueAnimator {
-    val animator = ValueAnimator.ofFloat(fromY, toY)
-    animator.duration = duration
-    animator.addUpdateListener { y = it.animatedValue as Float }
-    return animator
-}
-
-private fun List<View>.asFadeInAnimator(duration: Long, delay: Long): ValueAnimator {
-    forEach { it.alpha = 0f }
-    val animator = ValueAnimator.ofFloat(0f, 1f)
-    animator.duration = duration
-    animator.startDelay = delay
-    animator.addUpdateListener {
-        val alpha = it.animatedValue as Float
-        forEach { view -> view.alpha = alpha }
-    }
-    return animator
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
index 18e2a56..49f4b05 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
@@ -10,7 +10,6 @@
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.app.animation.Interpolators
-import com.android.systemui.Flags.constraintBp
 import com.android.systemui.biometrics.AuthPanelController
 import com.android.systemui.biometrics.ui.CredentialPasswordView
 import com.android.systemui.biometrics.ui.CredentialPatternView
@@ -82,7 +81,7 @@
 
                         subtitleView.textOrHide = header.subtitle
                         descriptionView.textOrHide = header.description
-                        if (Flags.customBiometricPrompt() && constraintBp()) {
+                        if (Flags.customBiometricPrompt()) {
                             BiometricCustomizedViewBinder.bind(
                                 customizedViewContainer,
                                 header.contentView,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
index 9e4aaaa..eab3b26 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
@@ -22,9 +22,7 @@
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.airbnb.lottie.LottieAnimationView
-import com.airbnb.lottie.LottieOnCompositionLoadedListener
 import com.android.settingslib.widget.LottieColorUtils
-import com.android.systemui.Flags.constraintBp
 import com.android.systemui.biometrics.ui.viewmodel.PromptIconViewModel
 import com.android.systemui.biometrics.ui.viewmodel.PromptIconViewModel.AuthType
 import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel
@@ -44,55 +42,12 @@
     @JvmStatic
     fun bind(
         iconView: LottieAnimationView,
-        iconViewLayoutParamSizeOverride: Pair<Int, Int>?,
         promptViewModel: PromptViewModel
     ) {
         val viewModel = promptViewModel.iconViewModel
         iconView.repeatWhenAttached {
             repeatOnLifecycle(Lifecycle.State.STARTED) {
                 viewModel.onConfigurationChanged(iconView.context.resources.configuration)
-                if (iconViewLayoutParamSizeOverride != null) {
-                    iconView.layoutParams.width = iconViewLayoutParamSizeOverride.first
-                    iconView.layoutParams.height = iconViewLayoutParamSizeOverride.second
-                }
-
-                if (!constraintBp()) {
-                    launch {
-                        var lottieOnCompositionLoadedListener: LottieOnCompositionLoadedListener? =
-                            null
-
-                        viewModel.iconSize.collect { iconSize ->
-                            /**
-                             * When we bind the BiometricPrompt View and ViewModel in
-                             * [BiometricViewBinder], the view is set invisible and
-                             * [isIconViewLoaded] is set to false. We configure the iconView with a
-                             * LottieOnCompositionLoadedListener that sets [isIconViewLoaded] to
-                             * true, in order to wait for the iconView to load before determining
-                             * the prompt size, and prevent any prompt resizing from being visible
-                             * to the user.
-                             *
-                             * TODO(b/288175072): May be able to remove this once constraint layout
-                             *   is unflagged
-                             */
-                            if (lottieOnCompositionLoadedListener != null) {
-                                iconView.removeLottieOnCompositionLoadedListener(
-                                    lottieOnCompositionLoadedListener!!
-                                )
-                            }
-                            lottieOnCompositionLoadedListener = LottieOnCompositionLoadedListener {
-                                promptViewModel.setIsIconViewLoaded(true)
-                            }
-                            iconView.addLottieOnCompositionLoadedListener(
-                                lottieOnCompositionLoadedListener!!
-                            )
-
-                            if (iconViewLayoutParamSizeOverride == null) {
-                                iconView.layoutParams.width = iconSize.first
-                                iconView.layoutParams.height = iconSize.second
-                            }
-                        }
-                    }
-                }
 
                 launch {
                     viewModel.iconAsset
@@ -154,7 +109,7 @@
     setAnimation(asset)
     if (animatingFromSfpsAuthenticating(asset)) {
         // Skipping to error / success / unlock segment of animation
-        setMinFrame(151)
+        setMinFrame(158)
     } else {
         frame = 0
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt
index 31af126..761c3da 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt
@@ -6,7 +6,6 @@
 import android.hardware.biometrics.PromptContentView
 import android.text.InputType
 import com.android.internal.widget.LockPatternView
-import com.android.systemui.Flags.constraintBp
 import com.android.systemui.biometrics.Utils
 import com.android.systemui.biometrics.domain.interactor.CredentialStatus
 import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor
@@ -39,7 +38,7 @@
             credentialInteractor.prompt.filterIsInstance<BiometricPromptRequest.Credential>(),
             credentialInteractor.showTitleOnly
         ) { request, showTitleOnly ->
-            val flagEnabled = customBiometricPrompt() && constraintBp()
+            val flagEnabled = customBiometricPrompt()
             val showTitleOnlyForCredential = showTitleOnly && flagEnabled
             BiometricPromptHeaderViewModelImpl(
                 request,
@@ -82,8 +81,8 @@
     val errorMessage: Flow<String> =
         combine(credentialInteractor.verificationError, credentialInteractor.prompt) { error, p ->
             when (error) {
-                is CredentialStatus.Fail.Error -> error.error
-                        ?: applicationContext.asBadCredentialErrorMessage(p)
+                is CredentialStatus.Fail.Error ->
+                    error.error ?: applicationContext.asBadCredentialErrorMessage(p)
                 is CredentialStatus.Fail.Throttled -> error.error
                 null -> ""
             }
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 fbc6470..25d43d9 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
@@ -36,7 +36,6 @@
 import android.view.HapticFeedbackConstants
 import android.view.MotionEvent
 import com.android.launcher3.icons.IconProvider
-import com.android.systemui.Flags.constraintBp
 import com.android.systemui.biometrics.UdfpsUtils
 import com.android.systemui.biometrics.Utils
 import com.android.systemui.biometrics.Utils.isSystem
@@ -470,7 +469,7 @@
         promptSelectorInteractor.prompt
             .map {
                 when {
-                    !(customBiometricPrompt() && constraintBp()) || it == null -> Pair(null, "")
+                    !(customBiometricPrompt()) || it == null -> Pair(null, "")
                     else -> context.getUserBadgedLogoInfo(it, iconProvider, activityTaskManager)
                 }
             }
@@ -487,7 +486,7 @@
     /** Custom content view for the prompt. */
     val contentView: Flow<PromptContentView?> =
         promptSelectorInteractor.prompt
-            .map { if (customBiometricPrompt() && constraintBp()) it?.contentView else null }
+            .map { if (customBiometricPrompt()) it?.contentView else null }
             .distinctUntilChanged()
 
     private val originalDescription =
@@ -833,7 +832,7 @@
         messageJob?.cancel()
         messageJob = null
 
-        if (helpMessage.isNotBlank()) {
+        if (helpMessage.isNotBlank() && needsUserConfirmation) {
             showHelp(helpMessage)
         }
     }
@@ -1045,7 +1044,7 @@
     val packageName =
         when {
             componentNameForLogo != null -> componentNameForLogo.packageName
-            // TODO(b/339532378): We should check whether |allowBackgroundAuthentication| should be
+            // TODO(b/353597496): We should check whether |allowBackgroundAuthentication| should be
             // removed.
             // This is being consistent with the check in [AuthController.showDialog()].
             allowBackgroundAuthentication || isSystem(context, opPackageName) -> opPackageName
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt
index 19ea007..c2a4ee3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt
@@ -28,7 +28,6 @@
 import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
 import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
 import com.airbnb.lottie.model.KeyPath
-import com.android.systemui.Flags.constraintBp
 import com.android.systemui.biometrics.Utils
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
 import com.android.systemui.biometrics.domain.interactor.SideFpsSensorInteractor
@@ -152,21 +151,6 @@
             ->
             val topLeft = Point(sensorLocation.left, sensorLocation.top)
 
-            if (!constraintBp()) {
-                if (sensorLocation.isSensorVerticalInDefaultOrientation) {
-                    if (displayRotation == DisplayRotation.ROTATION_0) {
-                        topLeft.x -= bounds!!.width()
-                    } else if (displayRotation == DisplayRotation.ROTATION_270) {
-                        topLeft.y -= bounds!!.height()
-                    }
-                } else {
-                    if (displayRotation == DisplayRotation.ROTATION_180) {
-                        topLeft.y -= bounds!!.height()
-                    } else if (displayRotation == DisplayRotation.ROTATION_270) {
-                        topLeft.x -= bounds!!.width()
-                    }
-                }
-            }
             defaultOverlayViewParams.apply {
                 x = topLeft.x
                 y = topLeft.y
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 4dafa93..9d82e76 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt
@@ -297,7 +297,7 @@
 
         val DeviceItem.isMediaDevice: Boolean
             get() =
-                cachedBluetoothDevice.connectableProfiles.any {
+                cachedBluetoothDevice.uiAccessibleProfiles.any {
                     it is A2dpProfile ||
                         it is HearingAidProfile ||
                         it is LeAudioProfile ||
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 6aaaf3d..fbeb715 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
@@ -184,7 +184,11 @@
         keyguardTransitionInteractor.startedKeyguardTransitionStep
             .filter { step -> step.to == KeyguardState.OCCLUDED }
             .combine(isCommunalAvailable, ::Pair)
-            .map { (step, available) -> available && step.from == KeyguardState.GLANCEABLE_HUB }
+            .map { (step, available) ->
+                available &&
+                    (step.from == KeyguardState.GLANCEABLE_HUB ||
+                        step.from == KeyguardState.DREAMING)
+            }
             .flowOn(bgDispatcher)
             .stateIn(
                 scope = applicationScope,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalAppWidgetHostViewBinder.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalAppWidgetHostViewBinder.kt
new file mode 100644
index 0000000..7a05671
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalAppWidgetHostViewBinder.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.communal.ui.binder
+
+import android.content.Context
+import android.util.SizeF
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import com.android.app.tracing.coroutines.launch
+import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.util.WidgetViewFactory
+import com.android.systemui.util.kotlin.DisposableHandles
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.DisposableHandle
+
+object CommunalAppWidgetHostViewBinder {
+    private const val TAG = "CommunalAppWidgetHostViewBinder"
+
+    fun bind(
+        context: Context,
+        applicationScope: CoroutineScope,
+        container: FrameLayout,
+        model: CommunalContentModel.WidgetContent.Widget,
+        size: SizeF,
+        factory: WidgetViewFactory,
+    ): DisposableHandle {
+        val disposables = DisposableHandles()
+
+        val loadingJob =
+            applicationScope.launch("$TAG#createWidgetView") {
+                val widget = factory.createWidget(context, model, size)
+                // TODO(b/358662507): Remove this workaround
+                (container.parent as? ViewGroup)?.let { parent ->
+                    val index = parent.indexOfChild(container)
+                    parent.removeView(container)
+                    parent.addView(container, index)
+                }
+                container.setView(widget)
+            }
+
+        disposables += DisposableHandle { loadingJob.cancel() }
+        disposables += DisposableHandle { container.removeAllViews() }
+
+        return disposables
+    }
+}
+
+private fun ViewGroup.setView(view: View) {
+    if (view.parent == this) {
+        return
+    }
+    (view.parent as? ViewGroup)?.removeView(view)
+    addView(view)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalAppWidgetSection.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalAppWidgetSection.kt
new file mode 100644
index 0000000..56b769e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalAppWidgetSection.kt
@@ -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.systemui.communal.ui.view.layout.sections
+
+import android.util.SizeF
+import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+import android.widget.FrameLayout
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.ui.binder.CommunalAppWidgetHostViewBinder
+import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
+import com.android.systemui.communal.util.WidgetViewFactory
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.res.R
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.DisposableHandle
+
+class CommunalAppWidgetSection
+@Inject
+constructor(
+    @Application private val applicationScope: CoroutineScope,
+    private val factory: WidgetViewFactory,
+) {
+
+    private companion object {
+        val DISPOSABLE_TAG = R.id.communal_widget_disposable_tag
+    }
+
+    @Composable
+    fun Widget(
+        viewModel: BaseCommunalViewModel,
+        model: CommunalContentModel.WidgetContent.Widget,
+        size: SizeF,
+        modifier: Modifier = Modifier,
+    ) {
+        val isFocusable by viewModel.isFocusable.collectAsStateWithLifecycle(initialValue = false)
+
+        AndroidView(
+            factory = { context ->
+                FrameLayout(context).apply {
+                    layoutParams =
+                        FrameLayout.LayoutParams(
+                            FrameLayout.LayoutParams.MATCH_PARENT,
+                            FrameLayout.LayoutParams.MATCH_PARENT,
+                        )
+
+                    // Need to attach the disposable handle to the view here instead of storing
+                    // the state in the composable in order to properly support lazy lists. In a
+                    // lazy list, when the composable is no longer in view - it will exit
+                    // composition and any state inside the composable will be lost. However,
+                    // the View instance will be re-used. Therefore we can store data on the view
+                    // in order to preserve it.
+                    setTag(
+                        DISPOSABLE_TAG,
+                        CommunalAppWidgetHostViewBinder.bind(
+                            context = context,
+                            container = this,
+                            model = model,
+                            size = size,
+                            factory = factory,
+                            applicationScope = applicationScope,
+                        )
+                    )
+
+                    accessibilityDelegate = viewModel.widgetAccessibilityDelegate
+                }
+            },
+            update = { container ->
+                container.importantForAccessibility =
+                    if (isFocusable) {
+                        IMPORTANT_FOR_ACCESSIBILITY_AUTO
+                    } else {
+                        IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+                    }
+            },
+            onRelease = { view ->
+                val disposable = (view.getTag(DISPOSABLE_TAG) as? DisposableHandle)
+                disposable?.dispose()
+            },
+            modifier = modifier,
+            // For reusing composition in lazy lists.
+            onReset = {},
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt b/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt
new file mode 100644
index 0000000..0e39a99
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.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.communal.util
+
+import android.content.Context
+import android.os.Bundle
+import android.util.SizeF
+import com.android.app.tracing.coroutines.withContext
+import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
+import com.android.systemui.communal.widgets.CommunalAppWidgetHostView
+import com.android.systemui.dagger.qualifiers.UiBackground
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+
+/** Factory for creating [CommunalAppWidgetHostView] in a background thread. */
+class WidgetViewFactory
+@Inject
+constructor(
+    @UiBackground private val uiBgContext: CoroutineContext,
+    private val appWidgetHost: CommunalAppWidgetHost,
+) {
+    suspend fun createWidget(
+        context: Context,
+        model: CommunalContentModel.WidgetContent.Widget,
+        size: SizeF,
+    ): CommunalAppWidgetHostView =
+        withContext("$TAG#createWidget", uiBgContext) {
+            appWidgetHost
+                .createViewForCommunal(context, model.appWidgetId, model.providerInfo)
+                .apply {
+                    updateAppWidgetSize(
+                        /* newOptions = */ Bundle(),
+                        /* minWidth = */ size.width.toInt(),
+                        /* minHeight = */ size.height.toInt(),
+                        /* maxWidth = */ size.width.toInt(),
+                        /* maxHeight = */ size.height.toInt(),
+                        /* ignorePadding = */ true,
+                    )
+                }
+        }
+
+    private companion object {
+        const val TAG = "WidgetViewFactory"
+    }
+}
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 03ef17b..668fef6 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
@@ -39,6 +42,7 @@
 import com.android.systemui.communal.shared.model.CommunalTransitionKeys
 import com.android.systemui.communal.shared.model.EditModeState
 import com.android.systemui.communal.ui.compose.CommunalHub
+import com.android.systemui.communal.ui.view.layout.sections.CommunalAppWidgetSection
 import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
 import com.android.systemui.communal.util.WidgetPickerIntentUtils.getWidgetExtraFromIntent
 import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -57,6 +61,7 @@
     private var windowManagerService: IWindowManager? = null,
     private val uiEventLogger: UiEventLogger,
     private val widgetConfiguratorFactory: WidgetConfigurationController.Factory,
+    private val widgetSection: CommunalAppWidgetSection,
     @CommunalLog logBuffer: LogBuffer,
 ) : ComponentActivity() {
     companion object {
@@ -66,12 +71,78 @@
         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
+     */
+    class ActivityController(activity: Activity) {
+        companion object {
+            private const val STATE_EXTRA_IS_WAITING_FOR_RESULT = "extra_is_waiting_for_result"
+        }
+
+        private var waitingForResult: Boolean = 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 resul (either widget
+                        // selection
+                        // or configuration), finish activity.
+                        if (!waitingForResult) {
+                            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.
+                    }
+                }
+            )
+        }
+
+        /**
+         * 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) {
+            this.waitingForResult = waitingForResult
+        }
+    }
+
     private val logger = Logger(logBuffer, "EditWidgetsActivity")
 
     private val widgetConfigurator by lazy { widgetConfiguratorFactory.create(this) }
 
     private var shouldOpenWidgetPickerOnStart = false
 
+    private val activityController: ActivityController = ActivityController(this)
+
     private val addWidgetActivityLauncher: ActivityResultLauncher<Intent> =
         registerForActivityResult(StartActivityForResult()) { result ->
             when (result.resultCode) {
@@ -135,6 +206,7 @@
                         onOpenWidgetPicker = ::onOpenWidgetPicker,
                         widgetConfigurator = widgetConfigurator,
                         onEditDone = ::onEditDone,
+                        widgetSection = widgetSection,
                     )
                 }
             }
@@ -154,6 +226,13 @@
                 // edit mode
                 communalViewModel.currentScene.first { it == CommunalScenes.Blank }
                 communalViewModel.setEditModeState(EditModeState.SHOWING)
+
+                // 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) {
+                    onOpenWidgetPicker()
+                    shouldOpenWidgetPickerOnStart = false
+                }
             }
         }
     }
@@ -186,7 +265,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)
@@ -198,11 +304,6 @@
 
         communalViewModel.setEditActivityShowing(true)
 
-        if (shouldOpenWidgetPickerOnStart) {
-            onOpenWidgetPicker()
-            shouldOpenWidgetPickerOnStart = false
-        }
-
         logger.i("Starting the communal widget editor activity")
         uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_EDIT_MODE_SHOWN)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index 3273111..79f4568 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -33,6 +33,7 @@
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dock.DockManagerImpl;
 import com.android.systemui.doze.DozeHost;
+import com.android.systemui.inputdevice.tutorial.KeyboardTouchpadTutorialModule;
 import com.android.systemui.keyboard.shortcut.ShortcutHelperModule;
 import com.android.systemui.keyguard.ui.composable.blueprint.DefaultBlueprintModule;
 import com.android.systemui.keyguard.ui.view.layout.blueprints.KeyguardBlueprintModule;
@@ -77,7 +78,7 @@
 import com.android.systemui.statusbar.policy.SensorPrivacyController;
 import com.android.systemui.statusbar.policy.SensorPrivacyControllerImpl;
 import com.android.systemui.toast.ToastModule;
-import com.android.systemui.touchpad.tutorial.TouchpadKeyboardTutorialModule;
+import com.android.systemui.touchpad.tutorial.TouchpadTutorialModule;
 import com.android.systemui.unfold.SysUIUnfoldStartableModule;
 import com.android.systemui.unfold.UnfoldTransitionModule;
 import com.android.systemui.util.kotlin.SysUICoroutinesModule;
@@ -122,6 +123,7 @@
         KeyboardShortcutsModule.class,
         KeyguardBlueprintModule.class,
         KeyguardSectionsModule.class,
+        KeyboardTouchpadTutorialModule.class,
         MediaModule.class,
         MediaMuteAwaitConnectionCli.StartableModule.class,
         MultiUserUtilsModule.class,
@@ -142,7 +144,7 @@
         SysUIUnfoldStartableModule.class,
         UnfoldTransitionModule.Startables.class,
         ToastModule.class,
-        TouchpadKeyboardTutorialModule.class,
+        TouchpadTutorialModule.class,
         VolumeModule.class,
         WallpaperModule.class,
         ShortcutHelperModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 4286646..cbea876 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -30,7 +30,7 @@
 import com.android.systemui.dreams.DreamMonitor
 import com.android.systemui.dreams.homecontrols.HomeControlsDreamStartable
 import com.android.systemui.globalactions.GlobalActionsComponent
-import com.android.systemui.inputdevice.oobe.KeyboardTouchpadOobeTutorialCoreStartable
+import com.android.systemui.inputdevice.tutorial.KeyboardTouchpadTutorialCoreStartable
 import com.android.systemui.keyboard.KeyboardUI
 import com.android.systemui.keyboard.PhysicalKeyboardCoreStartable
 import com.android.systemui.keyguard.KeyguardViewConfigurator
@@ -258,13 +258,6 @@
 
     @Binds
     @IntoMap
-    @ClassKey(KeyboardTouchpadOobeTutorialCoreStartable::class)
-    abstract fun bindOobeSchedulerCoreStartable(
-        listener: KeyboardTouchpadOobeTutorialCoreStartable
-    ): CoreStartable
-
-    @Binds
-    @IntoMap
     @ClassKey(PhysicalKeyboardCoreStartable::class)
     abstract fun bindKeyboardCoreStartable(listener: PhysicalKeyboardCoreStartable): CoreStartable
 
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 609aa39..25b6b14 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -132,7 +132,6 @@
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.ConfigurationControllerModule;
 import com.android.systemui.statusbar.phone.LetterboxModule;
-import com.android.systemui.statusbar.phone.NotificationIconAreaControllerModule;
 import com.android.systemui.statusbar.pipeline.dagger.StatusBarPipelineModule;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -230,7 +229,6 @@
         MediaProjectionTaskSwitcherModule.class,
         MediaRouterModule.class,
         MotionToolModule.class,
-        NotificationIconAreaControllerModule.class,
         PeopleHubModule.class,
         PeopleModule.class,
         PluginModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt
index 195aa5f..28db3b8 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt
@@ -20,6 +20,7 @@
 import android.content.Intent
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.deviceentry.shared.model.BiometricMessage
@@ -33,6 +34,7 @@
 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.util.kotlin.combine
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -64,6 +66,7 @@
     activityStarter: ActivityStarter,
     powerInteractor: PowerInteractor,
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    communalSceneInteractor: CommunalSceneInteractor,
 ) {
     private val keyguardOccludedByApp: Flow<Boolean> =
         if (KeyguardWmStateRefactor.isEnabled) {
@@ -75,12 +78,20 @@
                     primaryBouncerInteractor.isShowing,
                     alternateBouncerInteractor.isVisible,
                     keyguardInteractor.isDozing,
-                ) { occluded, showing, primaryBouncerShowing, alternateBouncerVisible, dozing ->
+                    communalSceneInteractor.isIdleOnCommunal,
+                ) {
+                    occluded,
+                    showing,
+                    primaryBouncerShowing,
+                    alternateBouncerVisible,
+                    dozing,
+                    isIdleOnCommunal ->
                     occluded &&
                         showing &&
                         !primaryBouncerShowing &&
                         !alternateBouncerVisible &&
-                        !dozing
+                        !dozing &&
+                        !isIdleOnCommunal
                 }
                 .distinctUntilChanged()
         }
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 69ddb62..40e2f17 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
@@ -160,7 +160,10 @@
                     .stateIn(
                         bgApplicationScope,
                         SharingStarted.WhileSubscribed(),
-                        emptySet(),
+                        // This is necessary because there might be multiple displays, and we could
+                        // have missed events for those added before this process or flow started.
+                        // Note it causes a binder call from the main thread (it's traced).
+                        getDisplays().map { display -> display.displayId }.toSet(),
                     )
             } else {
                 oldEnabledDisplays.map { enabledDisplaysSet ->
@@ -186,8 +189,12 @@
                 .stateIn(
                     bgApplicationScope,
                     started = SharingStarted.WhileSubscribed(),
-                    initialValue = setOf(defaultDisplay)
-                )
+                    // This triggers a single binder call on the UI thread per process. The
+                    // alternative would be to use sharedFlows, but they are prohibited due to
+                    // performance concerns.
+                    // Ultimately, this is a trade-off between a one-time UI thread binder call and
+                    // the constant overhead of sharedFlows.
+                    initialValue = getDisplays())
         } else {
             oldEnabledDisplays
         }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeBrightnessHostForwarder.java b/packages/SystemUI/src/com/android/systemui/doze/DozeBrightnessHostForwarder.java
index 0b33614..6fd4d33 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeBrightnessHostForwarder.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeBrightnessHostForwarder.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.doze;
 
-import java.util.concurrent.Executor;
-
 /**
  * Forwards the currently used brightness to {@link DozeHost}.
  */
@@ -25,9 +23,8 @@
 
     private final DozeHost mHost;
 
-    public DozeBrightnessHostForwarder(DozeMachine.Service wrappedService, DozeHost host,
-            Executor bgExecutor) {
-        super(wrappedService, bgExecutor);
+    public DozeBrightnessHostForwarder(DozeMachine.Service wrappedService, DozeHost host) {
+        super(wrappedService);
         mHost = host;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index 5bfcc97..cdcb03e 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -253,9 +253,11 @@
     /**
      * Appends display state changed event to the logs
      * @param displayState new DozeMachine state
+     * @param afterRequest whether the request has successfully been sent else false for it's
+     *                        about to be requested
      */
-    public void traceDisplayState(int displayState) {
-        mLogger.logDisplayStateChanged(displayState);
+    public void traceDisplayState(int displayState, boolean afterRequest) {
+        mLogger.logDisplayStateChanged(displayState, afterRequest);
     }
 
     /**
@@ -402,18 +404,22 @@
     /**
      * Appends new AOD screen brightness to logs
      * @param brightness display brightness setting between 1 and 255
+     * @param afterRequest whether the request has successfully been sent else false for it's
+     *                        about to be requested
      */
-    public void traceDozeScreenBrightness(int brightness) {
-        mLogger.logDozeScreenBrightness(brightness);
+    public void traceDozeScreenBrightness(int brightness, boolean afterRequest) {
+        mLogger.logDozeScreenBrightness(brightness, afterRequest);
     }
 
     /**
      * Appends new AOD screen brightness to logs
      * @param brightness display brightness setting between {@link PowerManager#BRIGHTNESS_MIN} and
      *                   {@link PowerManager#BRIGHTNESS_MAX}
+     * @param afterRequest whether the request has successfully been sent else false for it's
+     *                        about to be requested
      */
-    public void traceDozeScreenBrightnessFloat(float brightness) {
-        mLogger.logDozeScreenBrightnessFloat(brightness);
+    public void traceDozeScreenBrightnessFloat(float brightness, boolean afterRequest) {
+        mLogger.logDozeScreenBrightnessFloat(brightness, afterRequest);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
index a31dbec..7128731 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
@@ -32,23 +32,18 @@
 import javax.inject.Inject
 
 /** Interface for logging messages to the [DozeLog]. */
-class DozeLogger @Inject constructor(
-    @DozeLog private val buffer: LogBuffer
-) {
+class DozeLogger @Inject constructor(@DozeLog private val buffer: LogBuffer) {
     fun logPickupWakeup(isWithinVibrationThreshold: Boolean) {
-        buffer.log(TAG, DEBUG, {
-            bool1 = isWithinVibrationThreshold
-        }, {
-            "PickupWakeup withinVibrationThreshold=$bool1"
-        })
+        buffer.log(
+            TAG,
+            DEBUG,
+            { bool1 = isWithinVibrationThreshold },
+            { "PickupWakeup withinVibrationThreshold=$bool1" }
+        )
     }
 
     fun logPulseStart(@Reason reason: Int) {
-        buffer.log(TAG, INFO, {
-            int1 = reason
-        }, {
-            "Pulse start, reason=${reasonToString(int1)}"
-        })
+        buffer.log(TAG, INFO, { int1 = reason }, { "Pulse start, reason=${reasonToString(int1)}" })
     }
 
     fun logPulseFinish() {
@@ -60,52 +55,51 @@
     }
 
     fun logDozing(isDozing: Boolean) {
-        buffer.log(TAG, INFO, {
-            bool1 = isDozing
-        }, {
-            "Dozing=$bool1"
-        })
+        buffer.log(TAG, INFO, { bool1 = isDozing }, { "Dozing=$bool1" })
     }
 
     fun logDozingChanged(isDozing: Boolean) {
-        buffer.log(TAG, INFO, {
-            bool1 = isDozing
-        }, {
-            "Dozing changed dozing=$bool1"
-        })
+        buffer.log(TAG, INFO, { bool1 = isDozing }, { "Dozing changed dozing=$bool1" })
     }
 
     fun logPowerSaveChanged(powerSaveActive: Boolean, nextState: DozeMachine.State) {
-        buffer.log(TAG, INFO, {
-            bool1 = powerSaveActive
-            str1 = nextState.name
-        }, {
-            "Power save active=$bool1 nextState=$str1"
-        })
+        buffer.log(
+            TAG,
+            INFO,
+            {
+                bool1 = powerSaveActive
+                str1 = nextState.name
+            },
+            { "Power save active=$bool1 nextState=$str1" }
+        )
     }
 
     fun logAlwaysOnSuppressedChange(isAodSuppressed: Boolean, nextState: DozeMachine.State) {
-        buffer.log(TAG, INFO, {
-            bool1 = isAodSuppressed
-            str1 = nextState.name
-        }, {
-            "Always on (AOD) suppressed changed, suppressed=$bool1 nextState=$str1"
-        })
+        buffer.log(
+            TAG,
+            INFO,
+            {
+                bool1 = isAodSuppressed
+                str1 = nextState.name
+            },
+            { "Always on (AOD) suppressed changed, suppressed=$bool1 nextState=$str1" }
+        )
     }
 
-    fun logFling(
-        expand: Boolean,
-        aboveThreshold: Boolean,
-        screenOnFromTouch: Boolean
-    ) {
-        buffer.log(TAG, DEBUG, {
-            bool1 = expand
-            bool2 = aboveThreshold
-            bool4 = screenOnFromTouch
-        }, {
-            "Fling expand=$bool1 aboveThreshold=$bool2 thresholdNeeded=$bool3 " +
-                "screenOnFromTouch=$bool4"
-        })
+    fun logFling(expand: Boolean, aboveThreshold: Boolean, screenOnFromTouch: Boolean) {
+        buffer.log(
+            TAG,
+            DEBUG,
+            {
+                bool1 = expand
+                bool2 = aboveThreshold
+                bool4 = screenOnFromTouch
+            },
+            {
+                "Fling expand=$bool1 aboveThreshold=$bool2 thresholdNeeded=$bool3 " +
+                    "screenOnFromTouch=$bool4"
+            }
+        )
     }
 
     fun logEmergencyCall() {
@@ -113,280 +107,314 @@
     }
 
     fun logKeyguardBouncerChanged(isShowing: Boolean) {
-        buffer.log(TAG, INFO, {
-            bool1 = isShowing
-        }, {
-            "Keyguard bouncer changed, showing=$bool1"
-        })
+        buffer.log(TAG, INFO, { bool1 = isShowing }, { "Keyguard bouncer changed, showing=$bool1" })
     }
 
     fun logScreenOn(isPulsing: Boolean) {
-        buffer.log(TAG, INFO, {
-            bool1 = isPulsing
-        }, {
-            "Screen on, pulsing=$bool1"
-        })
+        buffer.log(TAG, INFO, { bool1 = isPulsing }, { "Screen on, pulsing=$bool1" })
     }
 
     fun logScreenOff(why: Int) {
-        buffer.log(TAG, INFO, {
-            int1 = why
-        }, {
-            "Screen off, why=$int1"
-        })
+        buffer.log(TAG, INFO, { int1 = why }, { "Screen off, why=$int1" })
     }
 
     fun logMissedTick(delay: String) {
-        buffer.log(TAG, ERROR, {
-            str1 = delay
-        }, {
-            "Missed AOD time tick by $str1"
-        })
+        buffer.log(TAG, ERROR, { str1 = delay }, { "Missed AOD time tick by $str1" })
     }
 
     fun logTimeTickScheduled(whenAt: Long, triggerAt: Long) {
-        buffer.log(TAG, DEBUG, {
-            long1 = whenAt
-            long2 = triggerAt
-        }, {
-            "Time tick scheduledAt=${DATE_FORMAT.format(Date(long1))} " +
-                "triggerAt=${DATE_FORMAT.format(Date(long2))}"
-        })
+        buffer.log(
+            TAG,
+            DEBUG,
+            {
+                long1 = whenAt
+                long2 = triggerAt
+            },
+            {
+                "Time tick scheduledAt=${DATE_FORMAT.format(Date(long1))} " +
+                    "triggerAt=${DATE_FORMAT.format(Date(long2))}"
+            }
+        )
     }
 
     fun logKeyguardVisibilityChange(isVisible: Boolean) {
-        buffer.log(TAG, INFO, {
-            bool1 = isVisible
-        }, {
-            "Keyguard visibility change, isVisible=$bool1"
-        })
+        buffer.log(
+            TAG,
+            INFO,
+            { bool1 = isVisible },
+            { "Keyguard visibility change, isVisible=$bool1" }
+        )
     }
 
     fun logPendingUnscheduleTimeTick(isPending: Boolean, isTimeTickScheduled: Boolean) {
-        buffer.log(TAG, INFO, {
-            bool1 = isPending
-            bool2 = isTimeTickScheduled
-        }, {
-            "Pending unschedule time tick, isPending=$bool1, isTimeTickScheduled:$bool2"
-        })
+        buffer.log(
+            TAG,
+            INFO,
+            {
+                bool1 = isPending
+                bool2 = isTimeTickScheduled
+            },
+            { "Pending unschedule time tick, isPending=$bool1, isTimeTickScheduled:$bool2" }
+        )
     }
 
     fun logDozeStateChanged(state: DozeMachine.State) {
-        buffer.log(TAG, INFO, {
-            str1 = state.name
-        }, {
-            "Doze state changed to $str1"
-        })
+        buffer.log(TAG, INFO, { str1 = state.name }, { "Doze state changed to $str1" })
     }
 
     fun logStateChangedSent(state: DozeMachine.State) {
-        buffer.log(TAG, INFO, {
-            str1 = state.name
-        }, {
-            "Doze state sent to all DozeMachineParts stateSent=$str1"
-        })
+        buffer.log(
+            TAG,
+            INFO,
+            { str1 = state.name },
+            { "Doze state sent to all DozeMachineParts stateSent=$str1" }
+        )
     }
 
     fun logDisplayStateDelayedByUdfps(delayedDisplayState: Int) {
-        buffer.log(TAG, INFO, {
-            str1 = Display.stateToString(delayedDisplayState)
-        }, {
-            "Delaying display state change to: $str1 due to UDFPS activity"
-        })
+        buffer.log(
+            TAG,
+            INFO,
+            { str1 = Display.stateToString(delayedDisplayState) },
+            { "Delaying display state change to: $str1 due to UDFPS activity" }
+        )
     }
 
-    fun logDisplayStateChanged(displayState: Int) {
-        buffer.log(TAG, INFO, {
-            str1 = Display.stateToString(displayState)
-        }, {
-            "Display state changed to $str1"
-        })
+    fun logDisplayStateChanged(displayState: Int, afterRequest: Boolean) {
+        buffer.log(
+            TAG,
+            INFO,
+            {
+                str1 = Display.stateToString(displayState)
+                bool1 = afterRequest
+            },
+            { "Display state ${if (bool1) "changed" else "requested"} to $str1" }
+        )
     }
 
     fun logWakeDisplay(isAwake: Boolean, @Reason reason: Int) {
-        buffer.log(TAG, DEBUG, {
-            bool1 = isAwake
-            int1 = reason
-        }, {
-            "Display wakefulness changed, isAwake=$bool1, reason=${reasonToString(int1)}"
-        })
+        buffer.log(
+            TAG,
+            DEBUG,
+            {
+                bool1 = isAwake
+                int1 = reason
+            },
+            { "Display wakefulness changed, isAwake=$bool1, reason=${reasonToString(int1)}" }
+        )
     }
 
     fun logProximityResult(isNear: Boolean, millis: Long, @Reason reason: Int) {
-        buffer.log(TAG, DEBUG, {
-            bool1 = isNear
-            long1 = millis
-            int1 = reason
-        }, {
-            "Proximity result reason=${reasonToString(int1)} near=$bool1 millis=$long1"
-        })
+        buffer.log(
+            TAG,
+            DEBUG,
+            {
+                bool1 = isNear
+                long1 = millis
+                int1 = reason
+            },
+            { "Proximity result reason=${reasonToString(int1)} near=$bool1 millis=$long1" }
+        )
     }
 
     fun logPostureChanged(posture: Int, partUpdated: String) {
-        buffer.log(TAG, INFO, {
-            int1 = posture
-            str1 = partUpdated
-        }, {
-            "Posture changed, posture=${DevicePostureController.devicePostureToString(int1)}" +
-                " partUpdated=$str1"
-        })
+        buffer.log(
+            TAG,
+            INFO,
+            {
+                int1 = posture
+                str1 = partUpdated
+            },
+            {
+                "Posture changed, posture=${DevicePostureController.devicePostureToString(int1)}" +
+                    " partUpdated=$str1"
+            }
+        )
     }
 
     /**
-     * Log why a pulse was dropped and the current doze machine state. The state can be null
-     * if the DozeMachine is the middle of transitioning between states.
+     * Log why a pulse was dropped and the current doze machine state. The state can be null if the
+     * DozeMachine is the middle of transitioning between states.
      */
     fun logPulseDropped(from: String, state: DozeMachine.State?) {
-        buffer.log(TAG, INFO, {
-            str1 = from
-            str2 = state?.name
-        }, {
-            "Pulse dropped, cannot pulse from=$str1 state=$str2"
-        })
+        buffer.log(
+            TAG,
+            INFO,
+            {
+                str1 = from
+                str2 = state?.name
+            },
+            { "Pulse dropped, cannot pulse from=$str1 state=$str2" }
+        )
     }
 
     fun logSensorEventDropped(sensorEvent: Int, reason: String) {
-        buffer.log(TAG, INFO, {
-            int1 = sensorEvent
-            str1 = reason
-        }, {
-            "SensorEvent [$int1] dropped, reason=$str1"
-        })
+        buffer.log(
+            TAG,
+            INFO,
+            {
+                int1 = sensorEvent
+                str1 = reason
+            },
+            { "SensorEvent [$int1] dropped, reason=$str1" }
+        )
     }
 
     fun logPulseEvent(pulseEvent: String, dozing: Boolean, pulseReason: String) {
-        buffer.log(TAG, DEBUG, {
-            str1 = pulseEvent
-            bool1 = dozing
-            str2 = pulseReason
-        }, {
-            "Pulse-$str1 dozing=$bool1 pulseReason=$str2"
-        })
+        buffer.log(
+            TAG,
+            DEBUG,
+            {
+                str1 = pulseEvent
+                bool1 = dozing
+                str2 = pulseReason
+            },
+            { "Pulse-$str1 dozing=$bool1 pulseReason=$str2" }
+        )
     }
 
     fun logPulseDropped(reason: String) {
-        buffer.log(TAG, INFO, {
-            str1 = reason
-        }, {
-            "Pulse dropped, why=$str1"
-        })
+        buffer.log(TAG, INFO, { str1 = reason }, { "Pulse dropped, why=$str1" })
     }
 
     fun logPulseTouchDisabledByProx(disabled: Boolean) {
-        buffer.log(TAG, DEBUG, {
-            bool1 = disabled
-        }, {
-            "Pulse touch modified by prox, disabled=$bool1"
-        })
+        buffer.log(
+            TAG,
+            DEBUG,
+            { bool1 = disabled },
+            { "Pulse touch modified by prox, disabled=$bool1" }
+        )
     }
 
     fun logSensorTriggered(@Reason reason: Int) {
-        buffer.log(TAG, DEBUG, {
-            int1 = reason
-        }, {
-            "Sensor triggered, type=${reasonToString(int1)}"
-        })
+        buffer.log(
+            TAG,
+            DEBUG,
+            { int1 = reason },
+            { "Sensor triggered, type=${reasonToString(int1)}" }
+        )
     }
 
     fun logAlwaysOnSuppressed(state: DozeMachine.State, reason: String) {
-        buffer.log(TAG, INFO, {
-            str1 = state.name
-            str2 = reason
-        }, {
-            "Always-on state suppressed, suppressed state=$str1 reason=$str2"
-        })
+        buffer.log(
+            TAG,
+            INFO,
+            {
+                str1 = state.name
+                str2 = reason
+            },
+            { "Always-on state suppressed, suppressed state=$str1 reason=$str2" }
+        )
     }
 
     fun logImmediatelyEndDoze(reason: String) {
-        buffer.log(TAG, INFO, {
-            str1 = reason
-        }, {
-            "Doze immediately ended due to $str1"
-        })
+        buffer.log(TAG, INFO, { str1 = reason }, { "Doze immediately ended due to $str1" })
     }
 
-    fun logDozeScreenBrightness(brightness: Int) {
-        buffer.log(TAG, INFO, {
-            int1 = brightness
-        }, {
-            "Doze screen brightness set (int), brightness=$int1"
-        })
+    fun logDozeScreenBrightness(brightness: Int, afterRequest: Boolean) {
+        buffer.log(
+            TAG,
+            INFO,
+            {
+                int1 = brightness
+                bool1 = afterRequest
+            },
+            {
+                "Doze screen brightness ${if (bool1) "set" else "requested"}" +
+                    " (int), brightness=$int1"
+            }
+        )
     }
 
-    fun logDozeScreenBrightnessFloat(brightness: Float) {
-        buffer.log(TAG, INFO, {
-            double1 = brightness.toDouble()
-        }, {
-            "Doze screen brightness set (float), brightness=$double1"
-        })
+    fun logDozeScreenBrightnessFloat(brightness: Float, afterRequest: Boolean) {
+        buffer.log(
+            TAG,
+            INFO,
+            {
+                double1 = brightness.toDouble()
+                bool1 = afterRequest
+            },
+            {
+                "Doze screen brightness ${if (bool1) "set" else "requested"}" +
+                    " (float), brightness=$double1"
+            }
+        )
     }
 
     fun logSetAodDimmingScrim(scrimOpacity: Long) {
-        buffer.log(TAG, INFO, {
-            long1 = scrimOpacity
-        }, {
-            "Doze aod dimming scrim opacity set, opacity=$long1"
-        })
+        buffer.log(
+            TAG,
+            INFO,
+            { long1 = scrimOpacity },
+            { "Doze aod dimming scrim opacity set, opacity=$long1" }
+        )
     }
 
     fun logCarModeEnded() {
-        buffer.log(TAG, INFO, {}, {
-            "Doze car mode ended"
-        })
+        buffer.log(TAG, INFO, {}, { "Doze car mode ended" })
     }
 
     fun logCarModeStarted() {
-        buffer.log(TAG, INFO, {}, {
-            "Doze car mode started"
-        })
+        buffer.log(TAG, INFO, {}, { "Doze car mode started" })
     }
 
     fun logSensorRegisterAttempt(sensorInfo: String, successfulRegistration: Boolean) {
-        buffer.log(TAG, INFO, {
-            str1 = sensorInfo
-            bool1 = successfulRegistration
-        }, {
-            "Register sensor. Success=$bool1 sensor=$str1"
-        })
+        buffer.log(
+            TAG,
+            INFO,
+            {
+                str1 = sensorInfo
+                bool1 = successfulRegistration
+            },
+            { "Register sensor. Success=$bool1 sensor=$str1" }
+        )
     }
 
     fun logSensorUnregisterAttempt(sensorInfo: String, successfulUnregister: Boolean) {
-        buffer.log(TAG, INFO, {
-            str1 = sensorInfo
-            bool1 = successfulUnregister
-        }, {
-            "Unregister sensor. Success=$bool1 sensor=$str1"
-        })
+        buffer.log(
+            TAG,
+            INFO,
+            {
+                str1 = sensorInfo
+                bool1 = successfulUnregister
+            },
+            { "Unregister sensor. Success=$bool1 sensor=$str1" }
+        )
     }
 
     fun logSensorUnregisterAttempt(
-            sensorInfo: String,
-            successfulUnregister: Boolean,
-            reason: String
+        sensorInfo: String,
+        successfulUnregister: Boolean,
+        reason: String
     ) {
-        buffer.log(TAG, INFO, {
-            str1 = sensorInfo
-            bool1 = successfulUnregister
-            str2 = reason
-        }, {
-            "Unregister sensor. reason=$str2. Success=$bool1 sensor=$str1"
-        })
+        buffer.log(
+            TAG,
+            INFO,
+            {
+                str1 = sensorInfo
+                bool1 = successfulUnregister
+                str2 = reason
+            },
+            { "Unregister sensor. reason=$str2. Success=$bool1 sensor=$str1" }
+        )
     }
 
     fun logSkipSensorRegistration(sensor: String) {
-        buffer.log(TAG, DEBUG, {
-            str1 = sensor
-        }, {
-            "Skipping sensor registration because its already registered. sensor=$str1"
-        })
+        buffer.log(
+            TAG,
+            DEBUG,
+            { str1 = sensor },
+            { "Skipping sensor registration because its already registered. sensor=$str1" }
+        )
     }
 
     fun logSetIgnoreTouchWhilePulsing(ignoreTouchWhilePulsing: Boolean) {
-        buffer.log(TAG, DEBUG, {
-            bool1 = ignoreTouchWhilePulsing
-        }, {
-            "Prox changed while pulsing. setIgnoreTouchWhilePulsing=$bool1"
-        })
+        buffer.log(
+            TAG,
+            DEBUG,
+            { bool1 = ignoreTouchWhilePulsing },
+            { "Prox changed while pulsing. setIgnoreTouchWhilePulsing=$bool1" }
+        )
     }
 
     fun log(@CompileTimeConstant msg: String) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
index 8198ef4..e02e3fb 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
@@ -39,7 +39,6 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 
@@ -232,7 +231,6 @@
     }
 
     void onScreenState(int state) {
-        mDozeLog.traceDisplayState(state);
         for (Part part : mParts) {
             part.onScreenState(state);
         }
@@ -516,11 +514,9 @@
 
         class Delegate implements Service {
             private final Service mDelegate;
-            private final Executor mBgExecutor;
 
-            public Delegate(Service delegate, Executor bgExecutor) {
+            public Delegate(Service delegate) {
                 mDelegate = delegate;
-                mBgExecutor = bgExecutor;
             }
 
             @Override
@@ -540,16 +536,12 @@
 
             @Override
             public void setDozeScreenBrightness(int brightness) {
-                mBgExecutor.execute(() -> {
-                    mDelegate.setDozeScreenBrightness(brightness);
-                });
+                mDelegate.setDozeScreenBrightness(brightness);
             }
 
             @Override
             public void setDozeScreenBrightnessFloat(float brightness) {
-                mBgExecutor.execute(() -> {
-                    mDelegate.setDozeScreenBrightnessFloat(brightness);
-                });
+                mDelegate.setDozeScreenBrightnessFloat(brightness);
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java
index 8d44472..25c2c39 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java
@@ -22,16 +22,14 @@
 
 import com.android.systemui.statusbar.phone.DozeParameters;
 
-import java.util.concurrent.Executor;
-
 /**
  * Prevents usage of doze screen states on devices that don't support them.
  */
 public class DozeScreenStatePreventingAdapter extends DozeMachine.Service.Delegate {
 
     @VisibleForTesting
-    DozeScreenStatePreventingAdapter(DozeMachine.Service inner, Executor bgExecutor) {
-        super(inner, bgExecutor);
+    DozeScreenStatePreventingAdapter(DozeMachine.Service inner) {
+        super(inner);
     }
 
     @Override
@@ -49,8 +47,8 @@
      * return a new instance of {@link DozeScreenStatePreventingAdapter} wrapping {@code inner}.
      */
     public static DozeMachine.Service wrapIfNeeded(DozeMachine.Service inner,
-            DozeParameters params, Executor bgExecutor) {
-        return isNeeded(params) ? new DozeScreenStatePreventingAdapter(inner, bgExecutor) : inner;
+            DozeParameters params) {
+        return isNeeded(params) ? new DozeScreenStatePreventingAdapter(inner) : inner;
     }
 
     private static boolean isNeeded(DozeParameters params) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
index ba38ab0..2e372ff 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
@@ -23,6 +23,7 @@
 import android.service.dreams.DreamService;
 import android.util.Log;
 
+import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.doze.dagger.DozeComponent;
 import com.android.systemui.plugins.DozeServicePlugin;
 import com.android.systemui.plugins.DozeServicePlugin.RequestDoze;
@@ -31,6 +32,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 
@@ -43,9 +45,14 @@
     private DozeMachine mDozeMachine;
     private DozeServicePlugin mDozePlugin;
     private PluginManager mPluginManager;
+    private DozeLog mDozeLog;
+    private Executor mBgExecutor;
 
     @Inject
-    public DozeService(DozeComponent.Builder dozeComponentBuilder, PluginManager pluginManager) {
+    public DozeService(DozeComponent.Builder dozeComponentBuilder, PluginManager pluginManager,
+            DozeLog dozeLog, @UiBackground Executor bgExecutor) {
+        mDozeLog = dozeLog;
+        mBgExecutor = bgExecutor;
         mDozeComponentBuilder = dozeComponentBuilder;
         setDebug(DEBUG);
         mPluginManager = pluginManager;
@@ -143,9 +150,29 @@
 
     @Override
     public void setDozeScreenState(int state) {
+        mDozeLog.traceDisplayState(state, /* afterRequest */ false);
         super.setDozeScreenState(state);
+        mDozeLog.traceDisplayState(state, /* afterRequest */ true);
         if (mDozeMachine != null) {
             mDozeMachine.onScreenState(state);
         }
     }
+
+    @Override
+    public void setDozeScreenBrightness(int brightness) {
+        mBgExecutor.execute(() -> {
+            mDozeLog.traceDozeScreenBrightness(brightness, /* afterRequest */ false);
+            super.setDozeScreenBrightness(brightness);
+            mDozeLog.traceDozeScreenBrightness(brightness, /* afterRequest */ true);
+        });
+    }
+
+    @Override
+    public void setDozeScreenBrightnessFloat(float brightness) {
+        mBgExecutor.execute(() -> {
+            mDozeLog.traceDozeScreenBrightnessFloat(brightness, /* afterRequest */ false);
+            super.setDozeScreenBrightnessFloat(brightness);
+            mDozeLog.traceDozeScreenBrightnessFloat(brightness, /* afterRequest */ true);
+        });
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapter.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapter.java
index f7773f1..cfc952d 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapter.java
@@ -22,16 +22,14 @@
 
 import com.android.systemui.statusbar.phone.DozeParameters;
 
-import java.util.concurrent.Executor;
-
 /**
  * Prevents usage of doze screen states on devices that don't support them.
  */
 public class DozeSuspendScreenStatePreventingAdapter extends DozeMachine.Service.Delegate {
 
     @VisibleForTesting
-    DozeSuspendScreenStatePreventingAdapter(DozeMachine.Service inner, Executor bgExecutor) {
-        super(inner, bgExecutor);
+    DozeSuspendScreenStatePreventingAdapter(DozeMachine.Service inner) {
+        super(inner);
     }
 
     @Override
@@ -47,8 +45,8 @@
      * return a new instance of {@link DozeSuspendScreenStatePreventingAdapter} wrapping {@code inner}.
      */
     public static DozeMachine.Service wrapIfNeeded(DozeMachine.Service inner,
-            DozeParameters params, Executor bgExecutor) {
-        return isNeeded(params) ? new DozeSuspendScreenStatePreventingAdapter(inner, bgExecutor)
+            DozeParameters params) {
+        return isNeeded(params) ? new DozeSuspendScreenStatePreventingAdapter(inner)
                 : inner;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
index 8c3de4b..f383a04 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
@@ -19,7 +19,6 @@
 import android.content.Context;
 import android.hardware.Sensor;
 
-import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.doze.DozeAuthRemover;
 import com.android.systemui.doze.DozeBrightnessHostForwarder;
 import com.android.systemui.doze.DozeDockHandler;
@@ -51,7 +50,6 @@
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Optional;
-import java.util.concurrent.Executor;
 
 /** Dagger module for use with {@link com.android.systemui.doze.dagger.DozeComponent}. */
 @Module
@@ -60,13 +58,13 @@
     @DozeScope
     @WrappedService
     static DozeMachine.Service providesWrappedService(DozeMachine.Service dozeMachineService,
-            DozeHost dozeHost, DozeParameters dozeParameters, @UiBackground Executor bgExecutor) {
+            DozeHost dozeHost, DozeParameters dozeParameters) {
         DozeMachine.Service wrappedService = dozeMachineService;
-        wrappedService = new DozeBrightnessHostForwarder(wrappedService, dozeHost, bgExecutor);
+        wrappedService = new DozeBrightnessHostForwarder(wrappedService, dozeHost);
         wrappedService = DozeScreenStatePreventingAdapter.wrapIfNeeded(
-                wrappedService, dozeParameters, bgExecutor);
+                wrappedService, dozeParameters);
         wrappedService = DozeSuspendScreenStatePreventingAdapter.wrapIfNeeded(
-                wrappedService, dozeParameters, bgExecutor);
+                wrappedService, dozeParameters);
 
         return wrappedService;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
index b45ebd8..24ac542 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()
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 76c7d23..bf6d266 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);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 931066d..4b9e5a0 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -70,8 +70,12 @@
 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;
@@ -140,6 +144,8 @@
 
     private ComponentName mCurrentBlockedGestureDreamActivityComponent;
 
+    private final ArrayList<Job> mFlows = new ArrayList<>();
+
     /**
      * This {@link LifecycleRegistry} controls when dream overlay functionality, like touch
      * handling, should be active. It will automatically be paused when the dream overlay is hidden
@@ -309,12 +315,12 @@
 
         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,6 +345,11 @@
     public void onDestroy() {
         mKeyguardUpdateMonitor.removeCallback(mKeyguardCallback);
 
+        for (Job job : mFlows) {
+            job.cancel(new CancellationException());
+        }
+        mFlows.clear();
+
         mExecutor.execute(() -> {
             setLifecycleStateLocked(Lifecycle.State.DESTROYED);
 
@@ -559,6 +570,7 @@
 
         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);
@@ -569,7 +581,10 @@
         mStateController.setLowLightActive(false);
         mStateController.setEntryAnimationsFinished(false);
 
-        mDreamOverlayContainerViewController = null;
+        if (mDreamOverlayContainerViewController != null) {
+            mDreamOverlayContainerViewController.destroy();
+            mDreamOverlayContainerViewController = null;
+        }
 
         if (mTouchMonitor != null) {
             mTouchMonitor.destroy();
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/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt b/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
index 532b123..7e2c9f8 100644
--- a/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
@@ -18,14 +18,15 @@
 
 import com.android.systemui.CoreStartable
 import com.android.systemui.Flags
-import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.contextualeducation.GestureType
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.education.data.repository.ContextualEducationRepository
-import com.android.systemui.education.data.repository.ContextualEducationRepositoryImpl
+import com.android.systemui.education.data.repository.UserContextualEducationRepository
 import com.android.systemui.education.domain.interactor.ContextualEducationInteractor
 import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduInteractor
 import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduStatsInteractor
 import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduStatsInteractorImpl
+import com.android.systemui.education.ui.view.ContextualEduUiCoordinator
 import dagger.Binds
 import dagger.Lazy
 import dagger.Module
@@ -42,7 +43,7 @@
 interface ContextualEducationModule {
     @Binds
     fun bindContextualEducationRepository(
-        impl: ContextualEducationRepositoryImpl
+        impl: UserContextualEducationRepository
     ): ContextualEducationRepository
 
     @Qualifier annotation class EduDataStoreScope
@@ -74,7 +75,7 @@
                 implLazy.get()
             } else {
                 // No-op implementation when the flag is disabled.
-                return NoOpContextualEducationInteractor
+                return NoOpCoreStartable
             }
         }
 
@@ -91,6 +92,8 @@
         }
 
         @Provides
+        @IntoMap
+        @ClassKey(KeyboardTouchpadEduInteractor::class)
         fun provideKeyboardTouchpadEduInteractor(
             implLazy: Lazy<KeyboardTouchpadEduInteractor>
         ): CoreStartable {
@@ -98,22 +101,32 @@
                 implLazy.get()
             } else {
                 // No-op implementation when the flag is disabled.
-                return NoOpKeyboardTouchpadEduInteractor
+                return NoOpCoreStartable
+            }
+        }
+
+        @Provides
+        @IntoMap
+        @ClassKey(ContextualEduUiCoordinator::class)
+        fun provideContextualEduUiCoordinator(
+            implLazy: Lazy<ContextualEduUiCoordinator>
+        ): CoreStartable {
+            return if (Flags.keyboardTouchpadContextualEducation()) {
+                implLazy.get()
+            } else {
+                // No-op implementation when the flag is disabled.
+                return NoOpCoreStartable
             }
         }
     }
+}
 
-    private object NoOpKeyboardTouchpadEduStatsInteractor : KeyboardTouchpadEduStatsInteractor {
-        override fun incrementSignalCount(gestureType: GestureType) {}
+private object NoOpKeyboardTouchpadEduStatsInteractor : KeyboardTouchpadEduStatsInteractor {
+    override fun incrementSignalCount(gestureType: GestureType) {}
 
-        override fun updateShortcutTriggerTime(gestureType: GestureType) {}
-    }
+    override fun updateShortcutTriggerTime(gestureType: GestureType) {}
+}
 
-    private object NoOpContextualEducationInteractor : CoreStartable {
-        override fun start() {}
-    }
-
-    private object NoOpKeyboardTouchpadEduInteractor : CoreStartable {
-        override fun start() {}
-    }
+private object NoOpCoreStartable : CoreStartable {
+    override fun start() {}
 }
diff --git a/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt b/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt
index 9f6cb4d..a171f87 100644
--- a/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt
@@ -26,4 +26,6 @@
     val signalCount: Int = 0,
     val educationShownCount: Int = 0,
     val lastShortcutTriggeredTime: Instant? = null,
+    val usageSessionStartTime: Instant? = null,
+    val lastEducationTime: Instant? = null,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/education/data/repository/ContextualEducationRepository.kt b/packages/SystemUI/src/com/android/systemui/education/data/repository/ContextualEducationRepository.kt
deleted file mode 100644
index 52ccba4..0000000
--- a/packages/SystemUI/src/com/android/systemui/education/data/repository/ContextualEducationRepository.kt
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * 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.systemui.education.data.repository
-
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.contextualeducation.GestureType
-import com.android.systemui.education.dagger.ContextualEducationModule.EduClock
-import com.android.systemui.education.data.model.GestureEduModel
-import java.time.Clock
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-
-/** Encapsulates the functions of ContextualEducationRepository. */
-interface ContextualEducationRepository {
-    fun setUser(userId: Int)
-
-    fun readGestureEduModelFlow(gestureType: GestureType): Flow<GestureEduModel>
-
-    suspend fun incrementSignalCount(gestureType: GestureType)
-
-    suspend fun updateShortcutTriggerTime(gestureType: GestureType)
-}
-
-/**
- * Provide methods to read and update on field level and allow setting datastore when user is
- * changed
- */
-@SysUISingleton
-class ContextualEducationRepositoryImpl
-@Inject
-constructor(
-    @EduClock private val clock: Clock,
-    private val userEduRepository: UserContextualEducationRepository
-) : ContextualEducationRepository {
-    /** To change data store when user is changed */
-    override fun setUser(userId: Int) = userEduRepository.setUser(userId)
-
-    override fun readGestureEduModelFlow(gestureType: GestureType) =
-        userEduRepository.readGestureEduModelFlow(gestureType)
-
-    override suspend fun incrementSignalCount(gestureType: GestureType) {
-        userEduRepository.updateGestureEduModel(gestureType) {
-            it.copy(signalCount = it.signalCount + 1)
-        }
-    }
-
-    override suspend fun updateShortcutTriggerTime(gestureType: GestureType) {
-        userEduRepository.updateGestureEduModel(gestureType) {
-            it.copy(lastShortcutTriggeredTime = clock.instant())
-        }
-    }
-}
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 4b37b29..7c3d6338 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
@@ -25,9 +25,9 @@
 import androidx.datastore.preferences.core.intPreferencesKey
 import androidx.datastore.preferences.core.longPreferencesKey
 import androidx.datastore.preferences.preferencesDataStoreFile
+import com.android.systemui.contextualeducation.GestureType
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.contextualeducation.GestureType
 import com.android.systemui.education.dagger.ContextualEducationModule.EduDataStoreScope
 import com.android.systemui.education.data.model.GestureEduModel
 import java.time.Instant
@@ -43,10 +43,24 @@
 import kotlinx.coroutines.flow.map
 
 /**
- * A contextual education repository to:
- * 1) store education data per user
- * 2) provide methods to read and update data on model-level
- * 3) provide method to enable changing datastore when user is changed
+ * Allows to:
+ * 1) read and update data on model-level
+ * 2) change data store when user is changed
+ */
+interface ContextualEducationRepository {
+    fun setUser(userId: Int)
+
+    fun readGestureEduModelFlow(gestureType: GestureType): Flow<GestureEduModel>
+
+    suspend fun updateGestureEduModel(
+        gestureType: GestureType,
+        transform: (GestureEduModel) -> GestureEduModel
+    )
+}
+
+/**
+ * Implementation of [ContextualEducationRepository] that uses [androidx.datastore.preferences.core]
+ * for storage. Data is stored per user.
  */
 @SysUISingleton
 class UserContextualEducationRepository
@@ -54,11 +68,13 @@
 constructor(
     @Application private val applicationContext: Context,
     @EduDataStoreScope private val dataStoreScopeProvider: Provider<CoroutineScope>
-) {
+) : ContextualEducationRepository {
     companion object {
         const val SIGNAL_COUNT_SUFFIX = "_SIGNAL_COUNT"
         const val NUMBER_OF_EDU_SHOWN_SUFFIX = "_NUMBER_OF_EDU_SHOWN"
         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 DATASTORE_DIR = "education/USER%s_ContextualEducation"
     }
@@ -70,7 +86,7 @@
     @OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
     private val prefData: Flow<Preferences> = datastore.filterNotNull().flatMapLatest { it.data }
 
-    internal fun setUser(userId: Int) {
+    override fun setUser(userId: Int) {
         dataStoreScope?.cancel()
         val newDsScope = dataStoreScopeProvider.get()
         datastore.value =
@@ -85,7 +101,7 @@
         dataStoreScope = newDsScope
     }
 
-    internal fun readGestureEduModelFlow(gestureType: GestureType): Flow<GestureEduModel> =
+    override fun readGestureEduModelFlow(gestureType: GestureType): Flow<GestureEduModel> =
         prefData.map { preferences -> getGestureEduModel(gestureType, preferences) }
 
     private fun getGestureEduModel(
@@ -97,12 +113,20 @@
             educationShownCount = preferences[getEducationShownCountKey(gestureType)] ?: 0,
             lastShortcutTriggeredTime =
                 preferences[getLastShortcutTriggeredTimeKey(gestureType)]?.let {
-                    Instant.ofEpochMilli(it)
+                    Instant.ofEpochSecond(it)
+                },
+            usageSessionStartTime =
+                preferences[getUsageSessionStartTimeKey(gestureType)]?.let {
+                    Instant.ofEpochSecond(it)
+                },
+            lastEducationTime =
+                preferences[getLastEducationTimeKey(gestureType)]?.let {
+                    Instant.ofEpochSecond(it)
                 },
         )
     }
 
-    internal suspend fun updateGestureEduModel(
+    override suspend fun updateGestureEduModel(
         gestureType: GestureType,
         transform: (GestureEduModel) -> GestureEduModel
     ) {
@@ -111,11 +135,21 @@
             val updatedModel = transform(currentModel)
             preferences[getSignalCountKey(gestureType)] = updatedModel.signalCount
             preferences[getEducationShownCountKey(gestureType)] = updatedModel.educationShownCount
-            updateTimeByInstant(
+            setInstant(
                 preferences,
                 updatedModel.lastShortcutTriggeredTime,
                 getLastShortcutTriggeredTimeKey(gestureType)
             )
+            setInstant(
+                preferences,
+                updatedModel.usageSessionStartTime,
+                getUsageSessionStartTimeKey(gestureType)
+            )
+            setInstant(
+                preferences,
+                updatedModel.lastEducationTime,
+                getLastEducationTimeKey(gestureType)
+            )
         }
     }
 
@@ -128,13 +162,22 @@
     private fun getLastShortcutTriggeredTimeKey(gestureType: GestureType): Preferences.Key<Long> =
         longPreferencesKey(gestureType.name + LAST_SHORTCUT_TRIGGERED_TIME_SUFFIX)
 
-    private fun updateTimeByInstant(
+    private fun getUsageSessionStartTimeKey(gestureType: GestureType): Preferences.Key<Long> =
+        longPreferencesKey(gestureType.name + USAGE_SESSION_START_TIME_SUFFIX)
+
+    private fun getLastEducationTimeKey(gestureType: GestureType): Preferences.Key<Long> =
+        longPreferencesKey(gestureType.name + LAST_EDUCATION_TIME_SUFFIX)
+
+    private fun setInstant(
         preferences: MutablePreferences,
         instant: Instant?,
         key: Preferences.Key<Long>
     ) {
         if (instant != null) {
-            preferences[key] = instant.toEpochMilli()
+            // Use epochSecond because an instant is defined as a signed long (64bit number) of
+            // seconds. Using toEpochMilli() on Instant.MIN or Instant.MAX will throw exception
+            // when converting to a long. So we use second instead of milliseconds for storage.
+            preferences[key] = instant.epochSecond
         } else {
             preferences.remove(key)
         }
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 bee289d..db5c386 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
@@ -17,13 +17,15 @@
 package com.android.systemui.education.domain.interactor
 
 import com.android.systemui.CoreStartable
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.contextualeducation.GestureType
 import com.android.systemui.contextualeducation.GestureType.BACK
+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.GestureEduModel
 import com.android.systemui.education.data.repository.ContextualEducationRepository
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import java.time.Clock
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
@@ -43,6 +45,7 @@
 constructor(
     @Background private val backgroundScope: CoroutineScope,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
+    @EduClock private val clock: Clock,
     private val selectedUserInteractor: SelectedUserInteractor,
     private val repository: ContextualEducationRepository,
 ) : CoreStartable {
@@ -64,9 +67,37 @@
             .flowOn(backgroundDispatcher)
     }
 
-    suspend fun incrementSignalCount(gestureType: GestureType) =
-        repository.incrementSignalCount(gestureType)
+    suspend fun incrementSignalCount(gestureType: GestureType) {
+        repository.updateGestureEduModel(gestureType) {
+            it.copy(
+                signalCount = it.signalCount + 1,
+                usageSessionStartTime =
+                    if (it.signalCount == 0) clock.instant() else it.usageSessionStartTime
+            )
+        }
+    }
 
-    suspend fun updateShortcutTriggerTime(gestureType: GestureType) =
-        repository.updateShortcutTriggerTime(gestureType)
+    suspend fun updateShortcutTriggerTime(gestureType: GestureType) {
+        repository.updateGestureEduModel(gestureType) {
+            it.copy(lastShortcutTriggeredTime = clock.instant())
+        }
+    }
+
+    suspend fun updateOnEduTriggered(gestureType: GestureType) {
+        repository.updateGestureEduModel(gestureType) {
+            it.copy(
+                // Reset signal counter and usageSessionStartTime after edu triggered
+                signalCount = 0,
+                lastEducationTime = clock.instant(),
+                educationShownCount = it.educationShownCount + 1,
+                usageSessionStartTime = null
+            )
+        }
+    }
+
+    suspend fun startNewUsageSession(gestureType: GestureType) {
+        repository.updateGestureEduModel(gestureType) {
+            it.copy(usageSessionStartTime = clock.instant(), signalCount = 1)
+        }
+    }
 }
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 9016c73..3a3fb8c 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
@@ -17,17 +17,19 @@
 package com.android.systemui.education.domain.interactor
 
 import com.android.systemui.CoreStartable
+import com.android.systemui.contextualeducation.GestureType.BACK
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.contextualeducation.GestureType.BACK
+import com.android.systemui.education.dagger.ContextualEducationModule.EduClock
 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 java.time.Clock
 import javax.inject.Inject
+import kotlin.time.Duration.Companion.hours
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.mapNotNull
 import kotlinx.coroutines.launch
 
 /** Allow listening to new contextual education triggered */
@@ -36,11 +38,13 @@
 @Inject
 constructor(
     @Background private val backgroundScope: CoroutineScope,
-    private val contextualEducationInteractor: ContextualEducationInteractor
+    private val contextualEducationInteractor: ContextualEducationInteractor,
+    @EduClock private val clock: Clock,
 ) : CoreStartable {
 
     companion object {
         const val MAX_SIGNAL_COUNT: Int = 2
+        val usageSessionDuration = 72.hours
     }
 
     private val _educationTriggered = MutableStateFlow<EducationInfo?>(null)
@@ -48,25 +52,30 @@
 
     override fun start() {
         backgroundScope.launch {
-            contextualEducationInteractor.backGestureModelFlow
-                .mapNotNull { getEduType(it) }
-                .collect { _educationTriggered.value = EducationInfo(BACK, it) }
-        }
-    }
-
-    private fun getEduType(model: GestureEduModel): EducationUiType? {
-        if (isEducationNeeded(model)) {
-            return EducationUiType.Toast
-        } else {
-            return null
+            contextualEducationInteractor.backGestureModelFlow.collect {
+                if (isUsageSessionExpired(it)) {
+                    contextualEducationInteractor.startNewUsageSession(BACK)
+                } else if (isEducationNeeded(it)) {
+                    _educationTriggered.value = EducationInfo(BACK, getEduType(it))
+                    contextualEducationInteractor.updateOnEduTriggered(BACK)
+                }
+            }
         }
     }
 
     private fun isEducationNeeded(model: GestureEduModel): Boolean {
         // Todo: b/354884305 - add complete education logic to show education in correct scenarios
-        val shortcutWasTriggered = model.lastShortcutTriggeredTime == null
+        val noShortcutTriggered = model.lastShortcutTriggeredTime == null
         val signalCountReached = model.signalCount >= MAX_SIGNAL_COUNT
-
-        return shortcutWasTriggered && signalCountReached
+        return noShortcutTriggered && signalCountReached
     }
+
+    private fun isUsageSessionExpired(model: GestureEduModel): Boolean {
+        return model.usageSessionStartTime
+            ?.plusSeconds(usageSessionDuration.inWholeSeconds)
+            ?.isBefore(clock.instant()) ?: false
+    }
+
+    private fun getEduType(model: GestureEduModel) =
+        if (model.educationShownCount > 0) EducationUiType.Notification else EducationUiType.Toast
 }
diff --git a/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt b/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt
new file mode 100644
index 0000000..b446ea2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt
@@ -0,0 +1,68 @@
+/*
+ * 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.systemui.education.ui.view
+
+import android.content.Context
+import android.widget.Toast
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.education.shared.model.EducationUiType
+import com.android.systemui.education.ui.viewmodel.ContextualEduContentViewModel
+import com.android.systemui.education.ui.viewmodel.ContextualEduViewModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * A class to show contextual education on UI based on the edu produced from
+ * [ContextualEduViewModel]
+ */
+@SysUISingleton
+class ContextualEduUiCoordinator
+constructor(
+    @Application private val applicationScope: CoroutineScope,
+    private val viewModel: ContextualEduViewModel,
+    private val createToast: (String) -> Toast
+) : CoreStartable {
+
+    @Inject
+    constructor(
+        @Application applicationScope: CoroutineScope,
+        context: Context,
+        viewModel: ContextualEduViewModel,
+    ) : this(
+        applicationScope,
+        viewModel,
+        createToast = { message -> Toast.makeText(context, message, Toast.LENGTH_LONG) }
+    )
+
+    override fun start() {
+        applicationScope.launch {
+            viewModel.eduContent.collect { contentModel ->
+                if (contentModel.type == EducationUiType.Toast) {
+                    showToast(contentModel)
+                }
+            }
+        }
+    }
+
+    private fun showToast(model: ContextualEduContentViewModel) {
+        val toast = createToast(model.message)
+        toast.show()
+    }
+}
diff --git a/telephony/java/android/telephony/satellite/stub/ProvisionSubscriberId.aidl b/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduContentViewModel.kt
similarity index 65%
copy from telephony/java/android/telephony/satellite/stub/ProvisionSubscriberId.aidl
copy to packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduContentViewModel.kt
index 460de8c..3cba4c8 100644
--- a/telephony/java/android/telephony/satellite/stub/ProvisionSubscriberId.aidl
+++ b/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduContentViewModel.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,18 +14,8 @@
  * limitations under the License.
  */
 
-package android.telephony.satellite.stub;
+package com.android.systemui.education.ui.viewmodel
 
-/**
- * {@hide}
- */
-parcelable ProvisionSubscriberId {
-    /** provision subscriberId */
-    String subscriberId;
+import com.android.systemui.education.shared.model.EducationUiType
 
-    /** carrier id */
-    int mCarrierId;
-
-    /** apn */
-    String mNiddApn;
-}
+data class ContextualEduContentViewModel(val message: String, val type: EducationUiType)
diff --git a/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt b/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt
new file mode 100644
index 0000000..58276e0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.systemui.education.ui.viewmodel
+
+import android.content.res.Resources
+import com.android.systemui.contextualeducation.GestureType
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduInteractor
+import com.android.systemui.education.shared.model.EducationInfo
+import com.android.systemui.res.R
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+
+@SysUISingleton
+class ContextualEduViewModel
+@Inject
+constructor(@Main private val resources: Resources, interactor: KeyboardTouchpadEduInteractor) {
+    val eduContent: Flow<ContextualEduContentViewModel> =
+        interactor.educationTriggered.filterNotNull().map {
+            ContextualEduContentViewModel(getEduContent(it), it.educationUiType)
+        }
+
+    private fun getEduContent(educationInfo: EducationInfo): String {
+        // Todo: also check UiType in educationInfo to determine the string
+        val resourceId =
+            when (educationInfo.gestureType) {
+                GestureType.BACK -> R.string.back_edu_toast_content
+                GestureType.HOME -> R.string.home_edu_toast_content
+                GestureType.OVERVIEW -> R.string.overview_edu_toast_content
+                GestureType.ALL_APPS -> R.string.all_apps_edu_toast_content
+            }
+        return resources.getString(resourceId)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
index 59de203..cd0b3f9 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -34,19 +34,14 @@
 import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
 import com.android.systemui.keyguard.MigrateClocksToBlueprint
 import com.android.systemui.keyguard.shared.ComposeLockscreen
-import com.android.systemui.qs.flags.NewQsUI
-import com.android.systemui.qs.flags.QSComposeFragment
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.shade.shared.flag.DualShade
 import com.android.systemui.statusbar.notification.collection.SortBySectionTimeFlag
-import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor
 import com.android.systemui.statusbar.notification.shared.NotificationAvalancheSuppression
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
 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.NotificationsLiveDataStoreRefactor
 import com.android.systemui.statusbar.notification.shared.PriorityPeopleSection
 import javax.inject.Inject
 
@@ -61,11 +56,9 @@
         modesUi dependsOn modesApi
 
         // Internal notification frontend dependencies
-        NotificationsLiveDataStoreRefactor.token dependsOn NotificationIconContainerRefactor.token
-        FooterViewRefactor.token dependsOn NotificationIconContainerRefactor.token
         NotificationAvalancheSuppression.token dependsOn VisualInterruptionRefactor.token
         PriorityPeopleSection.token dependsOn SortBySectionTimeFlag.token
-        NotificationMinimalismPrototype.token dependsOn NotificationsHeadsUpRefactor.token
+        NotificationMinimalismPrototype.token dependsOn NotificationThrottleHun.token
         NotificationsHeadsUpRefactor.token dependsOn NotificationThrottleHun.token
 
         // SceneContainer dependencies
@@ -81,9 +74,6 @@
         // DualShade dependencies
         DualShade.token dependsOn SceneContainerFlag.getMainAconfigFlag()
 
-        // QS Fragment using Compose dependencies
-        QSComposeFragment.token dependsOn NewQsUI.token
-
         // Status bar chip dependencies
         statusBarCallChipNotificationIconToken dependsOn statusBarUseReposForCallChipToken
         statusBarCallChipNotificationIconToken dependsOn statusBarScreenSharingChipsToken
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 8990505..e5f3a57 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -152,9 +152,6 @@
     // TODO(b/286563884): Tracking bug
     @JvmField val KEYGUARD_TALKBACK_FIX = unreleasedFlag("keyguard_talkback_fix")
 
-    // TODO(b/287268101): Tracking bug.
-    @JvmField val TRANSIT_CLOCK = releasedFlag("lockscreen_custom_transit_clock")
-
     /** Enables preview loading animation in the wallpaper picker. */
     // TODO(b/274443705): Tracking Bug
     @JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
index 4652b2a..e50c05c 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
@@ -107,7 +107,11 @@
     fun handleActionDown() {
         logEvent(qsTile?.tileSpec, state, "action down received")
         when (state) {
-            State.IDLE -> {
+            State.IDLE,
+            // ACTION_DOWN typically only happens in State.IDLE but including CLICKED and
+            // LONG_CLICKED just to be safe`b
+            State.CLICKED,
+            State.LONG_CLICKED -> {
                 setState(State.TIMEOUT_WAIT)
             }
             State.RUNNING_BACKWARDS_FROM_UP,
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/KeyboardTouchpadTutorialModule.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/KeyboardTouchpadTutorialModule.kt
new file mode 100644
index 0000000..8e6cb07
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/KeyboardTouchpadTutorialModule.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.inputdevice.tutorial
+
+import android.app.Activity
+import com.android.systemui.CoreStartable
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity
+import com.android.systemui.touchpad.tutorial.domain.interactor.TouchpadGesturesInteractor
+import dagger.Binds
+import dagger.BindsOptionalOf
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+interface KeyboardTouchpadTutorialModule {
+
+    @Binds
+    @IntoMap
+    @ClassKey(KeyboardTouchpadTutorialCoreStartable::class)
+    fun bindKeyboardTouchpadTutorialCoreStartable(
+        listener: KeyboardTouchpadTutorialCoreStartable
+    ): CoreStartable
+
+    @Binds
+    @IntoMap
+    @ClassKey(KeyboardTouchpadTutorialActivity::class)
+    fun activity(impl: KeyboardTouchpadTutorialActivity): Activity
+
+    // TouchpadModule dependencies below
+    // all should be optional to not introduce touchpad dependency in all sysui variants
+
+    @BindsOptionalOf fun touchpadScreensProvider(): TouchpadTutorialScreensProvider
+
+    @BindsOptionalOf fun touchpadGesturesInteractor(): TouchpadGesturesInteractor
+}
diff --git a/telephony/java/android/telephony/satellite/stub/ProvisionSubscriberId.aidl b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/TouchpadTutorialScreensProvider.kt
similarity index 66%
copy from telephony/java/android/telephony/satellite/stub/ProvisionSubscriberId.aidl
copy to packages/SystemUI/src/com/android/systemui/inputdevice/oobe/TouchpadTutorialScreensProvider.kt
index 460de8c..bd3e771 100644
--- a/telephony/java/android/telephony/satellite/stub/ProvisionSubscriberId.aidl
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/TouchpadTutorialScreensProvider.kt
@@ -14,18 +14,13 @@
  * limitations under the License.
  */
 
-package android.telephony.satellite.stub;
+package com.android.systemui.inputdevice.tutorial
 
-/**
- * {@hide}
- */
-parcelable ProvisionSubscriberId {
-    /** provision subscriberId */
-    String subscriberId;
+import androidx.compose.runtime.Composable
 
-    /** carrier id */
-    int mCarrierId;
+interface TouchpadTutorialScreensProvider {
 
-    /** apn */
-    String mNiddApn;
+    @Composable fun BackGesture(onDoneButtonClicked: () -> Unit, onBack: () -> Unit)
+
+    @Composable fun HomeGesture(onDoneButtonClicked: () -> Unit, onBack: () -> Unit)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/data/model/OobeSchedulerInfo.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/data/model/OobeSchedulerInfo.kt
deleted file mode 100644
index e5aedc0..0000000
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/data/model/OobeSchedulerInfo.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.inputdevice.oobe.data.model
-
-data class OobeSchedulerInfo(
-    val keyboard: DeviceSchedulerInfo = DeviceSchedulerInfo(),
-    val touchpad: DeviceSchedulerInfo = DeviceSchedulerInfo()
-)
-
-data class DeviceSchedulerInfo(var isLaunched: Boolean = false, var connectionTime: Long? = null) {
-    val wasEverConnected: Boolean
-        get() = connectionTime != null
-}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/domain/interactor/OobeSchedulerInteractor.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/domain/interactor/OobeSchedulerInteractor.kt
deleted file mode 100644
index b014c08..0000000
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/domain/interactor/OobeSchedulerInteractor.kt
+++ /dev/null
@@ -1,97 +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.inputdevice.oobe.domain.interactor
-
-import android.content.Context
-import android.content.Intent
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.inputdevice.oobe.data.model.DeviceSchedulerInfo
-import com.android.systemui.inputdevice.oobe.data.model.OobeSchedulerInfo
-import com.android.systemui.keyboard.data.repository.KeyboardRepository
-import com.android.systemui.touchpad.data.repository.TouchpadRepository
-import java.time.Duration
-import java.time.Instant
-import javax.inject.Inject
-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.launch
-
-/**
- * When the first time a keyboard or touchpad id connected, wait for [LAUNCH_DELAY], then launch the
- * tutorial as soon as there's a connected device
- */
-@SysUISingleton
-class OobeSchedulerInteractor
-@Inject
-constructor(
-    @Application private val context: Context,
-    @Application private val applicationScope: CoroutineScope,
-    private val keyboardRepository: KeyboardRepository,
-    private val touchpadRepository: TouchpadRepository
-) {
-    private val info = OobeSchedulerInfo()
-
-    fun start() {
-        if (!info.keyboard.isLaunched) {
-            applicationScope.launch {
-                schedule(keyboardRepository.isAnyKeyboardConnected, info.keyboard)
-            }
-        }
-        if (!info.touchpad.isLaunched) {
-            applicationScope.launch {
-                schedule(touchpadRepository.isAnyTouchpadConnected, info.touchpad)
-            }
-        }
-    }
-
-    private suspend fun schedule(isAnyDeviceConnected: Flow<Boolean>, info: DeviceSchedulerInfo) {
-        if (!info.wasEverConnected) {
-            waitForDeviceConnection(isAnyDeviceConnected)
-            info.connectionTime = Instant.now().toEpochMilli()
-        }
-        delay(remainingTimeMillis(info.connectionTime!!))
-        waitForDeviceConnection(isAnyDeviceConnected)
-        info.isLaunched = true
-        launchOobe()
-    }
-
-    private suspend fun waitForDeviceConnection(isAnyDeviceConnected: Flow<Boolean>): Boolean {
-        return isAnyDeviceConnected.filter { it }.first()
-    }
-
-    private fun launchOobe() {
-        val intent = Intent(TUTORIAL_ACTION)
-        intent.addCategory(Intent.CATEGORY_DEFAULT)
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-        context.startActivity(intent)
-    }
-
-    private fun remainingTimeMillis(start: Long): Long {
-        val elapsed = Instant.now().toEpochMilli() - start
-        return LAUNCH_DELAY - elapsed
-    }
-
-    companion object {
-        const val TAG = "OobeSchedulerInteractor"
-        const val TUTORIAL_ACTION = "com.android.systemui.action.TOUCHPAD_TUTORIAL"
-        private val LAUNCH_DELAY = Duration.ofHours(72).toMillis()
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/composable/ActionKeyTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/composable/ActionKeyTutorialScreen.kt
new file mode 100644
index 0000000..c5b0ca7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/composable/ActionKeyTutorialScreen.kt
@@ -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.systemui.inputdevice.tutorial.ui.composable
+
+import androidx.activity.compose.BackHandler
+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.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.key.Key
+import androidx.compose.ui.input.key.KeyEvent
+import androidx.compose.ui.input.key.KeyEventType
+import androidx.compose.ui.input.key.key
+import androidx.compose.ui.input.key.onKeyEvent
+import androidx.compose.ui.input.key.type
+import com.airbnb.lottie.compose.rememberLottieDynamicProperties
+import com.android.compose.theme.LocalAndroidColorScheme
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.FINISHED
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.NOT_STARTED
+import com.android.systemui.res.R
+
+@Composable
+fun ActionKeyTutorialScreen(
+    onDoneButtonClicked: () -> Unit,
+    onBack: () -> Unit,
+) {
+    BackHandler(onBack = onBack)
+    val screenConfig = buildScreenConfig()
+    var actionState by remember { mutableStateOf(NOT_STARTED) }
+    Box(
+        modifier =
+            Modifier.fillMaxSize().onKeyEvent { keyEvent: KeyEvent ->
+                // temporary before we can access Action/Meta key
+                if (keyEvent.key == Key.AltLeft && keyEvent.type == KeyEventType.KeyUp) {
+                    actionState = FINISHED
+                }
+                true
+            }
+    ) {
+        ActionTutorialContent(actionState, onDoneButtonClicked, screenConfig)
+    }
+}
+
+@Composable
+private fun buildScreenConfig() =
+    TutorialScreenConfig(
+        colors = rememberScreenColors(),
+        strings =
+            TutorialScreenConfig.Strings(
+                titleResId = R.string.tutorial_action_key_title,
+                bodyResId = R.string.tutorial_action_key_guidance,
+                titleSuccessResId = R.string.tutorial_action_key_success_title,
+                bodySuccessResId = R.string.tutorial_action_key_success_body
+            ),
+        animations =
+            TutorialScreenConfig.Animations(
+                educationResId = R.raw.action_key_edu,
+                successResId = R.raw.action_key_success
+            )
+    )
+
+@Composable
+private fun rememberScreenColors(): TutorialScreenConfig.Colors {
+    val primaryFixedDim = LocalAndroidColorScheme.current.primaryFixedDim
+    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),
+            rememberColorFilterProperty(".secondaryFixedDim", secondaryFixedDim),
+            rememberColorFilterProperty(".onSecondaryFixed", onSecondaryFixed),
+            rememberColorFilterProperty(".onSecondaryFixedVariant", onSecondaryFixedVariant)
+        )
+    val screenColors =
+        remember(surfaceContainer, dynamicProperties) {
+            TutorialScreenConfig.Colors(
+                background = onSecondaryFixed,
+                successBackground = surfaceContainer,
+                title = primaryFixedDim,
+                animationColors = dynamicProperties,
+            )
+        }
+    return screenColors
+}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/composable/ActionTutorialContent.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/composable/ActionTutorialContent.kt
new file mode 100644
index 0000000..c50b7dc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/composable/ActionTutorialContent.kt
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.composable
+
+import android.graphics.ColorFilter
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffColorFilter
+import androidx.annotation.RawRes
+import androidx.annotation.StringRes
+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.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+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
+import androidx.compose.ui.unit.dp
+import com.airbnb.lottie.LottieProperty
+import com.airbnb.lottie.compose.LottieAnimation
+import com.airbnb.lottie.compose.LottieCompositionSpec
+import com.airbnb.lottie.compose.LottieConstants
+import com.airbnb.lottie.compose.LottieDynamicProperties
+import com.airbnb.lottie.compose.LottieDynamicProperty
+import com.airbnb.lottie.compose.animateLottieCompositionAsState
+import com.airbnb.lottie.compose.rememberLottieComposition
+import com.airbnb.lottie.compose.rememberLottieDynamicProperty
+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
+
+enum class TutorialActionState {
+    NOT_STARTED,
+    IN_PROGRESS,
+    FINISHED
+}
+
+@Composable
+fun ActionTutorialContent(
+    actionState: TutorialActionState,
+    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) }
+                .padding(start = 48.dp, top = 124.dp, end = 48.dp, bottom = 48.dp)
+    ) {
+        Row(modifier = Modifier.fillMaxWidth().weight(1f)) {
+            TutorialDescription(
+                titleTextId =
+                    if (actionState == FINISHED) config.strings.titleSuccessResId
+                    else config.strings.titleResId,
+                titleColor = config.colors.title,
+                bodyTextId =
+                    if (actionState == FINISHED) config.strings.bodySuccessResId
+                    else config.strings.bodyResId,
+                modifier = Modifier.weight(1f)
+            )
+            Spacer(modifier = Modifier.width(76.dp))
+            TutorialAnimation(
+                actionState,
+                config,
+                modifier = Modifier.weight(1f).padding(top = 8.dp)
+            )
+        }
+        DoneButton(onDoneButtonClicked = onDoneButtonClicked)
+    }
+}
+
+@Composable
+fun TutorialDescription(
+    @StringRes titleTextId: Int,
+    titleColor: Color,
+    @StringRes bodyTextId: Int,
+    modifier: Modifier = Modifier
+) {
+    Column(verticalArrangement = Arrangement.Top, modifier = modifier) {
+        Text(
+            text = stringResource(id = titleTextId),
+            style = MaterialTheme.typography.displayLarge,
+            color = titleColor
+        )
+        Spacer(modifier = Modifier.height(16.dp))
+        Text(
+            text = stringResource(id = bodyTextId),
+            style = MaterialTheme.typography.bodyLarge,
+            color = Color.White
+        )
+    }
+}
+
+@Composable
+fun TutorialAnimation(
+    actionState: TutorialActionState,
+    config: TutorialScreenConfig,
+    modifier: Modifier = Modifier
+) {
+    Box(modifier = modifier.fillMaxWidth()) {
+        AnimatedContent(
+            targetState = actionState,
+            transitionSpec = {
+                if (initialState == NOT_STARTED) {
+                    val transitionDurationMillis = 150
+                    fadeIn(animationSpec = tween(transitionDurationMillis, easing = LinearEasing))
+                        .togetherWith(
+                            fadeOut(animationSpec = snap(delayMillis = transitionDurationMillis))
+                        )
+                        // we explicitly don't want size transform because when targetState
+                        // animation is loaded for the first time, AnimatedContent thinks target
+                        // size is smaller and tries to shrink initial state animation
+                        .using(sizeTransform = null)
+                } else {
+                    // empty transition works because all remaining transitions are from IN_PROGRESS
+                    // state which shares initial animation frame with both FINISHED and NOT_STARTED
+                    EnterTransition.None togetherWith ExitTransition.None
+                }
+            }
+        ) { state ->
+            when (state) {
+                NOT_STARTED ->
+                    EducationAnimation(
+                        config.animations.educationResId,
+                        config.colors.animationColors
+                    )
+                IN_PROGRESS ->
+                    FrozenSuccessAnimation(
+                        config.animations.successResId,
+                        config.colors.animationColors
+                    )
+                FINISHED ->
+                    SuccessAnimation(config.animations.successResId, config.colors.animationColors)
+            }
+        }
+    }
+}
+
+@Composable
+private fun FrozenSuccessAnimation(
+    @RawRes successAnimationId: Int,
+    animationProperties: LottieDynamicProperties
+) {
+    val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(successAnimationId))
+    LottieAnimation(
+        composition = composition,
+        progress = { 0f }, // animation should freeze on 1st frame
+        dynamicProperties = animationProperties,
+    )
+}
+
+@Composable
+private fun EducationAnimation(
+    @RawRes educationAnimationId: Int,
+    animationProperties: LottieDynamicProperties
+) {
+    val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(educationAnimationId))
+    val progress by
+        animateLottieCompositionAsState(composition, iterations = LottieConstants.IterateForever)
+    LottieAnimation(
+        composition = composition,
+        progress = { progress },
+        dynamicProperties = animationProperties,
+    )
+}
+
+@Composable
+private fun SuccessAnimation(
+    @RawRes successAnimationId: Int,
+    animationProperties: LottieDynamicProperties
+) {
+    val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(successAnimationId))
+    val progress by animateLottieCompositionAsState(composition, iterations = 1)
+    LottieAnimation(
+        composition = composition,
+        progress = { progress },
+        dynamicProperties = animationProperties,
+    )
+}
+
+@Composable
+fun rememberColorFilterProperty(
+    layerName: String,
+    color: Color
+): LottieDynamicProperty<ColorFilter> {
+    return rememberLottieDynamicProperty(
+        LottieProperty.COLOR_FILTER,
+        value = PorterDuffColorFilter(color.toArgb(), PorterDuff.Mode.SRC_ATOP),
+        // "**" below means match zero or more layers, so ** layerName ** means find layer with that
+        // name at any depth
+        keyPath = arrayOf("**", layerName, "**")
+    )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialComponents.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/composable/TutorialComponents.kt
similarity index 95%
rename from packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialComponents.kt
rename to packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/composable/TutorialComponents.kt
index f2276c8..01ad585 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialComponents.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/composable/TutorialComponents.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.touchpad.tutorial.ui.composable
+package com.android.systemui.inputdevice.tutorial.ui.composable
 
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Row
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialScreenConfig.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/composable/TutorialScreenConfig.kt
similarity index 95%
rename from packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialScreenConfig.kt
rename to packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/composable/TutorialScreenConfig.kt
index d76ceb9..0406bb9 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialScreenConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/composable/TutorialScreenConfig.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.touchpad.tutorial.ui.composable
+package com.android.systemui.inputdevice.tutorial.ui.composable
 
 import androidx.annotation.RawRes
 import androidx.annotation.StringRes
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/view/KeyboardTouchpadTutorialActivity.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/view/KeyboardTouchpadTutorialActivity.kt
new file mode 100644
index 0000000..3e382d6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/view/KeyboardTouchpadTutorialActivity.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.inputdevice.tutorial.ui.view
+
+import android.os.Bundle
+import android.view.WindowManager
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.activity.enableEdgeToEdge
+import androidx.activity.viewModels
+import androidx.compose.runtime.Composable
+import com.android.compose.theme.PlatformTheme
+import com.android.systemui.inputdevice.tutorial.TouchpadTutorialScreensProvider
+import com.android.systemui.inputdevice.tutorial.ui.viewmodel.KeyboardTouchpadTutorialViewModel
+import java.util.Optional
+import javax.inject.Inject
+
+/**
+ * Activity for out of the box experience for keyboard and touchpad. Note that it's possible that
+ * either of them are actually not connected when this is launched
+ */
+class KeyboardTouchpadTutorialActivity
+@Inject
+constructor(
+    private val viewModelFactory: KeyboardTouchpadTutorialViewModel.Factory,
+    private val touchpadTutorialScreensProvider: Optional<TouchpadTutorialScreensProvider>,
+) : ComponentActivity() {
+
+    private val vm by
+        viewModels<KeyboardTouchpadTutorialViewModel>(factoryProducer = { viewModelFactory })
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        enableEdgeToEdge()
+        setContent {
+            PlatformTheme {
+                KeyboardTouchpadTutorialContainer(vm, touchpadTutorialScreensProvider) { finish() }
+            }
+        }
+        // required to handle 3+ fingers on touchpad
+        window.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)
+    }
+
+    override fun onResume() {
+        super.onResume()
+        vm.onOpened()
+    }
+
+    override fun onPause() {
+        super.onPause()
+        vm.onClosed()
+    }
+}
+
+@Composable
+fun KeyboardTouchpadTutorialContainer(
+    vm: KeyboardTouchpadTutorialViewModel,
+    touchpadTutorialScreensProvider: Optional<TouchpadTutorialScreensProvider>,
+    closeTutorial: () -> Unit
+) {}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt
new file mode 100644
index 0000000..39b1ec0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.viewmodel
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import com.android.systemui.touchpad.tutorial.domain.interactor.TouchpadGesturesInteractor
+import java.util.Optional
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+class KeyboardTouchpadTutorialViewModel(
+    private val gesturesInteractor: Optional<TouchpadGesturesInteractor>
+) : ViewModel() {
+
+    private val _screen = MutableStateFlow(Screen.BACK_GESTURE)
+    val screen: StateFlow<Screen> = _screen
+
+    fun goTo(screen: Screen) {
+        _screen.value = screen
+    }
+
+    fun onOpened() {
+        gesturesInteractor.ifPresent { it.disableGestures() }
+    }
+
+    fun onClosed() {
+        gesturesInteractor.ifPresent { it.enableGestures() }
+    }
+
+    class Factory
+    @Inject
+    constructor(private val gesturesInteractor: Optional<TouchpadGesturesInteractor>) :
+        ViewModelProvider.Factory {
+
+        @Suppress("UNCHECKED_CAST")
+        override fun <T : ViewModel> create(modelClass: Class<T>): T {
+            return KeyboardTouchpadTutorialViewModel(gesturesInteractor) as T
+        }
+    }
+}
+
+enum class Screen {
+    BACK_GESTURE,
+    HOME_GESTURE,
+    ACTION_KEY
+}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/KeyboardTouchpadOobeTutorialCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartable.kt
similarity index 72%
rename from packages/SystemUI/src/com/android/systemui/inputdevice/oobe/KeyboardTouchpadOobeTutorialCoreStartable.kt
rename to packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartable.kt
index 701d3da..e8e1dd4 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/KeyboardTouchpadOobeTutorialCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartable.kt
@@ -14,23 +14,24 @@
  * limitations under the License.
  */
 
-package com.android.systemui.inputdevice.oobe
+package com.android.systemui.inputdevice.tutorial
 
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.inputdevice.oobe.domain.interactor.OobeSchedulerInteractor
+import com.android.systemui.inputdevice.tutorial.domain.interactor.TutorialSchedulerInteractor
 import com.android.systemui.shared.Flags.newTouchpadGesturesTutorial
 import dagger.Lazy
 import javax.inject.Inject
 
-/** A [CoreStartable] to launch a scheduler for keyboard and touchpad OOBE education */
+/** A [CoreStartable] to launch a scheduler for keyboard and touchpad education */
 @SysUISingleton
-class KeyboardTouchpadOobeTutorialCoreStartable
+class KeyboardTouchpadTutorialCoreStartable
 @Inject
-constructor(private val oobeSchedulerInteractor: Lazy<OobeSchedulerInteractor>) : CoreStartable {
+constructor(private val tutorialSchedulerInteractor: Lazy<TutorialSchedulerInteractor>) :
+    CoreStartable {
     override fun start() {
         if (newTouchpadGesturesTutorial()) {
-            oobeSchedulerInteractor.get().start()
+            tutorialSchedulerInteractor.get().start()
         }
     }
 }
diff --git a/telephony/java/android/telephony/satellite/stub/ProvisionSubscriberId.aidl b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/model/TutorialSchedulerInfo.kt
similarity index 71%
copy from telephony/java/android/telephony/satellite/stub/ProvisionSubscriberId.aidl
copy to packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/model/TutorialSchedulerInfo.kt
index 460de8c..9f46846 100644
--- a/telephony/java/android/telephony/satellite/stub/ProvisionSubscriberId.aidl
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/model/TutorialSchedulerInfo.kt
@@ -14,18 +14,9 @@
  * limitations under the License.
  */
 
-package android.telephony.satellite.stub;
+package com.android.systemui.inputdevice.tutorial.data.model
 
-/**
- * {@hide}
- */
-parcelable ProvisionSubscriberId {
-    /** provision subscriberId */
-    String subscriberId;
-
-    /** carrier id */
-    int mCarrierId;
-
-    /** apn */
-    String mNiddApn;
+data class DeviceSchedulerInfo(var isLaunched: Boolean = false, var connectTime: Long? = null) {
+    val wasEverConnected: Boolean
+        get() = connectTime != null
 }
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/repository/TutorialSchedulerRepository.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/repository/TutorialSchedulerRepository.kt
new file mode 100644
index 0000000..b9b3895
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/repository/TutorialSchedulerRepository.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.data.repository
+
+import android.content.Context
+import androidx.datastore.core.DataStore
+import androidx.datastore.preferences.core.Preferences
+import androidx.datastore.preferences.core.booleanPreferencesKey
+import androidx.datastore.preferences.core.edit
+import androidx.datastore.preferences.core.longPreferencesKey
+import androidx.datastore.preferences.preferencesDataStore
+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.data.model.DeviceSchedulerInfo
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
+
+@SysUISingleton
+class TutorialSchedulerRepository
+@Inject
+constructor(
+    @Application private val applicationContext: Context,
+    @Background private val backgroundScope: CoroutineScope
+) {
+
+    private val Context.dataStore: DataStore<Preferences> by
+        preferencesDataStore(name = DATASTORE_NAME, scope = backgroundScope)
+
+    suspend fun isLaunched(deviceType: DeviceType): Boolean = loadData()[deviceType]!!.isLaunched
+
+    suspend fun wasEverConnected(deviceType: DeviceType): Boolean =
+        loadData()[deviceType]!!.wasEverConnected
+
+    suspend fun connectTime(deviceType: DeviceType): Long = loadData()[deviceType]!!.connectTime!!
+
+    private suspend fun loadData(): Map<DeviceType, DeviceSchedulerInfo> {
+        return applicationContext.dataStore.data.map { pref -> getSchedulerInfo(pref) }.first()
+    }
+
+    suspend fun updateConnectTime(device: DeviceType, time: Long) {
+        applicationContext.dataStore.edit { pref -> pref[getConnectKey(device)] = time }
+    }
+
+    suspend fun updateLaunch(device: DeviceType) {
+        applicationContext.dataStore.edit { pref -> pref[getLaunchedKey(device)] = true }
+    }
+
+    private fun getSchedulerInfo(pref: Preferences): Map<DeviceType, DeviceSchedulerInfo> {
+        return mapOf(
+            DeviceType.KEYBOARD to getDeviceSchedulerInfo(pref, DeviceType.KEYBOARD),
+            DeviceType.TOUCHPAD to getDeviceSchedulerInfo(pref, DeviceType.TOUCHPAD)
+        )
+    }
+
+    private fun getDeviceSchedulerInfo(pref: Preferences, device: DeviceType): DeviceSchedulerInfo {
+        val isLaunched = pref[getLaunchedKey(device)] ?: false
+        val connectionTime = pref[getConnectKey(device)] ?: null
+        return DeviceSchedulerInfo(isLaunched, connectionTime)
+    }
+
+    private fun getLaunchedKey(device: DeviceType) =
+        booleanPreferencesKey(device.name + IS_LAUNCHED_SUFFIX)
+
+    private fun getConnectKey(device: DeviceType) =
+        longPreferencesKey(device.name + CONNECT_TIME_SUFFIX)
+
+    companion object {
+        const val DATASTORE_NAME = "TutorialScheduler"
+        const val IS_LAUNCHED_SUFFIX = "_IS_LAUNCHED"
+        const val CONNECT_TIME_SUFFIX = "_CONNECTED_TIME"
+    }
+}
+
+enum class DeviceType {
+    KEYBOARD,
+    TOUCHPAD
+}
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
new file mode 100644
index 0000000..b3b8f21
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.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
+import com.android.systemui.inputdevice.tutorial.data.repository.TutorialSchedulerRepository
+import com.android.systemui.keyboard.data.repository.KeyboardRepository
+import com.android.systemui.touchpad.data.repository.TouchpadRepository
+import java.time.Instant
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.hours
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flow
+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
+ */
+@SysUISingleton
+class TutorialSchedulerInteractor
+@Inject
+constructor(
+    @Background private val backgroundScope: CoroutineScope,
+    keyboardRepository: KeyboardRepository,
+    touchpadRepository: TouchpadRepository,
+    private val repo: TutorialSchedulerRepository
+) {
+    private val isAnyDeviceConnected =
+        mapOf(
+            KEYBOARD to keyboardRepository.isAnyKeyboardConnected,
+            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)
+            emit(TOUCHPAD)
+        }
+    }
+
+    private val keyboardScheduleFlow = flow {
+        if (!repo.isLaunched(KEYBOARD)) {
+            schedule(KEYBOARD)
+            emit(KEYBOARD)
+        }
+    }
+
+    private suspend fun schedule(deviceType: DeviceType) {
+        if (!repo.wasEverConnected(deviceType)) {
+            waitForDeviceConnection(deviceType)
+            repo.updateConnectTime(deviceType, Instant.now().toEpochMilli())
+        }
+        delay(remainingTimeMillis(start = repo.connectTime(deviceType)))
+        waitForDeviceConnection(deviceType)
+    }
+
+    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.updateLaunch(KEYBOARD)
+        if (tutorialType == TutorialType.TOUCHPAD || tutorialType == TutorialType.BOTH)
+            repo.updateLaunch(TOUCHPAD)
+        // TODO: launch tutorial
+        Log.d(TAG, "Launch tutorial for $tutorialType")
+    }
+
+    private suspend fun resolveTutorialType(deviceType: DeviceType): TutorialType {
+        // Resolve the type of tutorial depending on which device are connected when the tutorial is
+        // launched. E.g. when the keyboard is connected for [LAUNCH_DELAY], both keyboard and
+        // touchpad are connected, we launch the tutorial for both.
+        if (repo.isLaunched(deviceType)) return TutorialType.NONE
+        val otherDevice = if (deviceType == KEYBOARD) TOUCHPAD else KEYBOARD
+        val isOtherDeviceConnected = isAnyDeviceConnected[otherDevice]!!.first()
+        if (!repo.isLaunched(otherDevice) && isOtherDeviceConnected) return TutorialType.BOTH
+        return if (deviceType == KEYBOARD) TutorialType.KEYBOARD else TutorialType.TOUCHPAD
+    }
+
+    private fun remainingTimeMillis(start: Long): Long {
+        val elapsed = Instant.now().toEpochMilli() - start
+        return LAUNCH_DELAY - elapsed
+    }
+
+    companion object {
+        const val TAG = "TutorialSchedulerInteractor"
+        private val DEFAULT_LAUNCH_DELAY = 72.hours.inWholeMilliseconds
+        private val LAUNCH_DELAY: Long
+            get() =
+                SystemProperties.getLong(
+                    "persist.peripheral_tutorial_delay_ms",
+                    DEFAULT_LAUNCH_DELAY
+                )
+    }
+
+    enum class TutorialType {
+        KEYBOARD,
+        TOUCHPAD,
+        BOTH,
+        NONE
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/PhysicalKeyboardCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyboard/PhysicalKeyboardCoreStartable.kt
index 90867edd..da671e3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/PhysicalKeyboardCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/PhysicalKeyboardCoreStartable.kt
@@ -17,7 +17,6 @@
 
 package com.android.systemui.keyboard
 
-import android.hardware.input.InputSettings
 import com.android.systemui.CoreStartable
 import com.android.systemui.Flags
 import com.android.systemui.dagger.SysUISingleton
@@ -40,12 +39,10 @@
     private val featureFlags: FeatureFlags,
 ) : CoreStartable {
     override fun start() {
+        stickyKeysIndicatorCoordinator.get().startListening()
         if (featureFlags.isEnabled(LegacyFlag.KEYBOARD_BACKLIGHT_INDICATOR)) {
             keyboardBacklightDialogCoordinator.get().startListening()
         }
-        if (InputSettings.isAccessibilityStickyKeysFeatureEnabled()) {
-            stickyKeysIndicatorCoordinator.get().startListening()
-        }
         if (Flags.keyboardDockingIndicator()) {
             keyboardDockingIndicationViewBinder.get().startListening()
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 9f33113..871d046 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -645,6 +645,9 @@
         public void showDismissibleKeyguard() {
             trace("showDismissibleKeyguard");
             checkPermission();
+            if (mFoldGracePeriodProvider.get().isEnabled()) {
+                mKeyguardInteractor.showDismissibleKeyguard();
+            }
             mKeyguardViewMediator.showDismissibleKeyguard();
 
             if (SceneContainerFlag.isEnabled() && mFoldGracePeriodProvider.get().isEnabled()) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index 80cf4c5..ba533ce 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -106,7 +106,7 @@
     private val falsingManager: FalsingManager,
     private val keyguardClockViewModel: KeyguardClockViewModel,
     private val smartspaceViewModel: KeyguardSmartspaceViewModel,
-    private val lockscreenContentViewModel: LockscreenContentViewModel,
+    private val lockscreenContentViewModelFactory: LockscreenContentViewModel.Factory,
     private val lockscreenSceneBlueprintsLazy: Lazy<Set<LockscreenSceneBlueprint>>,
     private val clockInteractor: KeyguardClockInteractor,
     private val keyguardViewMediator: KeyguardViewMediator,
@@ -143,7 +143,7 @@
                 val composeView =
                     createLockscreen(
                         context = context,
-                        viewModel = lockscreenContentViewModel,
+                        viewModelFactory = lockscreenContentViewModelFactory,
                         blueprints = lockscreenSceneBlueprintsLazy.get(),
                     )
                 composeView.id = View.generateViewId()
@@ -224,7 +224,7 @@
 
     private fun createLockscreen(
         context: Context,
-        viewModel: LockscreenContentViewModel,
+        viewModelFactory: LockscreenContentViewModel.Factory,
         blueprints: Set<@JvmSuppressWildcards LockscreenSceneBlueprint>,
     ): View {
         val sceneBlueprints =
@@ -239,7 +239,7 @@
                     scene(currentScene) {
                         with(
                             LockscreenContent(
-                                viewModel = viewModel,
+                                viewModelFactory = viewModelFactory,
                                 blueprints = sceneBlueprints,
                                 clockInteractor = clockInteractor
                             )
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 0b3d0f7..17c5977 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -42,6 +42,7 @@
 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;
 
@@ -477,6 +478,7 @@
     private boolean mUnlockingAndWakingFromDream = false;
     private boolean mHideAnimationRun = false;
     private boolean mHideAnimationRunning = false;
+    private boolean mIsKeyguardExitAnimationCanceled = false;
 
     private SoundPool mLockSounds;
     private int mLockSoundId;
@@ -1588,10 +1590,11 @@
             setShowingLocked(!shouldWaitForProvisioning()
                     && !mLockPatternUtils.isLockScreenDisabled(
                             mSelectedUserInteractor.getSelectedUserId()),
-                    true /* forceCallbacks */);
+                    true /* forceCallbacks */, "setupLocked - keyguard service enabled");
         } else {
             // The system's keyguard is disabled or missing.
-            setShowingLocked(false /* showing */, true /* forceCallbacks */);
+            setShowingLocked(false /* showing */, true /* forceCallbacks */,
+                    "setupLocked - keyguard service disabled");
         }
 
         mKeyguardTransitions.register(
@@ -2334,6 +2337,12 @@
                     Log.e(TAG, "doKeyguard: we're still showing, but going away. Re-show the "
                             + "keyguard rather than short-circuiting and resetting.");
                 } else {
+                    // We're removing "reset" in the refactor - "resetting" the views will happen
+                    // as a reaction to the root cause of the "reset" signal.
+                    if (KeyguardWmStateRefactor.isEnabled()) {
+                        return;
+                    }
+
                     // It's already showing, and we're not trying to show it while the screen is
                     // off. We can simply reset all of the views, but don't hide the bouncer in case
                     // the user is currently interacting with it.
@@ -2399,6 +2408,16 @@
      */
     private void handleDismiss(IKeyguardDismissCallback callback, CharSequence message) {
         if (mShowing) {
+            if (KeyguardWmStateRefactor.isEnabled()) {
+                Log.d(TAG, "Dismissing keyguard with keyguard_wm_refactor_enabled: "
+                        + "cancelDoKeyguardLaterLocked");
+
+                // This won't get canceled in onKeyguardExitFinished() if the refactor is enabled,
+                // which can lead to the keyguard re-showing. Cancel here for now; this can be
+                // removed once we migrate the logic that posts doKeyguardLater in the first place.
+                cancelDoKeyguardLaterLocked();
+            }
+
             if (callback != null) {
                 mDismissCallbackRegistry.addCallback(callback);
             }
@@ -2817,9 +2836,10 @@
         playSound(mTrustedSoundId);
     }
 
-    private void updateActivityLockScreenState(boolean showing, boolean aodShowing) {
+    private void updateActivityLockScreenState(boolean showing, boolean aodShowing, String reason) {
         mUiBgExecutor.execute(() -> {
-            Log.d(TAG, "updateActivityLockScreenState(" + showing + ", " + aodShowing + ")");
+            Log.d(TAG, "updateActivityLockScreenState(" + showing + ", " + aodShowing + ", "
+                    + reason + ")");
 
             if (KeyguardWmStateRefactor.isEnabled()) {
                 // Handled in WmLockscreenVisibilityManager if flag is enabled.
@@ -2879,7 +2899,7 @@
 
             // Force if we're showing in the middle of unlocking, to ensure we end up in the
             // correct state.
-            setShowingLocked(true, hidingOrGoingAway /* force */);
+            setShowingLocked(true, hidingOrGoingAway /* force */, "handleShowInner");
             mHiding = false;
 
             if (!KeyguardWmStateRefactor.isEnabled()) {
@@ -3051,15 +3071,14 @@
                 mHiding = true;
                 mKeyguardGoingAwayRunnable.run();
             } else {
-                Log.d(TAG, "Hiding keyguard while occluded. Just hide the keyguard view and exit.");
-
                 if (!KeyguardWmStateRefactor.isEnabled()) {
                     mKeyguardViewControllerLazy.get().hide(
                             mSystemClock.uptimeMillis() + mHideAnimation.getStartOffset(),
                             mHideAnimation.getDuration());
                 }
 
-                onKeyguardExitFinished();
+                onKeyguardExitFinished("Hiding keyguard while occluded. Just hide the keyguard "
+                        + "view and exit.");
             }
 
             // It's possible that the device was unlocked (via BOUNCER or Fingerprint) while
@@ -3090,6 +3109,7 @@
         Log.d(TAG, "handleStartKeyguardExitAnimation startTime=" + startTime
                 + " fadeoutDuration=" + fadeoutDuration);
         synchronized (KeyguardViewMediator.this) {
+            mIsKeyguardExitAnimationCanceled = false;
             // Tell ActivityManager that we canceled the keyguard animation if
             // handleStartKeyguardExitAnimation was called, but we're not hiding the keyguard,
             // unless we're animating the surface behind the keyguard and will be hiding the
@@ -3109,7 +3129,8 @@
                         Slog.w(TAG, "Failed to call onAnimationFinished", e);
                     }
                 }
-                setShowingLocked(mShowing, true /* force */);
+                setShowingLocked(mShowing, true /* force */,
+                        "handleStartKeyguardExitAnimation - canceled");
                 return;
             }
             mHiding = false;
@@ -3133,9 +3154,11 @@
                                         Slog.w(TAG, "Failed to call onAnimationFinished", e);
                                     }
                                 }
-                                onKeyguardExitFinished();
-                                mKeyguardViewControllerLazy.get().hide(0 /* startTime */,
-                                        0 /* fadeoutDuration */);
+                                if (!mIsKeyguardExitAnimationCanceled) {
+                                    onKeyguardExitFinished("onRemoteAnimationFinished");
+                                    mKeyguardViewControllerLazy.get().hide(0 /* startTime */,
+                                            0 /* fadeoutDuration */);
+                                }
                                 mInteractionJankMonitor.end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION);
                             }
 
@@ -3272,12 +3295,12 @@
                     anim.start();
                 });
 
-                onKeyguardExitFinished();
+                onKeyguardExitFinished("remote animation disabled");
             }
         }
     }
 
-    private void onKeyguardExitFinished() {
+    private void onKeyguardExitFinished(String reason) {
         if (DEBUG) Log.d(TAG, "onKeyguardExitFinished()");
         // only play "unlock" noises if not on a call (since the incall UI
         // disables the keyguard)
@@ -3285,7 +3308,7 @@
             playSounds(false);
         }
 
-        setShowingLocked(false);
+        setShowingLocked(false, "onKeyguardExitFinished: " + reason);
         mWakeAndUnlocking = false;
         mDismissCallbackRegistry.notifyDismissSucceeded();
         resetKeyguardDonePendingLocked();
@@ -3333,6 +3356,9 @@
             // A lock is pending, meaning the keyguard exit animation was cancelled because we're
             // re-locking. We should just end the surface-behind animation without exiting the
             // keyguard. The pending lock will be handled by onFinishedGoingToSleep().
+            if (relockWithPowerButtonImmediately()) {
+                mIsKeyguardExitAnimationCanceled = true;
+            }
             finishSurfaceBehindRemoteAnimation(true /* showKeyguard */);
             maybeHandlePendingLock();
         } else {
@@ -3381,12 +3407,13 @@
                 doKeyguardLocked(null);
                 finishSurfaceBehindRemoteAnimation(true /* showKeyguard */);
                 // Ensure WM is notified that we made a decision to show
-                setShowingLocked(true /* showing */, true /* force */);
+                setShowingLocked(true /* showing */, true /* force */,
+                        "exitKeyguardAndFinishSurfaceBehindRemoteAnimation - relocked");
 
                 return;
             }
 
-            onKeyguardExitFinished();
+            onKeyguardExitFinished("exitKeyguardAndFinishSurfaceBehindRemoteAnimation");
 
             if (mKeyguardStateController.isDismissingFromSwipe() || wasShowing) {
                 Log.d(TAG, "onKeyguardExitRemoteAnimationFinished"
@@ -3443,7 +3470,7 @@
         mSurfaceBehindRemoteAnimationRequested = false;
         mKeyguardStateController.notifyKeyguardGoingAway(false);
         if (mShowing) {
-            setShowingLocked(true, true);
+            setShowingLocked(true, true, "hideSurfaceBehindKeyguard");
         }
     }
 
@@ -3789,7 +3816,7 @@
         // update lock screen state in ATMS here, otherwise ATMS tries to resume activities when
         // enabling doze state.
         if (mShowing || !mPendingLock || !mDozeParameters.canControlUnlockedScreenOff()) {
-            setShowingLocked(mShowing);
+            setShowingLocked(mShowing, "setDozing");
         }
     }
 
@@ -3799,7 +3826,7 @@
         // is 1f), then show the activity lock screen.
         if (mAnimatingScreenOff && mDozing && linear == 1f) {
             mAnimatingScreenOff = false;
-            setShowingLocked(mShowing, true);
+            setShowingLocked(mShowing, true, "onDozeAmountChanged");
         }
     }
 
@@ -3837,11 +3864,11 @@
         }
     }
 
-    void setShowingLocked(boolean showing) {
-        setShowingLocked(showing, false /* forceCallbacks */);
+    void setShowingLocked(boolean showing, String reason) {
+        setShowingLocked(showing, false /* forceCallbacks */, reason);
     }
 
-    private void setShowingLocked(boolean showing, boolean forceCallbacks) {
+    private void setShowingLocked(boolean showing, boolean forceCallbacks, String reason) {
         final boolean aodShowing = mDozing && !mWakeAndUnlocking;
         final boolean notifyDefaultDisplayCallbacks = showing != mShowing || forceCallbacks;
         final boolean updateActivityLockScreenState = showing != mShowing
@@ -3852,9 +3879,8 @@
             notifyDefaultDisplayCallbacks(showing);
         }
         if (updateActivityLockScreenState) {
-            updateActivityLockScreenState(showing, aodShowing);
+            updateActivityLockScreenState(showing, aodShowing, reason);
         }
-
     }
 
     private void notifyDefaultDisplayCallbacks(boolean showing) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/OWNERS b/packages/SystemUI/src/com/android/systemui/keyguard/OWNERS
new file mode 100644
index 0000000..443e9876
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/OWNERS
@@ -0,0 +1,11 @@
+set noparent
+
+# Bug component: 78010
+
+amiko@google.com
+beverlyt@google.com
+bhinegardner@google.com
+chandruis@google.com
+jglazier@google.com
+mpietal@google.com
+tsuji@google.com
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index edf17c1..81b0064 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -232,6 +232,9 @@
     /** Receive an event for doze time tick */
     val dozeTimeTick: Flow<Long>
 
+    /** Receive an event lockscreen being shown in a dismissible state */
+    val showDismissibleKeyguard: MutableStateFlow<Long>
+
     /** Observable for DismissAction */
     val dismissAction: StateFlow<DismissAction>
 
@@ -305,6 +308,8 @@
 
     fun dozeTimeTick()
 
+    fun showDismissibleKeyguard()
+
     fun setDismissAction(dismissAction: DismissAction)
 
     suspend fun setKeyguardDone(keyguardDoneType: KeyguardDone)
@@ -439,6 +444,12 @@
         _dozeTimeTick.value = systemClock.uptimeMillis()
     }
 
+    override val showDismissibleKeyguard = MutableStateFlow<Long>(0L)
+
+    override fun showDismissibleKeyguard() {
+        showDismissibleKeyguard.value = systemClock.uptimeMillis()
+    }
+
     private val _lastDozeTapToWakePosition = MutableStateFlow<Point?>(null)
     override val lastDozeTapToWakePosition = _lastDozeTapToWakePosition.asStateFlow()
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index de60c11..797a4ec 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -285,7 +285,7 @@
         state: TransitionState
     ) {
         if (updateTransitionId != transitionId) {
-            Log.wtf(TAG, "Attempting to update with old/invalid transitionId: $transitionId")
+            Log.e(TAG, "Attempting to update with old/invalid transitionId: $transitionId")
             return
         }
 
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 3775d19..17c1e82 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
@@ -133,7 +133,12 @@
                 transitionInteractor.startedKeyguardState.replayCache.last() ==
                     KeyguardState.DREAMING
             ) {
-                startTransitionTo(KeyguardState.LOCKSCREEN)
+                if (powerInteractor.detailedWakefulness.value.isAwake()) {
+                    startTransitionTo(
+                        KeyguardState.LOCKSCREEN,
+                        ownerReason = "Dream has ended and device is awake"
+                    )
+                }
             }
         }
     }
@@ -144,7 +149,7 @@
             scope.launch {
                 combine(
                         keyguardInteractor.isKeyguardOccluded,
-                        keyguardInteractor.isDreaming
+                        keyguardInteractor.isAbleToDream
                             // Debounce the dreaming signal since there is a race condition between
                             // the occluded and dreaming signals. We therefore add a small delay
                             // to give enough time for occluded to flip to false when the dream
@@ -172,7 +177,7 @@
         }
 
         scope.launch {
-            keyguardInteractor.isDreaming
+            keyguardInteractor.isAbleToDream
                 .filter { !it }
                 .sample(deviceEntryInteractor.isUnlocked, ::Pair)
                 .collect { (_, dismissable) ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index 8f4110c..db5a63b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -74,6 +74,7 @@
         listenForGoneToAodOrDozing()
         listenForGoneToDreaming()
         listenForGoneToLockscreenOrHub()
+        listenForGoneToOccluded()
         listenForGoneToDreamingLockscreenHosted()
     }
 
@@ -81,6 +82,27 @@
         scope.launch("$TAG#showKeyguard") { startTransitionTo(KeyguardState.LOCKSCREEN) }
     }
 
+    /**
+     * A special case supported on foldables, where folding the device may put the device on an
+     * unlocked lockscreen, but if an occluding app is already showing (like a active phone call),
+     * then go directly to OCCLUDED.
+     */
+    private fun listenForGoneToOccluded() {
+        scope.launch("$TAG#listenForGoneToOccluded") {
+            keyguardInteractor.showDismissibleKeyguard
+                .filterRelevantKeyguardState()
+                .sample(keyguardInteractor.isKeyguardOccluded, ::Pair)
+                .collect { (_, isKeyguardOccluded) ->
+                    if (isKeyguardOccluded) {
+                        startTransitionTo(
+                            KeyguardState.OCCLUDED,
+                            ownerReason = "Dismissible keyguard with occlusion"
+                        )
+                    }
+                }
+        }
+    }
+
     // Primarily for when the user chooses to lock down the device
     private fun listenForGoneToLockscreenOrHub() {
         if (KeyguardWmStateRefactor.isEnabled) {
@@ -166,11 +188,12 @@
             interpolator = Interpolators.LINEAR
             duration =
                 when (toState) {
-                    KeyguardState.DREAMING -> TO_DREAMING_DURATION
                     KeyguardState.AOD -> TO_AOD_DURATION
                     KeyguardState.DOZING -> TO_DOZING_DURATION
+                    KeyguardState.DREAMING -> TO_DREAMING_DURATION
                     KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION
                     KeyguardState.GLANCEABLE_HUB -> TO_GLANCEABLE_HUB_DURATION
+                    KeyguardState.OCCLUDED -> TO_OCCLUDED_DURATION
                     else -> DEFAULT_DURATION
                 }.inWholeMilliseconds
         }
@@ -179,10 +202,11 @@
     companion object {
         private const val TAG = "FromGoneTransitionInteractor"
         private val DEFAULT_DURATION = 500.milliseconds
-        val TO_DREAMING_DURATION = 933.milliseconds
         val TO_AOD_DURATION = 1300.milliseconds
         val TO_DOZING_DURATION = 933.milliseconds
+        val TO_DREAMING_DURATION = 933.milliseconds
         val TO_LOCKSCREEN_DURATION = DEFAULT_DURATION
         val TO_GLANCEABLE_HUB_DURATION = DEFAULT_DURATION
+        val TO_OCCLUDED_DURATION = 100.milliseconds
     }
 }
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 51d92f0..5dc020f 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
@@ -300,7 +300,9 @@
                 swipeToDismissInteractor.dismissFling
                     .filterNotNull()
                     .filterRelevantKeyguardState()
-                    .collect { _ -> startTransitionTo(KeyguardState.GONE) }
+                    .collect { _ ->
+                        startTransitionTo(KeyguardState.GONE, ownerReason = "dismissFling != null")
+                    }
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index 710b710a..aea57ce 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -157,6 +157,13 @@
         }
     }
 
+    /** Starts a transition to dismiss the keyguard from the OCCLUDED state. */
+    fun dismissFromOccluded() {
+        scope.launch {
+            startTransitionTo(KeyguardState.GONE, ownerReason = "Dismiss from occluded")
+        }
+    }
+
     private fun listenForOccludedToGone() {
         if (KeyguardWmStateRefactor.isEnabled) {
             // We don't think OCCLUDED to GONE is possible. You should always have to go via a
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 42490c4..0df989e 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
@@ -1,20 +1,18 @@
 /*
- *  Copyright (C) 2022 The Android Open Source Project
+ * 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
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this 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,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.keyguard.domain.interactor
@@ -156,15 +154,24 @@
 
     val isPulsing: Flow<Boolean> = dozeTransitionModel.map { it.to == DozeStateModel.DOZE_PULSING }
 
-    /**
-     * Whether the system is dreaming. [isDreaming] will be always be true when [isDozing] is true,
-     * but not vice-versa.
-     */
-    val isDreaming: StateFlow<Boolean> = repository.isDreaming
-
     /** Whether the system is dreaming with an overlay active */
     val isDreamingWithOverlay: Flow<Boolean> = repository.isDreamingWithOverlay
 
+    /**
+     * Whether the system is dreaming. [isDreaming] will be always be true when [isDozing] is true,
+     * but not vice-versa. Also accounts for [isDreamingWithOverlay]
+     */
+    val isDreaming: StateFlow<Boolean> =
+        merge(
+                repository.isDreaming,
+                repository.isDreamingWithOverlay,
+            )
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = false,
+            )
+
     /** Whether the system is dreaming and the active dream is hosted in lockscreen */
     val isActiveDreamLockscreenHosted: StateFlow<Boolean> = repository.isActiveDreamLockscreenHosted
 
@@ -172,6 +179,9 @@
     val onCameraLaunchDetected: Flow<CameraLaunchSourceModel> =
         repository.onCameraLaunchDetected.filter { it.type != CameraLaunchType.IGNORE }
 
+    /** Event for when an unlocked keyguard has been requested, such as on device fold */
+    val showDismissibleKeyguard: Flow<Long> = repository.showDismissibleKeyguard.asStateFlow()
+
     /**
      * Dozing and dreaming have overlapping events. If the doze state remains in FINISH, it means
      * that doze mode is not running and DREAMING is ok to commence.
@@ -179,12 +189,25 @@
      * Allow a brief moment to prevent rapidly oscillating between true/false signals.
      */
     val isAbleToDream: Flow<Boolean> =
-        merge(isDreaming, isDreamingWithOverlay)
-            .combine(dozeTransitionModel) { isDreaming, dozeTransitionModel ->
-                isDreaming && isDozeOff(dozeTransitionModel.to)
+        dozeTransitionModel
+            .flatMapLatest { dozeTransitionModel ->
+                if (isDozeOff(dozeTransitionModel.to)) {
+                    // When dozing stops, it is a very early signal that the device is exiting the
+                    // dream state. DreamManagerService eventually notifies window manager, which
+                    // invokes SystemUI through KeyguardService. Because of this substantial delay,
+                    // do not immediately process any dreaming information when exiting AOD. It
+                    // should actually be quite strange to leave AOD and then go straight to
+                    // DREAMING so this should be fine.
+                    delay(500L)
+                    isDreaming
+                        .sample(powerInteractor.isAwake) { isDreaming, isAwake ->
+                            isDreaming && isAwake
+                        }
+                        .debounce(50L)
+                } else {
+                    flowOf(false)
+                }
             }
-            .sample(powerInteractor.isAwake) { isAbleToDream, isAwake -> isAbleToDream && isAwake }
-            .debounce(50L)
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.WhileSubscribed(),
@@ -469,6 +492,10 @@
             CameraLaunchSourceModel(type = cameraLaunchSourceIntToType(source))
     }
 
+    fun showDismissibleKeyguard() {
+        repository.showDismissibleKeyguard()
+    }
+
     companion object {
         private const val TAG = "KeyguardInteractor"
     }
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 31236a4..0682d87 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
@@ -433,10 +433,6 @@
                 value = featureFlags.isEnabled(Flags.WALLPAPER_PICKER_UI_FOR_AIWP)
             ),
             KeyguardPickerFlag(
-                name = Contract.FlagsTable.FLAG_NAME_TRANSIT_CLOCK,
-                value = featureFlags.isEnabled(Flags.TRANSIT_CLOCK)
-            ),
-            KeyguardPickerFlag(
                 name = Contract.FlagsTable.FLAG_NAME_PAGE_TRANSITIONS,
                 value = featureFlags.isEnabled(Flags.WALLPAPER_PICKER_PAGE_TRANSITIONS)
             ),
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
index e132eb7..b89eb27 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
@@ -98,6 +98,16 @@
         }
 
         scope.launch {
+            keyguardInteractor.isDreaming.collect { logger.log(TAG, VERBOSE, "isDreaming", it) }
+        }
+
+        scope.launch {
+            keyguardInteractor.isDreamingWithOverlay.collect {
+                logger.log(TAG, VERBOSE, "isDreamingWithOverlay", it)
+            }
+        }
+
+        scope.launch {
             keyguardInteractor.isAbleToDream.collect {
                 logger.log(TAG, VERBOSE, "isAbleToDream", it)
             }
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 afbe357..efdae62 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
@@ -75,6 +75,7 @@
     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>>()
@@ -418,6 +419,7 @@
                 fromAlternateBouncerTransitionInteractor.get().dismissAlternateBouncer()
             AOD -> fromAodTransitionInteractor.get().dismissAod()
             DOZING -> fromDozingTransitionInteractor.get().dismissFromDozing()
+            KeyguardState.OCCLUDED -> fromOccludedTransitionInteractor.get().dismissFromOccluded()
             KeyguardState.GONE ->
                 Log.i(
                     TAG,
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 86e4115..906d586 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
@@ -19,14 +19,15 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.keyguard.shared.model.KeyguardState
+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,12 +54,14 @@
         shadeRepository.currentFling
             .sample(
                 transitionInteractor.startedKeyguardState,
-                keyguardInteractor.isKeyguardDismissible
+                keyguardInteractor.isKeyguardDismissible,
+                keyguardInteractor.statusBarState,
             )
-            .filter { (flingInfo, startedState, keyguardDismissable) ->
+            .filter { (flingInfo, startedState, keyguardDismissable, statusBarState) ->
                 flingInfo != null &&
-                    !flingInfo.expand &&
-                    startedState == KeyguardState.LOCKSCREEN &&
+                        !flingInfo.expand &&
+                        statusBarState != StatusBarState.SHADE_LOCKED &&
+                        startedState == KeyguardState.LOCKSCREEN &&
                     keyguardDismissable
             }
             .map { (flingInfo, _) -> flingInfo }
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 e1b333d..25b2b7c 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
@@ -93,6 +93,14 @@
                     KeyguardState.ALTERNATE_BOUNCER -> {
                         fromAlternateBouncerInteractor.surfaceBehindVisibility
                     }
+                    KeyguardState.OCCLUDED -> {
+                        // OCCLUDED -> GONE occurs when an app is on top of the keyguard, and then
+                        // requests manual dismissal of the keyguard in the background. The app will
+                        // remain visible on top of the stack throughout this transition, so we
+                        // should not trigger the keyguard going away animation by returning
+                        // surfaceBehindVisibility = true.
+                        flowOf(false)
+                    }
                     else -> flowOf(null)
                 }
             }
@@ -253,6 +261,18 @@
                     ) {
                         // Dreams dismiss keyguard and return to GONE if they can.
                         false
+                    } else if (
+                        startedWithPrev.newValue.from == KeyguardState.OCCLUDED &&
+                            startedWithPrev.newValue.to == KeyguardState.GONE
+                    ) {
+                        // OCCLUDED -> GONE directly, without transiting a *_BOUNCER state, occurs
+                        // when an app uses intent flags to launch over an insecure keyguard without
+                        // dismissing it, and then manually requests keyguard dismissal while
+                        // OCCLUDED. This transition is not user-visible; the device unlocks in the
+                        // background and the app remains on top, while we're now GONE. In this case
+                        // we should simply tell WM that the lockscreen is no longer visible, and
+                        // *not* play the going away animation or related animations.
+                        false
                     } else {
                         // Otherwise, use the visibility of the current state.
                         KeyguardState.lockscreenVisibleInState(currentState)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt
index 9dc77d3..fb97191 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
 import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 
 @ExperimentalCoroutinesApi
@@ -52,7 +53,11 @@
                     }
                 }
 
-                launch("$TAG#viewModel.alpha") { viewModel.alpha.collect { view.alpha = it } }
+                if (SceneContainerFlag.isEnabled) {
+                    view.alpha = 1f
+                } else {
+                    launch("$TAG#viewModel.alpha") { viewModel.alpha.collect { view.alpha = it } }
+                }
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
index a250b22..91a7f7f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
@@ -37,13 +37,13 @@
 import com.android.systemui.deviceentry.ui.binder.UdfpsAccessibilityOverlayBinder
 import com.android.systemui.deviceentry.ui.view.UdfpsAccessibilityOverlay
 import com.android.systemui.deviceentry.ui.viewmodel.AlternateBouncerUdfpsAccessibilityOverlayViewModel
-import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
 import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies
 import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel
 import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerWindowViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scrim.ScrimView
 import dagger.Lazy
 import javax.inject.Inject
@@ -67,7 +67,6 @@
     private val alternateBouncerDependencies: Lazy<AlternateBouncerDependencies>,
     private val windowManager: Lazy<WindowManager>,
     private val layoutInflater: Lazy<LayoutInflater>,
-    private val dismissCallbackRegistry: DismissCallbackRegistry,
 ) : CoreStartable {
     private val layoutParams: WindowManager.LayoutParams
         get() =
@@ -95,9 +94,10 @@
     private var alternateBouncerView: ConstraintLayout? = null
 
     override fun start() {
-        if (!DeviceEntryUdfpsRefactor.isEnabled) {
+        if (!DeviceEntryUdfpsRefactor.isEnabled || SceneContainerFlag.isEnabled) {
             return
         }
+
         applicationScope.launch("$TAG#alternateBouncerWindowViewModel") {
             alternateBouncerWindowViewModel.get().alternateBouncerWindowRequired.collect {
                 addAlternateBouncerWindowView ->
@@ -110,7 +110,7 @@
                     bind(alternateBouncerView!!, alternateBouncerDependencies.get())
                 } else {
                     removeViewFromWindowManager()
-                    alternateBouncerDependencies.get().viewModel.hideAlternateBouncer()
+                    alternateBouncerDependencies.get().viewModel.onRemovedFromWindow()
                 }
             }
         }
@@ -144,7 +144,7 @@
     private val onAttachAddBackGestureHandler =
         object : View.OnAttachStateChangeListener {
             private val onBackInvokedCallback: OnBackInvokedCallback = OnBackInvokedCallback {
-                onBackRequested()
+                alternateBouncerDependencies.get().viewModel.onBackRequested()
             }
 
             override fun onViewAttachedToWindow(view: View) {
@@ -161,14 +161,12 @@
                     .findOnBackInvokedDispatcher()
                     ?.unregisterOnBackInvokedCallback(onBackInvokedCallback)
             }
-
-            fun onBackRequested() {
-                alternateBouncerDependencies.get().viewModel.hideAlternateBouncer()
-                dismissCallbackRegistry.notifyDismissCancelled()
-            }
         }
 
     private fun addViewToWindowManager() {
+        if (SceneContainerFlag.isEnabled) {
+            return
+        }
         if (alternateBouncerView != null) {
             return
         }
@@ -190,6 +188,7 @@
         if (DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode()) {
             return
         }
+
         optionallyAddUdfpsViews(
             view = view,
             udfpsIconViewModel = alternateBouncerDependencies.udfpsIconViewModel,
@@ -202,12 +201,13 @@
             viewModel = alternateBouncerDependencies.messageAreaViewModel,
         )
 
-        val scrim = view.requireViewById(R.id.alternate_bouncer_scrim) as ScrimView
+        val scrim: ScrimView = view.requireViewById(R.id.alternate_bouncer_scrim)
         val viewModel = alternateBouncerDependencies.viewModel
         val swipeUpAnywhereGestureHandler =
             alternateBouncerDependencies.swipeUpAnywhereGestureHandler
         val tapGestureDetector = alternateBouncerDependencies.tapGestureDetector
-        view.repeatWhenAttached { alternateBouncerViewContainer ->
+
+        view.repeatWhenAttached {
             repeatOnLifecycle(Lifecycle.State.STARTED) {
                 launch("$TAG#viewModel.registerForDismissGestures") {
                         viewModel.registerForDismissGestures.collect { registerForDismissGestures ->
@@ -216,11 +216,11 @@
                                     swipeTag
                                 ) { _ ->
                                     alternateBouncerDependencies.powerInteractor.onUserTouch()
-                                    viewModel.showPrimaryBouncer()
+                                    viewModel.onTapped()
                                 }
                                 tapGestureDetector.addOnGestureDetectedCallback(tapTag) { _ ->
                                     alternateBouncerDependencies.powerInteractor.onUserTouch()
-                                    viewModel.showPrimaryBouncer()
+                                    viewModel.onTapped()
                                 }
                             } else {
                                 swipeUpAnywhereGestureHandler.removeOnGestureDetectedCallback(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
index 162a0d2..15e6b1d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
@@ -30,18 +30,23 @@
 import androidx.core.view.updateLayoutParams
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launch
 import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
 import com.android.settingslib.Utils
 import com.android.systemui.animation.Expandable
 import com.android.systemui.animation.view.LaunchableImageView
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.ui.binder.IconViewBinder
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.util.doOnEnd
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.combine
@@ -49,11 +54,20 @@
 import kotlinx.coroutines.launch
 
 /** This is only for a SINGLE Quick affordance */
-object KeyguardQuickAffordanceViewBinder {
+@SysUISingleton
+class KeyguardQuickAffordanceViewBinder
+@Inject
+constructor(
+    private val falsingManager: FalsingManager?,
+    private val vibratorHelper: VibratorHelper?,
+    private val logger: KeyguardQuickAffordancesLogger,
+    @Main private val mainImmediateDispatcher: CoroutineDispatcher,
+) {
 
-    private const val EXIT_DOZE_BUTTON_REVEAL_ANIMATION_DURATION_MS = 250L
-    private const val SCALE_SELECTED_BUTTON = 1.23f
-    private const val DIM_ALPHA = 0.3f
+    private val EXIT_DOZE_BUTTON_REVEAL_ANIMATION_DURATION_MS = 250L
+    private val SCALE_SELECTED_BUTTON = 1.23f
+    private val DIM_ALPHA = 0.3f
+    private val TAG = "KeyguardQuickAffordanceViewBinder"
 
     /**
      * Defines interface for an object that acts as the binding between the view and its view-model.
@@ -73,30 +87,24 @@
         view: LaunchableImageView,
         viewModel: Flow<KeyguardQuickAffordanceViewModel>,
         alpha: Flow<Float>,
-        falsingManager: FalsingManager?,
-        vibratorHelper: VibratorHelper?,
-        logger: KeyguardQuickAffordancesLogger,
         messageDisplayer: (Int) -> Unit,
     ): Binding {
         val button = view as ImageView
         val configurationBasedDimensions = MutableStateFlow(loadFromResources(view))
         val disposableHandle =
-            view.repeatWhenAttached {
+            view.repeatWhenAttached(mainImmediateDispatcher) {
                 repeatOnLifecycle(Lifecycle.State.STARTED) {
-                    launch {
+                    launch("$TAG#viewModel") {
                         viewModel.collect { buttonModel ->
                             updateButton(
                                 view = button,
                                 viewModel = buttonModel,
-                                falsingManager = falsingManager,
                                 messageDisplayer = messageDisplayer,
-                                vibratorHelper = vibratorHelper,
-                                logger = logger,
                             )
                         }
                     }
 
-                    launch {
+                    launch("$TAG#updateButtonAlpha") {
                         updateButtonAlpha(
                             view = button,
                             viewModel = viewModel,
@@ -104,7 +112,7 @@
                         )
                     }
 
-                    launch {
+                    launch("$TAG#configurationBasedDimensions") {
                         configurationBasedDimensions.collect { dimensions ->
                             button.updateLayoutParams<ViewGroup.LayoutParams> {
                                 width = dimensions.buttonSizePx.width
@@ -131,10 +139,7 @@
     private fun updateButton(
         view: ImageView,
         viewModel: KeyguardQuickAffordanceViewModel,
-        falsingManager: FalsingManager?,
         messageDisplayer: (Int) -> Unit,
-        vibratorHelper: VibratorHelper?,
-        logger: KeyguardQuickAffordancesLogger,
     ) {
         if (!viewModel.isVisible) {
             view.isInvisible = true
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 91b66c3..aab5b9b 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
@@ -70,7 +70,6 @@
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.CrossFadeHelper
 import com.android.systemui.statusbar.VibratorHelper
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController
 import com.android.systemui.temporarydisplay.ViewPriority
 import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
@@ -292,20 +291,18 @@
                             }
                         }
 
-                        if (NotificationIconContainerRefactor.isEnabled) {
-                            launch {
-                                val iconsAppearTranslationPx =
-                                    configuration
-                                        .getDimensionPixelSize(R.dimen.shelf_appear_translation)
-                                        .stateIn(this)
-                                viewModel.isNotifIconContainerVisible.collect { isVisible ->
-                                    childViews[aodNotificationIconContainerId]
-                                        ?.setAodNotifIconContainerIsVisible(
-                                            isVisible,
-                                            iconsAppearTranslationPx.value,
-                                            screenOffAnimationController,
-                                        )
-                                }
+                        launch {
+                            val iconsAppearTranslationPx =
+                                configuration
+                                    .getDimensionPixelSize(R.dimen.shelf_appear_translation)
+                                    .stateIn(this)
+                            viewModel.isNotifIconContainerVisible.collect { isVisible ->
+                                childViews[aodNotificationIconContainerId]
+                                    ?.setAodNotifIconContainerIsVisible(
+                                        isVisible,
+                                        iconsAppearTranslationPx.value,
+                                        screenOffAnimationController,
+                                    )
                             }
                         }
 
@@ -519,7 +516,6 @@
         if (MigrateClocksToBlueprint.isEnabled) {
             throw IllegalStateException("should only be called in legacy code paths")
         }
-        if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return
         coroutineScope {
             val iconAppearTranslationPx =
                 configuration.getDimensionPixelSize(R.dimen.shelf_appear_translation).stateIn(this)
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 6faca1e..6031ef6 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
@@ -50,7 +50,6 @@
 import com.android.internal.policy.SystemBarUtils
 import com.android.keyguard.ClockEventController
 import com.android.keyguard.KeyguardClockSwitch
-import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
 import com.android.systemui.animation.view.LaunchableImageView
 import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
 import com.android.systemui.broadcast.BroadcastDispatcher
@@ -79,7 +78,6 @@
 import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
 import com.android.systemui.monet.ColorScheme
 import com.android.systemui.monet.Style
-import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.plugins.clocks.ClockController
 import com.android.systemui.res.R
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -89,7 +87,6 @@
 import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
 import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants
 import com.android.systemui.statusbar.KeyguardIndicationController
-import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
 import com.android.systemui.statusbar.phone.KeyguardBottomAreaView
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController
@@ -133,8 +130,6 @@
     private val broadcastDispatcher: BroadcastDispatcher,
     private val lockscreenSmartspaceController: LockscreenSmartspaceController,
     private val udfpsOverlayInteractor: UdfpsOverlayInteractor,
-    private val falsingManager: FalsingManager,
-    private val vibratorHelper: VibratorHelper,
     private val indicationController: KeyguardIndicationController,
     private val keyguardRootViewModel: KeyguardRootViewModel,
     private val keyguardBlueprintViewModel: KeyguardBlueprintViewModel,
@@ -148,7 +143,7 @@
     private val defaultShortcutsSection: DefaultShortcutsSection,
     private val keyguardClockInteractor: KeyguardClockInteractor,
     private val keyguardClockViewModel: KeyguardClockViewModel,
-    private val quickAffordancesLogger: KeyguardQuickAffordancesLogger,
+    private val keyguardQuickAffordanceViewBinder: KeyguardQuickAffordanceViewBinder,
 ) {
     val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN)
     private val width: Int = bundle.getInt(KEY_VIEW_WIDTH)
@@ -458,13 +453,10 @@
 
         keyguardRootView.findViewById<LaunchableImageView?>(R.id.start_button)?.let { imageView ->
             shortcutsBindings.add(
-                KeyguardQuickAffordanceViewBinder.bind(
+                keyguardQuickAffordanceViewBinder.bind(
                     view = imageView,
                     viewModel = quickAffordancesCombinedViewModel.startButton,
                     alpha = flowOf(1f),
-                    falsingManager = falsingManager,
-                    vibratorHelper = vibratorHelper,
-                    logger = quickAffordancesLogger,
                 ) { message ->
                     indicationController.showTransientIndication(message)
                 }
@@ -473,13 +465,10 @@
 
         keyguardRootView.findViewById<LaunchableImageView?>(R.id.end_button)?.let { imageView ->
             shortcutsBindings.add(
-                KeyguardQuickAffordanceViewBinder.bind(
+                keyguardQuickAffordanceViewBinder.bind(
                     view = imageView,
                     viewModel = quickAffordancesCombinedViewModel.endButton,
                     alpha = flowOf(1f),
-                    falsingManager = falsingManager,
-                    vibratorHelper = vibratorHelper,
-                    logger = quickAffordancesLogger,
                 ) { message ->
                     indicationController.showTransientIndication(message)
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt
index 2dc9301..bf6f2c4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt
@@ -42,12 +42,6 @@
     ): KeyguardBlueprint
 
     @Binds
-    @IntoSet
-    abstract fun bindShortcutsBesideUdfpsLockscreenBlueprint(
-        shortcutsBesideUdfpsLockscreenBlueprint: ShortcutsBesideUdfpsKeyguardBlueprint
-    ): KeyguardBlueprint
-
-    @Binds
     @IntoMap
     @ClassKey(KeyguardBlueprintInteractor::class)
     abstract fun bindsKeyguardBlueprintInteractor(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
deleted file mode 100644
index b984a68..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
+++ /dev/null
@@ -1,94 +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.ui.view.layout.blueprints
-
-import com.android.systemui.communal.ui.view.layout.sections.CommunalTutorialIndicatorSection
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
-import com.android.systemui.keyguard.shared.model.KeyguardSection
-import com.android.systemui.keyguard.ui.view.layout.sections.AccessibilityActionsSection
-import com.android.systemui.keyguard.ui.view.layout.sections.AlignShortcutsToUdfpsSection
-import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection
-import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection
-import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection
-import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntrySection
-import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection
-import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection
-import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection
-import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection
-import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
-import com.android.systemui.keyguard.ui.view.layout.sections.DefaultUdfpsAccessibilityOverlaySection
-import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule
-import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSliceViewSection
-import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection
-import com.android.systemui.util.kotlin.getOrNull
-import java.util.Optional
-import javax.inject.Inject
-import javax.inject.Named
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-
-/** Vertically aligns the shortcuts with the udfps. */
-@ExperimentalCoroutinesApi
-@SysUISingleton
-class ShortcutsBesideUdfpsKeyguardBlueprint
-@Inject
-constructor(
-    accessibilityActionsSection: AccessibilityActionsSection,
-    alignShortcutsToUdfpsSection: AlignShortcutsToUdfpsSection,
-    defaultIndicationAreaSection: DefaultIndicationAreaSection,
-    defaultDeviceEntrySection: DefaultDeviceEntrySection,
-    @Named(KeyguardSectionsModule.KEYGUARD_AMBIENT_INDICATION_AREA_SECTION)
-    defaultAmbientIndicationAreaSection: Optional<KeyguardSection>,
-    defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection,
-    defaultStatusViewSection: DefaultStatusViewSection,
-    defaultStatusBarSection: DefaultStatusBarSection,
-    defaultNotificationStackScrollLayoutSection: DefaultNotificationStackScrollLayoutSection,
-    aodNotificationIconsSection: AodNotificationIconsSection,
-    aodBurnInSection: AodBurnInSection,
-    communalTutorialIndicatorSection: CommunalTutorialIndicatorSection,
-    clockSection: ClockSection,
-    smartspaceSection: SmartspaceSection,
-    keyguardSliceViewSection: KeyguardSliceViewSection,
-    udfpsAccessibilityOverlaySection: DefaultUdfpsAccessibilityOverlaySection,
-) : KeyguardBlueprint {
-    override val id: String = SHORTCUTS_BESIDE_UDFPS
-
-    override val sections =
-        listOfNotNull(
-            accessibilityActionsSection,
-            defaultIndicationAreaSection,
-            alignShortcutsToUdfpsSection,
-            defaultAmbientIndicationAreaSection.getOrNull(),
-            defaultSettingsPopupMenuSection,
-            defaultStatusViewSection,
-            defaultStatusBarSection,
-            defaultNotificationStackScrollLayoutSection,
-            aodNotificationIconsSection,
-            smartspaceSection,
-            aodBurnInSection,
-            communalTutorialIndicatorSection,
-            clockSection,
-            keyguardSliceViewSection,
-            defaultDeviceEntrySection,
-            udfpsAccessibilityOverlaySection, // Add LAST: Intentionally has z-order above others
-        )
-
-    companion object {
-        const val SHORTCUTS_BESIDE_UDFPS = "shortcuts-besides-udfps"
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt
deleted file mode 100644
index 1ba830b..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt
+++ /dev/null
@@ -1,114 +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.ui.view.layout.sections
-
-import android.content.res.Resources
-import androidx.constraintlayout.widget.ConstraintLayout
-import androidx.constraintlayout.widget.ConstraintSet
-import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
-import androidx.constraintlayout.widget.ConstraintSet.LEFT
-import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
-import androidx.constraintlayout.widget.ConstraintSet.RIGHT
-import androidx.constraintlayout.widget.ConstraintSet.TOP
-import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
-import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
-import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
-import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.res.R
-import com.android.systemui.statusbar.KeyguardIndicationController
-import com.android.systemui.statusbar.VibratorHelper
-import javax.inject.Inject
-
-class AlignShortcutsToUdfpsSection
-@Inject
-constructor(
-    @Main private val resources: Resources,
-    private val keyguardQuickAffordancesCombinedViewModel:
-        KeyguardQuickAffordancesCombinedViewModel,
-    private val keyguardRootViewModel: KeyguardRootViewModel,
-    private val falsingManager: FalsingManager,
-    private val indicationController: KeyguardIndicationController,
-    private val vibratorHelper: VibratorHelper,
-    private val shortcutsLogger: KeyguardQuickAffordancesLogger,
-) : BaseShortcutSection() {
-    override fun addViews(constraintLayout: ConstraintLayout) {
-        if (KeyguardBottomAreaRefactor.isEnabled) {
-            addLeftShortcut(constraintLayout)
-            addRightShortcut(constraintLayout)
-        }
-    }
-
-    override fun bindData(constraintLayout: ConstraintLayout) {
-        if (KeyguardBottomAreaRefactor.isEnabled) {
-            leftShortcutHandle =
-                KeyguardQuickAffordanceViewBinder.bind(
-                    constraintLayout.requireViewById(R.id.start_button),
-                    keyguardQuickAffordancesCombinedViewModel.startButton,
-                    keyguardQuickAffordancesCombinedViewModel.transitionAlpha,
-                    falsingManager,
-                    vibratorHelper,
-                    shortcutsLogger,
-                ) {
-                    indicationController.showTransientIndication(it)
-                }
-            rightShortcutHandle =
-                KeyguardQuickAffordanceViewBinder.bind(
-                    constraintLayout.requireViewById(R.id.end_button),
-                    keyguardQuickAffordancesCombinedViewModel.endButton,
-                    keyguardQuickAffordancesCombinedViewModel.transitionAlpha,
-                    falsingManager,
-                    vibratorHelper,
-                    shortcutsLogger,
-                ) {
-                    indicationController.showTransientIndication(it)
-                }
-        }
-    }
-
-    override fun applyConstraints(constraintSet: ConstraintSet) {
-        val width = resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width)
-        val height = resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height)
-
-        val lockIconViewId =
-            if (DeviceEntryUdfpsRefactor.isEnabled) {
-                R.id.device_entry_icon_view
-            } else {
-                R.id.lock_icon_view
-            }
-
-        constraintSet.apply {
-            constrainWidth(R.id.start_button, width)
-            constrainHeight(R.id.start_button, height)
-            connect(R.id.start_button, LEFT, PARENT_ID, LEFT)
-            connect(R.id.start_button, RIGHT, lockIconViewId, LEFT)
-            connect(R.id.start_button, TOP, lockIconViewId, TOP)
-            connect(R.id.start_button, BOTTOM, lockIconViewId, BOTTOM)
-
-            constrainWidth(R.id.end_button, width)
-            constrainHeight(R.id.end_button, height)
-            connect(R.id.end_button, RIGHT, PARENT_ID, RIGHT)
-            connect(R.id.end_button, LEFT, lockIconViewId, RIGHT)
-            connect(R.id.end_button, TOP, lockIconViewId, TOP)
-            connect(R.id.end_button, BOTTOM, lockIconViewId, BOTTOM)
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
index d77b548..36ef78e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
@@ -37,8 +37,6 @@
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker
 import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
-import com.android.systemui.statusbar.phone.NotificationIconAreaController
 import com.android.systemui.statusbar.phone.NotificationIconContainer
 import com.android.systemui.statusbar.ui.SystemBarUtilsState
 import com.android.systemui.util.ui.value
@@ -53,7 +51,6 @@
     private val iconBindingFailureTracker: StatusBarIconViewBindingFailureTracker,
     private val nicAodViewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
     private val nicAodIconViewStore: AlwaysOnDisplayNotificationIconViewStore,
-    private val notificationIconAreaController: NotificationIconAreaController,
     private val systemBarUtilsState: SystemBarUtilsState,
     private val rootViewModel: KeyguardRootViewModel,
 ) : KeyguardSection() {
@@ -86,20 +83,16 @@
             return
         }
 
-        if (NotificationIconContainerRefactor.isEnabled) {
-            nicBindingDisposable?.dispose()
-            nicBindingDisposable =
-                NotificationIconContainerViewBinder.bindWhileAttached(
-                    nic,
-                    nicAodViewModel,
-                    configurationState,
-                    systemBarUtilsState,
-                    iconBindingFailureTracker,
-                    nicAodIconViewStore,
-                )
-        } else {
-            notificationIconAreaController.setupAodIcons(nic)
-        }
+        nicBindingDisposable?.dispose()
+        nicBindingDisposable =
+            NotificationIconContainerViewBinder.bindWhileAttached(
+                nic,
+                nicAodViewModel,
+                configurationState,
+                systemBarUtilsState,
+                iconBindingFailureTracker,
+                nicAodIconViewStore,
+            )
     }
 
     override fun applyConstraints(constraintSet: ConstraintSet) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
index 380e361..6ac33af 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
@@ -24,7 +24,6 @@
 import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
 import androidx.constraintlayout.widget.ConstraintSet.START
 import androidx.constraintlayout.widget.ConstraintSet.TOP
-import com.android.systemui.Flags.centralizedStatusBarHeightFix
 import com.android.systemui.keyguard.MigrateClocksToBlueprint
 import com.android.systemui.res.R
 import com.android.systemui.shade.LargeScreenHeaderHelper
@@ -64,13 +63,7 @@
                 val useLargeScreenHeader =
                     context.resources.getBoolean(R.bool.config_use_large_screen_shade_header)
                 val marginTopLargeScreen =
-                    if (centralizedStatusBarHeightFix()) {
-                        largeScreenHeaderHelperLazy.get().getLargeScreenHeaderHeight()
-                    } else {
-                        context.resources.getDimensionPixelSize(
-                            R.dimen.large_screen_shade_header_height
-                        )
-                    }
+                    largeScreenHeaderHelperLazy.get().getLargeScreenHeaderHeight()
                 connect(
                     R.id.nssl_placeholder,
                     TOP,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
index 64c46db..e558033 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
@@ -26,7 +26,6 @@
 import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
 import androidx.constraintlayout.widget.ConstraintSet.RIGHT
 import androidx.constraintlayout.widget.ConstraintSet.VISIBILITY_MODE_IGNORE
-import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
 import com.android.systemui.animation.view.LaunchableImageView
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
@@ -35,10 +34,8 @@
 import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
-import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.KeyguardIndicationController
-import com.android.systemui.statusbar.VibratorHelper
 import dagger.Lazy
 import javax.inject.Inject
 
@@ -49,11 +46,9 @@
     private val keyguardQuickAffordancesCombinedViewModel:
         KeyguardQuickAffordancesCombinedViewModel,
     private val keyguardRootViewModel: KeyguardRootViewModel,
-    private val falsingManager: FalsingManager,
     private val indicationController: KeyguardIndicationController,
-    private val vibratorHelper: VibratorHelper,
     private val keyguardBlueprintInteractor: Lazy<KeyguardBlueprintInteractor>,
-    private val shortcutsLogger: KeyguardQuickAffordancesLogger,
+    private val keyguardQuickAffordanceViewBinder: KeyguardQuickAffordanceViewBinder,
 ) : BaseShortcutSection() {
 
     // Amount to increase the bottom margin by to avoid colliding with inset
@@ -82,24 +77,18 @@
     override fun bindData(constraintLayout: ConstraintLayout) {
         if (KeyguardBottomAreaRefactor.isEnabled) {
             leftShortcutHandle =
-                KeyguardQuickAffordanceViewBinder.bind(
+                keyguardQuickAffordanceViewBinder.bind(
                     constraintLayout.requireViewById(R.id.start_button),
                     keyguardQuickAffordancesCombinedViewModel.startButton,
                     keyguardQuickAffordancesCombinedViewModel.transitionAlpha,
-                    falsingManager,
-                    vibratorHelper,
-                    shortcutsLogger,
                 ) {
                     indicationController.showTransientIndication(it)
                 }
             rightShortcutHandle =
-                KeyguardQuickAffordanceViewBinder.bind(
+                keyguardQuickAffordanceViewBinder.bind(
                     constraintLayout.requireViewById(R.id.end_button),
                     keyguardQuickAffordancesCombinedViewModel.endButton,
                     keyguardQuickAffordancesCombinedViewModel.transitionAlpha,
-                    falsingManager,
-                    vibratorHelper,
-                    shortcutsLogger,
                 ) {
                     indicationController.showTransientIndication(it)
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModel.kt
index c590f07..992550c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModel.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import android.util.MathUtils
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
 import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor
@@ -47,6 +48,15 @@
             edge = Edge.create(from = ALTERNATE_BOUNCER, to = AOD),
         )
 
+    fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> {
+        var startAlpha = 1f
+        return transitionAnimation.sharedFlow(
+            duration = FromAlternateBouncerTransitionInteractor.TO_AOD_DURATION,
+            onStart = { startAlpha = viewState.alpha() },
+            onStep = { MathUtils.lerp(startAlpha, 1f, it) },
+        )
+    }
+
     val deviceEntryBackgroundViewAlpha: Flow<Float> =
         transitionAnimation.sharedFlow(
             duration = FromAlternateBouncerTransitionInteractor.TO_AOD_DURATION,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt
index df0b3dc..4908dbd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
 import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.shared.recents.utilities.Utilities.clamp
 import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -50,6 +51,7 @@
     private val isSupported: Flow<Boolean> = deviceEntryUdfpsInteractor.isUdfpsSupported
     val alpha: Flow<Float> =
         alternateBouncerViewModel.transitionToAlternateBouncerProgress.map {
+            SceneContainerFlag.assertInLegacyMode()
             clamp(it * 2f, 0f, 1f)
         }
 
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 5a559fc..7b0b23f 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
@@ -18,15 +18,20 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import android.graphics.Color
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import dagger.Lazy
 import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
 
 @ExperimentalCoroutinesApi
 class AlternateBouncerViewModel
@@ -34,16 +39,19 @@
 constructor(
     private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    private val dismissCallbackRegistry: DismissCallbackRegistry,
+    alternateBouncerInteractor: Lazy<AlternateBouncerInteractor>,
 ) {
     // When we're fully transitioned to the AlternateBouncer, the alpha of the scrim should be:
     private val alternateBouncerScrimAlpha = .66f
 
-    /** Progress to a fully transitioned alternate bouncer. 1f represents fully transitioned. */
-    val transitionToAlternateBouncerProgress =
-        keyguardTransitionInteractor.transitionValue(ALTERNATE_BOUNCER)
+    /** Reports the alternate bouncer visible state if the scene container flag is enabled. */
+    val isVisible: Flow<Boolean> =
+        alternateBouncerInteractor.get().isVisible.onEach { SceneContainerFlag.assertInNewMode() }
 
-    val forcePluginOpen: Flow<Boolean> =
-        transitionToAlternateBouncerProgress.map { it > 0f }.distinctUntilChanged()
+    /** Progress to a fully transitioned alternate bouncer. 1f represents fully transitioned. */
+    val transitionToAlternateBouncerProgress: Flow<Float> =
+        keyguardTransitionInteractor.transitionValue(ALTERNATE_BOUNCER)
 
     /** An observable for the scrim alpha. */
     val scrimAlpha = transitionToAlternateBouncerProgress.map { it * alternateBouncerScrimAlpha }
@@ -54,11 +62,16 @@
     val registerForDismissGestures: Flow<Boolean> =
         transitionToAlternateBouncerProgress.map { it == 1f }.distinctUntilChanged()
 
-    fun showPrimaryBouncer() {
+    fun onTapped() {
         statusBarKeyguardViewManager.showPrimaryBouncer(/* scrimmed */ true)
     }
 
-    fun hideAlternateBouncer() {
+    fun onRemovedFromWindow() {
         statusBarKeyguardViewManager.hideAlternateBouncer(false)
     }
+
+    fun onBackRequested() {
+        statusBarKeyguardViewManager.hideAlternateBouncer(false)
+        dismissCallbackRegistry.notifyDismissCancelled()
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt
index a460d51..9d8a7a8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt
@@ -51,7 +51,8 @@
             onCancel = { 0f },
         )
 
-    val lockscreenAlpha: Flow<Float> = shortcutsAlpha
+    // Show immediately to avoid what can appear to be a flicker on device wakeup
+    val lockscreenAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(1f)
 
     val deviceEntryBackgroundViewAlpha: Flow<Float> =
         transitionAnimation.immediatelyTransitionTo(1f)
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 680f966..2426f97 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
@@ -19,6 +19,7 @@
 
 import androidx.annotation.VisibleForTesting
 import com.android.app.tracing.FlowTracing.traceEmissionCount
+import com.android.systemui.dagger.qualifiers.Application
 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,19 +30,23 @@
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.stateIn
 
 @OptIn(ExperimentalCoroutinesApi::class)
 class KeyguardQuickAffordancesCombinedViewModel
 @Inject
 constructor(
+    @Application private val applicationScope: CoroutineScope,
     private val quickAffordanceInteractor: KeyguardQuickAffordanceInteractor,
     private val keyguardInteractor: KeyguardInteractor,
     shadeInteractor: ShadeInteractor,
@@ -133,9 +138,14 @@
     /** The source of truth of alpha for all of the quick affordances on lockscreen */
     val transitionAlpha: Flow<Float> =
         merge(
-            fadeInAlpha,
-            fadeOutAlpha,
-        )
+                fadeInAlpha,
+                fadeOutAlpha,
+            )
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = 0f,
+            )
 
     /**
      * Whether quick affordances are "opaque enough" to be considered visible to and interactive by
@@ -199,38 +209,42 @@
     private fun button(
         position: KeyguardQuickAffordancePosition
     ): Flow<KeyguardQuickAffordanceViewModel> {
-        return previewMode.flatMapLatest { previewMode ->
-            combine(
-                    if (previewMode.isInPreviewMode) {
-                        quickAffordanceInteractor.quickAffordanceAlwaysVisible(position = position)
-                    } else {
-                        quickAffordanceInteractor.quickAffordance(position = position)
-                    },
-                    keyguardInteractor.animateDozingTransitions.distinctUntilChanged(),
-                    areQuickAffordancesFullyOpaque,
-                    selectedPreviewSlotId,
-                    quickAffordanceInteractor.useLongPress(),
-                ) { model, animateReveal, isFullyOpaque, selectedPreviewSlotId, useLongPress ->
-                    val slotId = position.toSlotId()
-                    val isSelected = selectedPreviewSlotId == slotId
-                    model.toViewModel(
-                        animateReveal = !previewMode.isInPreviewMode && animateReveal,
-                        isClickable = isFullyOpaque && !previewMode.isInPreviewMode,
-                        isSelected =
-                            previewMode.isInPreviewMode &&
-                                previewMode.shouldHighlightSelectedAffordance &&
-                                isSelected,
-                        isDimmed =
-                            previewMode.isInPreviewMode &&
-                                previewMode.shouldHighlightSelectedAffordance &&
-                                !isSelected,
-                        forceInactive = previewMode.isInPreviewMode,
-                        slotId = slotId,
-                        useLongPress = useLongPress,
-                    )
-                }
-                .distinctUntilChanged()
-        }.traceEmissionCount({"QuickAfforcances#button${position.toSlotId()}"})
+        return previewMode
+            .flatMapLatest { previewMode ->
+                combine(
+                        if (previewMode.isInPreviewMode) {
+                            quickAffordanceInteractor.quickAffordanceAlwaysVisible(
+                                position = position
+                            )
+                        } else {
+                            quickAffordanceInteractor.quickAffordance(position = position)
+                        },
+                        keyguardInteractor.animateDozingTransitions.distinctUntilChanged(),
+                        areQuickAffordancesFullyOpaque,
+                        selectedPreviewSlotId,
+                        quickAffordanceInteractor.useLongPress(),
+                    ) { model, animateReveal, isFullyOpaque, selectedPreviewSlotId, useLongPress ->
+                        val slotId = position.toSlotId()
+                        val isSelected = selectedPreviewSlotId == slotId
+                        model.toViewModel(
+                            animateReveal = !previewMode.isInPreviewMode && animateReveal,
+                            isClickable = isFullyOpaque && !previewMode.isInPreviewMode,
+                            isSelected =
+                                previewMode.isInPreviewMode &&
+                                    previewMode.shouldHighlightSelectedAffordance &&
+                                    isSelected,
+                            isDimmed =
+                                previewMode.isInPreviewMode &&
+                                    previewMode.shouldHighlightSelectedAffordance &&
+                                    !isSelected,
+                            forceInactive = previewMode.isInPreviewMode,
+                            slotId = slotId,
+                            useLongPress = useLongPress,
+                        )
+                    }
+                    .distinctUntilChanged()
+            }
+            .traceEmissionCount({ "QuickAfforcances#button${position.toSlotId()}" })
     }
 
     private fun KeyguardQuickAffordanceModel.toViewModel(
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 38a2b1b..050ef6f 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
@@ -83,6 +83,7 @@
     private val communalInteractor: CommunalInteractor,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor,
+    private val alternateBouncerToAodTransitionViewModel: AlternateBouncerToAodTransitionViewModel,
     private val alternateBouncerToGoneTransitionViewModel:
         AlternateBouncerToGoneTransitionViewModel,
     private val alternateBouncerToLockscreenTransitionViewModel:
@@ -239,6 +240,7 @@
                 merge(
                         alphaOnShadeExpansion,
                         keyguardInteractor.dismissAlpha,
+                        alternateBouncerToAodTransitionViewModel.lockscreenAlpha(viewState),
                         alternateBouncerToGoneTransitionViewModel.lockscreenAlpha(viewState),
                         alternateBouncerToLockscreenTransitionViewModel.lockscreenAlpha(viewState),
                         aodToGoneTransitionViewModel.lockscreenAlpha(viewState),
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 3fffeff..59cb6e5 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,40 +20,43 @@
 import com.android.compose.animation.scene.ContentKey
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.biometrics.AuthController
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
 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.res.R
 import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
-import javax.inject.Inject
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
 
-@SysUISingleton
 class LockscreenContentViewModel
-@Inject
+@AssistedInject
 constructor(
     clockInteractor: KeyguardClockInteractor,
     private val interactor: KeyguardBlueprintInteractor,
     private val authController: AuthController,
     val touchHandling: KeyguardTouchHandlingViewModel,
-    val shadeInteractor: ShadeInteractor,
-    @Application private val applicationScope: CoroutineScope,
-    unfoldTransitionInteractor: UnfoldTransitionInteractor,
-    occlusionInteractor: SceneContainerOcclusionInteractor,
-) {
+    private val shadeInteractor: ShadeInteractor,
+    private val unfoldTransitionInteractor: UnfoldTransitionInteractor,
+    private val occlusionInteractor: SceneContainerOcclusionInteractor,
+) : SysUiViewModel() {
     @VisibleForTesting val clockSize = clockInteractor.clockSize
 
     val isUdfpsVisible: Boolean
@@ -61,32 +64,36 @@
 
     val isShadeLayoutWide: StateFlow<Boolean> = shadeInteractor.isShadeLayoutWide
 
+    private val _unfoldTranslations = MutableStateFlow(UnfoldTranslations())
     /** Amount of horizontal translation that should be applied to elements in the scene. */
-    val unfoldTranslations: StateFlow<UnfoldTranslations> =
-        combine(
-                unfoldTransitionInteractor.unfoldTranslationX(isOnStartSide = true),
-                unfoldTransitionInteractor.unfoldTranslationX(isOnStartSide = false),
-            ) { start, end ->
-                UnfoldTranslations(
-                    start = start,
-                    end = end,
-                )
-            }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = UnfoldTranslations(),
-            )
+    val unfoldTranslations: StateFlow<UnfoldTranslations> = _unfoldTranslations.asStateFlow()
 
+    private val _isContentVisible = MutableStateFlow(true)
     /** Whether the content of the scene UI should be shown. */
-    val isContentVisible: StateFlow<Boolean> =
-        occlusionInteractor.isOccludingActivityShown
-            .map { !it }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = true,
-            )
+    val isContentVisible: StateFlow<Boolean> = _isContentVisible.asStateFlow()
+
+    override suspend fun onActivated() {
+        coroutineScope {
+            launch {
+                combine(
+                        unfoldTransitionInteractor.unfoldTranslationX(isOnStartSide = true),
+                        unfoldTransitionInteractor.unfoldTranslationX(isOnStartSide = false),
+                    ) { start, end ->
+                        UnfoldTranslations(
+                            start = start,
+                            end = end,
+                        )
+                    }
+                    .collectLatest { _unfoldTranslations.value = it }
+            }
+
+            launch {
+                occlusionInteractor.isOccludingActivityShown
+                    .map { !it }
+                    .collectLatest { _isContentVisible.value = it }
+            }
+        }
+    }
 
     /**
      * Returns a flow that indicates whether lockscreen notifications should be rendered in the
@@ -142,4 +149,9 @@
          */
         val end: Float = 0f,
     )
+
+    @AssistedFactory
+    interface Factory {
+        fun create(): LockscreenContentViewModel
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneActionsViewModel.kt
new file mode 100644
index 0000000..7383f57
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneActionsViewModel.kt
@@ -0,0 +1,125 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.keyguard.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.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+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.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
+@AssistedInject
+constructor(
+    private val deviceEntryInteractor: DeviceEntryInteractor,
+    private val communalInteractor: CommunalInteractor,
+    private val shadeInteractor: ShadeInteractor,
+) : SceneActionsViewModel() {
+
+    override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
+        shadeInteractor.isShadeTouchable
+            .flatMapLatest { isShadeTouchable ->
+                if (!isShadeTouchable) {
+                    flowOf(emptyMap())
+                } else {
+                    combine(
+                        deviceEntryInteractor.isUnlocked,
+                        communalInteractor.isCommunalAvailable,
+                        shadeInteractor.shadeMode,
+                    ) { isDeviceUnlocked, isCommunalAvailable, shadeMode ->
+                        val notifShadeSceneKey =
+                            UserActionResult(
+                                toScene = SceneFamilies.NotifShade,
+                                transitionKey =
+                                    ToSplitShade.takeIf { shadeMode is ShadeMode.Split },
+                            )
+
+                        mapOf(
+                                Swipe.Left to
+                                    UserActionResult(Scenes.Communal).takeIf {
+                                        isCommunalAvailable
+                                    },
+                                Swipe.Up to if (isDeviceUnlocked) Scenes.Gone else Scenes.Bouncer,
+
+                                // Swiping down from the top edge goes to QS (or shade if in split
+                                // shade mode).
+                                swipeDownFromTop(pointerCount = 1) to
+                                    if (shadeMode is ShadeMode.Single) {
+                                        UserActionResult(Scenes.QuickSettings)
+                                    } else {
+                                        notifShadeSceneKey
+                                    },
+
+                                // TODO(b/338577208): Remove once we add Dual Shade invocation zones
+                                swipeDownFromTop(pointerCount = 2) to
+                                    UserActionResult(
+                                        toScene = SceneFamilies.QuickSettings,
+                                        transitionKey =
+                                            ToSplitShade.takeIf { shadeMode is ShadeMode.Split }
+                                    ),
+
+                                // Swiping down, not from the edge, always navigates to the notif
+                                // shade scene.
+                                swipeDown(pointerCount = 1) to notifShadeSceneKey,
+                                swipeDown(pointerCount = 2) to notifShadeSceneKey,
+                            )
+                            .filterValuesNotNull()
+                    }
+                }
+            }
+            .collectLatest { setActions(it) }
+    }
+
+    private fun swipeDownFromTop(pointerCount: Int): Swipe {
+        return Swipe(
+            SwipeDirection.Down,
+            fromSource = Edge.Top,
+            pointerCount = pointerCount,
+        )
+    }
+
+    private fun swipeDown(pointerCount: Int): Swipe {
+        return Swipe(
+            SwipeDirection.Down,
+            pointerCount = pointerCount,
+        )
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(): LockscreenSceneActionsViewModel
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
deleted file mode 100644
index 15892e9..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
+++ /dev/null
@@ -1,116 +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.
- */
-
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
-package com.android.systemui.keyguard.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.communal.domain.interactor.CommunalInteractor
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
-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.shade.domain.interactor.ShadeInteractor
-import com.android.systemui.shade.shared.model.ShadeMode
-import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
-import com.android.systemui.util.kotlin.filterValuesNotNull
-import javax.inject.Inject
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
-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. */
-@SysUISingleton
-class LockscreenSceneViewModel
-@Inject
-constructor(
-    private val deviceEntryInteractor: DeviceEntryInteractor,
-    private val communalInteractor: CommunalInteractor,
-    private val shadeInteractor: ShadeInteractor,
-    val touchHandling: KeyguardTouchHandlingViewModel,
-    val notifications: NotificationsPlaceholderViewModel,
-) {
-    val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
-        shadeInteractor.isShadeTouchable.flatMapLatest { isShadeTouchable ->
-            if (!isShadeTouchable) {
-                flowOf(emptyMap())
-            } else {
-                combine(
-                    deviceEntryInteractor.isUnlocked,
-                    communalInteractor.isCommunalAvailable,
-                    shadeInteractor.shadeMode,
-                ) { isDeviceUnlocked, isCommunalAvailable, shadeMode ->
-                    val notifShadeSceneKey =
-                        UserActionResult(
-                            toScene = SceneFamilies.NotifShade,
-                            transitionKey = ToSplitShade.takeIf { shadeMode is ShadeMode.Split },
-                        )
-
-                    mapOf(
-                            Swipe.Left to
-                                UserActionResult(Scenes.Communal).takeIf { isCommunalAvailable },
-                            Swipe.Up to if (isDeviceUnlocked) Scenes.Gone else Scenes.Bouncer,
-
-                            // Swiping down from the top edge goes to QS (or shade if in split shade
-                            // mode).
-                            swipeDownFromTop(pointerCount = 1) to
-                                if (shadeMode is ShadeMode.Single) {
-                                    UserActionResult(Scenes.QuickSettings)
-                                } else {
-                                    notifShadeSceneKey
-                                },
-
-                            // TODO(b/338577208): Remove once we add Dual Shade invocation zones.
-                            swipeDownFromTop(pointerCount = 2) to
-                                UserActionResult(
-                                    toScene = SceneFamilies.QuickSettings,
-                                    transitionKey =
-                                        ToSplitShade.takeIf { shadeMode is ShadeMode.Split }
-                                ),
-
-                            // Swiping down, not from the edge, always navigates to the notif shade
-                            // scene.
-                            swipeDown(pointerCount = 1) to notifShadeSceneKey,
-                            swipeDown(pointerCount = 2) to notifShadeSceneKey,
-                        )
-                        .filterValuesNotNull()
-                }
-            }
-        }
-
-    private fun swipeDownFromTop(pointerCount: Int): Swipe {
-        return Swipe(
-            SwipeDirection.Down,
-            fromSource = Edge.Top,
-            pointerCount = pointerCount,
-        )
-    }
-
-    private fun swipeDown(pointerCount: Int): Swipe {
-        return Swipe(
-            SwipeDirection.Down,
-            pointerCount = pointerCount,
-        )
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
index 661da6d..c2b5d98 100644
--- a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
@@ -227,13 +227,33 @@
 }
 
 /**
+ * Runs the given [block] in a new coroutine when `this` [View]'s Window's [WindowLifecycleState] is
+ * at least at [state] (or immediately after calling this function if the window is already at least
+ * at [state]), automatically canceling the work when the window is no longer at least at that
+ * state.
+ *
+ * [block] may be run multiple times, running once per every time this` [View]'s Window's
+ * [WindowLifecycleState] becomes at least at [state].
+ */
+suspend fun View.repeatOnWindowLifecycle(
+    state: WindowLifecycleState,
+    block: suspend CoroutineScope.() -> Unit,
+): Nothing {
+    when (state) {
+        WindowLifecycleState.ATTACHED -> repeatWhenAttachedToWindow(block)
+        WindowLifecycleState.VISIBLE -> repeatWhenWindowIsVisible(block)
+        WindowLifecycleState.FOCUSED -> repeatWhenWindowHasFocus(block)
+    }
+}
+
+/**
  * Runs the given [block] every time the [View] becomes attached (or immediately after calling this
  * function, if the view was already attached), automatically canceling the work when the view
  * becomes detached.
  *
  * Only use from the main thread.
  *
- * The [block] may be run multiple times, running once per every time the view is attached.
+ * [block] may be run multiple times, running once per every time the view is attached.
  */
 @MainThread
 suspend fun View.repeatWhenAttachedToWindow(block: suspend CoroutineScope.() -> Unit): Nothing {
@@ -249,7 +269,7 @@
  *
  * Only use from the main thread.
  *
- * The [block] may be run multiple times, running once per every time the window becomes visible.
+ * [block] may be run multiple times, running once per every time the window becomes visible.
  */
 @MainThread
 suspend fun View.repeatWhenWindowIsVisible(block: suspend CoroutineScope.() -> Unit): Nothing {
@@ -265,7 +285,7 @@
  *
  * Only use from the main thread.
  *
- * The [block] may be run multiple times, running once per every time the window is focused.
+ * [block] may be run multiple times, running once per every time the window is focused.
  */
 @MainThread
 suspend fun View.repeatWhenWindowHasFocus(block: suspend CoroutineScope.() -> Unit): Nothing {
@@ -274,6 +294,21 @@
     awaitCancellation() // satisfies return type of Nothing
 }
 
+/** Lifecycle states for a [View]'s interaction with a [android.view.Window]. */
+enum class WindowLifecycleState {
+    /** Indicates that the [View] is attached to a [android.view.Window]. */
+    ATTACHED,
+    /**
+     * Indicates that the [View] is attached to a [android.view.Window], and the window is visible.
+     */
+    VISIBLE,
+    /**
+     * Indicates that the [View] is attached to a [android.view.Window], and the window is visible
+     * and focused.
+     */
+    FOCUSED
+}
+
 private val View.isAttached
     get() = conflatedCallbackFlow {
         val onAttachListener =
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt
index 0af5fea..7731481 100644
--- a/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt
@@ -16,9 +16,10 @@
 
 package com.android.systemui.lifecycle
 
+import android.view.View
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.remember
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
 
 /** Base class for all System UI view-models. */
 abstract class SysUiViewModel : SafeActivatable() {
@@ -37,8 +38,20 @@
 fun <T : SysUiViewModel> rememberViewModel(
     key: Any = Unit,
     factory: () -> T,
-): T {
-    val instance = remember(key) { factory() }
-    LaunchedEffect(instance) { instance.activate() }
-    return instance
-}
+): 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(
+    minWindowLifecycleState: WindowLifecycleState,
+    factory: () -> T,
+    block: suspend CoroutineScope.(T) -> Unit,
+): Nothing =
+    repeatOnWindowLifecycle(minWindowLifecycleState) {
+        val instance = factory()
+        launch { instance.activate() }
+        block(instance)
+    }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaController.kt
index e17c0bb..9bea6e9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaController.kt
@@ -18,6 +18,7 @@
 
 import android.content.Context
 import android.content.res.Configuration
+import android.graphics.Rect
 import android.view.View
 import android.view.ViewGroup
 import androidx.annotation.VisibleForTesting
@@ -125,6 +126,7 @@
     /** single pane media container placed at the top of the notifications list */
     var singlePaneContainer: MediaContainerView? = null
         private set
+
     private var splitShadeContainer: ViewGroup? = null
 
     /**
@@ -185,6 +187,13 @@
         }
     }
 
+    fun isWithinMediaViewBounds(x: Int, y: Int): Boolean {
+        val bounds = Rect()
+        mediaHost.hostView.getBoundsOnScreen(bounds)
+
+        return bounds.contains(x, y)
+    }
+
     fun refreshMediaPosition(reason: String) {
         val currentState = statusBarStateController.state
 
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 c5d7b25..46c5c18 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
@@ -888,8 +888,6 @@
         heightInSceneContainerPx = height
         mediaCarouselScrollHandler.playerWidthPlusPadding =
             width + context.resources.getDimensionPixelSize(R.dimen.qs_media_padding)
-        mediaContent.minimumWidth = widthInSceneContainerPx
-        mediaContent.minimumHeight = heightInSceneContainerPx
         updatePlayers(recreateMedia = true)
     }
 
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 88a28bf..091b886 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
@@ -1210,7 +1210,6 @@
             (onCommunalNotDreaming && qsExpansion == 0.0f) || onCommunalDreamingAndShadeExpanding
         val location =
             when {
-                mediaFlags.isSceneContainerEnabled() -> desiredLocation
                 dreamOverlayActive && dreamMediaComplicationActive -> LOCATION_DREAM_OVERLAY
                 onCommunal -> LOCATION_COMMUNAL_HUB
                 (qsExpansion > 0.0f || inSplitShade) && !onLockscreen -> LOCATION_QS
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index b48b409..875e505 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -94,6 +94,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
+import com.android.systemui.volume.panel.domain.interactor.VolumePanelGlobalStateInteractor;
 
 import dagger.assisted.Assisted;
 import dagger.assisted.AssistedFactory;
@@ -173,6 +174,7 @@
     private float mActiveRadius;
     private FeatureFlags mFeatureFlags;
     private UserTracker mUserTracker;
+    private VolumePanelGlobalStateInteractor mVolumePanelGlobalStateInteractor;
 
     public enum BroadcastNotifyDialog {
         ACTION_FIRST_LAUNCH,
@@ -195,6 +197,7 @@
             PowerExemptionManager powerExemptionManager,
             KeyguardManager keyGuardManager,
             FeatureFlags featureFlags,
+            VolumePanelGlobalStateInteractor volumePanelGlobalStateInteractor,
             UserTracker userTracker) {
         mContext = context;
         mPackageName = packageName;
@@ -209,6 +212,7 @@
         mFeatureFlags = featureFlags;
         mUserTracker = userTracker;
         mToken = token;
+        mVolumePanelGlobalStateInteractor = volumePanelGlobalStateInteractor;
         InfoMediaManager imm =
                 InfoMediaManager.createInstance(mContext, packageName, userHandle, lbm, token);
         mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, packageName);
@@ -436,7 +440,7 @@
             launchIntent.putExtra(EXTRA_ROUTE_ID, routeId);
             launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             mCallback.dismissDialog();
-            mActivityStarter.startActivity(launchIntent, true, controller);
+            startActivity(launchIntent, controller);
         }
     }
 
@@ -447,7 +451,7 @@
         if (launchIntent != null) {
             launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             mCallback.dismissDialog();
-            mActivityStarter.startActivity(launchIntent, true, controller);
+            startActivity(launchIntent, controller);
         }
     }
 
@@ -951,10 +955,10 @@
             deepLinkIntent.putExtra(
                     Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY,
                     PAGE_CONNECTED_DEVICES_KEY);
-            mActivityStarter.startActivity(deepLinkIntent, true, controller);
+            startActivity(deepLinkIntent, controller);
             return;
         }
-        mActivityStarter.startActivity(launchIntent, true, controller);
+        startActivity(launchIntent, controller);
     }
 
     void launchLeBroadcastNotifyDialog(View mediaOutputDialog, BroadcastSender broadcastSender,
@@ -998,6 +1002,7 @@
                         mPowerExemptionManager,
                         mKeyGuardManager,
                         mFeatureFlags,
+                        mVolumePanelGlobalStateInteractor,
                         mUserTracker);
         MediaOutputBroadcastDialog dialog = new MediaOutputBroadcastDialog(mContext, true,
                 broadcastSender, controller);
@@ -1244,6 +1249,13 @@
         return !device.isVolumeFixed();
     }
 
+    private void startActivity(Intent intent, ActivityTransitionAnimator.Controller controller) {
+        // Media Output dialog can be shown from the volume panel. This makes sure the panel is
+        // closed when navigating to another activity, so it doesn't stays on top of it
+        mVolumePanelGlobalStateInteractor.setVisible(false);
+        mActivityStarter.startActivity(intent, true, controller);
+    }
+
     @Override
     public void onDevicesUpdated(List<NearbyDevice> nearbyDevices) throws RemoteException {
         mNearbyDeviceInfoMap.clear();
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt
index d6affd2..228b576 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt
@@ -32,6 +32,10 @@
 import android.view.View
 import android.view.ViewGroup
 import android.view.accessibility.AccessibilityEvent
+import android.widget.ImageView
+import androidx.annotation.ColorRes
+import androidx.annotation.DrawableRes
+import androidx.annotation.StringRes
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleOwner
 import androidx.lifecycle.LifecycleRegistry
@@ -52,6 +56,7 @@
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.AsyncActivityLauncher
+import java.lang.IllegalArgumentException
 import javax.inject.Inject
 
 class MediaProjectionAppSelectorActivity(
@@ -116,6 +121,7 @@
 
         super.onCreate(savedInstanceState)
         controller.init()
+        setIcon()
         // we override AppList's AccessibilityDelegate set in ResolverActivity.onCreate because in
         // our case this delegate must extend RecyclerViewAccessibilityDelegate, otherwise
         // RecyclerView scrolling is broken
@@ -298,6 +304,29 @@
     override fun createContentPreviewView(parent: ViewGroup): ViewGroup =
         recentsViewController.createView(parent)
 
+    /** Set up intent for the [ChooserActivity] */
+    private fun Intent.configureChooserIntent(
+        resources: Resources,
+        hostUserHandle: UserHandle,
+        personalProfileUserHandle: UserHandle,
+    ) {
+        // Specify the query intent to show icons for all apps on the chooser screen
+        val queryIntent = Intent(Intent.ACTION_MAIN).apply { addCategory(Intent.CATEGORY_LAUNCHER) }
+        putExtra(Intent.EXTRA_INTENT, queryIntent)
+
+        // Update the title of the chooser
+        putExtra(Intent.EXTRA_TITLE, resources.getString(titleResId))
+
+        // Select host app's profile tab by default
+        val selectedProfile =
+            if (hostUserHandle == personalProfileUserHandle) {
+                PROFILE_PERSONAL
+            } else {
+                PROFILE_WORK
+            }
+        putExtra(EXTRA_SELECTED_PROFILE, selectedProfile)
+    }
+
     private val hostUserHandle: UserHandle
         get() {
             val extras =
@@ -321,6 +350,54 @@
             return intent.getIntExtra(EXTRA_HOST_APP_UID, /* defaultValue= */ -1)
         }
 
+    /**
+     * The type of screen sharing being performed. Used to show the right text and icon in the
+     * activity.
+     */
+    private val screenShareType: ScreenShareType?
+        get() {
+            if (!intent.hasExtra(EXTRA_SCREEN_SHARE_TYPE)) {
+                return null
+            } else {
+                val type = intent.getStringExtra(EXTRA_SCREEN_SHARE_TYPE) ?: return null
+                return try {
+                    enumValueOf<ScreenShareType>(type)
+                } catch (e: IllegalArgumentException) {
+                    null
+                }
+            }
+        }
+
+    @get:StringRes
+    private val titleResId: Int
+        get() =
+            when (screenShareType) {
+                ScreenShareType.ShareToApp ->
+                    R.string.media_projection_entry_share_app_selector_title
+                ScreenShareType.SystemCast ->
+                    R.string.media_projection_entry_cast_app_selector_title
+                ScreenShareType.ScreenRecord -> R.string.screenrecord_app_selector_title
+                null -> R.string.screen_share_generic_app_selector_title
+            }
+
+    @get:DrawableRes
+    private val iconResId: Int
+        get() =
+            when (screenShareType) {
+                ScreenShareType.ShareToApp -> R.drawable.ic_present_to_all
+                ScreenShareType.SystemCast -> R.drawable.ic_cast_connected
+                ScreenShareType.ScreenRecord -> R.drawable.ic_screenrecord
+                null -> R.drawable.ic_present_to_all
+            }
+
+    @get:ColorRes
+    private val iconTintResId: Int?
+        get() =
+            when (screenShareType) {
+                ScreenShareType.ScreenRecord -> R.color.screenrecord_icon_color
+                else -> null
+            }
+
     companion object {
         const val TAG = "MediaProjectionAppSelectorActivity"
 
@@ -343,30 +420,18 @@
         const val EXTRA_HOST_APP_UID = "launched_from_host_uid"
         const val KEY_CAPTURE_TARGET = "capture_region"
 
-        /** Set up intent for the [ChooserActivity] */
-        private fun Intent.configureChooserIntent(
-            resources: Resources,
-            hostUserHandle: UserHandle,
-            personalProfileUserHandle: UserHandle
-        ) {
-            // Specify the query intent to show icons for all apps on the chooser screen
-            val queryIntent =
-                Intent(Intent.ACTION_MAIN).apply { addCategory(Intent.CATEGORY_LAUNCHER) }
-            putExtra(Intent.EXTRA_INTENT, queryIntent)
+        /**
+         * The type of screen sharing being performed.
+         *
+         * The value set for this extra should match the name of a [ScreenShareType].
+         */
+        const val EXTRA_SCREEN_SHARE_TYPE = "screen_share_type"
+    }
 
-            // Update the title of the chooser
-            val title = resources.getString(R.string.screen_share_permission_app_selector_title)
-            putExtra(Intent.EXTRA_TITLE, title)
-
-            // Select host app's profile tab by default
-            val selectedProfile =
-                if (hostUserHandle == personalProfileUserHandle) {
-                    PROFILE_PERSONAL
-                } else {
-                    PROFILE_WORK
-                }
-            putExtra(EXTRA_SELECTED_PROFILE, selectedProfile)
-        }
+    private fun setIcon() {
+        val iconView = findViewById<ImageView>(R.id.media_projection_app_selector_icon) ?: return
+        iconView.setImageResource(iconResId)
+        iconTintResId?.let { iconView.setColorFilter(this.resources.getColor(it, this.theme)) }
     }
 
     private fun setAppListAccessibilityDelegate() {
@@ -406,4 +471,14 @@
             return delegate.onRequestSendAccessibilityEvent(host, child, event)
         }
     }
+
+    /** Enum describing what type of app screen sharing is being performed. */
+    enum class ScreenShareType {
+        /** The selected app will be cast to another device. */
+        SystemCast,
+        /** The selected app will be shared to another app on the device. */
+        ShareToApp,
+        /** The selected app will be recorded. */
+        ScreenRecord,
+    }
 }
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 46aa064..2ce7044 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
@@ -27,7 +27,6 @@
 import android.window.RemoteTransition
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
-import com.android.systemui.Flags.pssAppSelectorAbruptExitFix
 import com.android.systemui.Flags.pssAppSelectorRecentsSplitScreen
 import com.android.systemui.display.naturalBounds
 import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorResultHandler
@@ -160,7 +159,7 @@
 
 
     private fun createAnimation(task: RecentTask, view: View): ActivityOptions =
-        if (pssAppSelectorAbruptExitFix() && task.isForegroundTask) {
+        if (task.isForegroundTask) {
             // When the selected task is in the foreground, the scale up animation doesn't work.
             // We fallback to the default close animation.
             ActivityOptions.makeCustomTaskAnimation(
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
index 3c83db3..18c6f53 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -73,8 +73,7 @@
 
 import dagger.Lazy;
 
-public class MediaProjectionPermissionActivity extends Activity
-        implements DialogInterface.OnClickListener {
+public class MediaProjectionPermissionActivity extends Activity {
     private static final String TAG = "MediaProjectionPermissionActivity";
     private static final float MAX_APP_NAME_SIZE_PX = 500f;
     private static final String ELLIPSIS = "\u2026";
@@ -269,7 +268,8 @@
         Consumer<BaseMediaProjectionPermissionDialogDelegate<AlertDialog>> onStartRecordingClicked =
                 dialog -> {
                     ScreenShareOption selectedOption = dialog.getSelectedScreenShareOption();
-                    grantMediaProjectionPermission(selectedOption.getMode());
+                    grantMediaProjectionPermission(
+                            selectedOption.getMode(), hasCastingCapabilities);
                 };
         Runnable onCancelClicked = () -> finish(RECORD_CANCEL, /* projection= */ null);
         if (hasCastingCapabilities) {
@@ -305,19 +305,6 @@
         }
     }
 
-    @Override
-    public void onClick(DialogInterface dialog, int which) {
-        if (which == AlertDialog.BUTTON_POSITIVE) {
-            grantMediaProjectionPermission(ENTIRE_SCREEN);
-        } else {
-            if (mDialog != null) {
-                mDialog.dismiss();
-            }
-            setResult(RESULT_CANCELED);
-            finish(RECORD_CANCEL, /* projection= */ null);
-        }
-    }
-
     private void setUpDialog(AlertDialog dialog) {
         SystemUIDialog.registerDismissListener(dialog);
         SystemUIDialog.applyFlags(dialog);
@@ -345,25 +332,21 @@
         return false;
     }
 
-    private void grantMediaProjectionPermission(int screenShareMode) {
+    private void grantMediaProjectionPermission(
+            int screenShareMode, boolean hasCastingCapabilities) {
         try {
+            IMediaProjection projection = MediaProjectionServiceHelper.createOrReuseProjection(
+                    mUid, mPackageName, mReviewGrantedConsentRequired);
             if (screenShareMode == ENTIRE_SCREEN) {
-                final IMediaProjection projection =
-                        MediaProjectionServiceHelper.createOrReuseProjection(mUid, mPackageName,
-                                mReviewGrantedConsentRequired);
                 final Intent intent = new Intent();
-                intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION,
-                        projection.asBinder());
+                setCommonIntentExtras(intent, hasCastingCapabilities, projection);
                 setResult(RESULT_OK, intent);
                 finish(RECORD_CONTENT_DISPLAY, projection);
             }
             if (screenShareMode == SINGLE_APP) {
-                IMediaProjection projection = MediaProjectionServiceHelper.createOrReuseProjection(
-                        mUid, mPackageName, mReviewGrantedConsentRequired);
                 final Intent intent = new Intent(this,
                         MediaProjectionAppSelectorActivity.class);
-                intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION,
-                        projection.asBinder());
+                setCommonIntentExtras(intent, hasCastingCapabilities, projection);
                 intent.putExtra(MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_USER_HANDLE,
                         getHostUserHandle());
                 intent.putExtra(
@@ -391,6 +374,19 @@
         }
     }
 
+    private void setCommonIntentExtras(
+            Intent intent,
+            boolean hasCastingCapabilities,
+            IMediaProjection projection) throws RemoteException {
+        intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION,
+                projection.asBinder());
+        intent.putExtra(
+                MediaProjectionAppSelectorActivity.EXTRA_SCREEN_SHARE_TYPE,
+                hasCastingCapabilities
+                        ? MediaProjectionAppSelectorActivity.ScreenShareType.SystemCast.name()
+                        : MediaProjectionAppSelectorActivity.ScreenShareType.ShareToApp.name());
+    }
+
     private UserHandle getHostUserHandle() {
         return UserHandle.getUserHandleForUid(getLaunchedFromUid());
     }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
index 13a786a..ac878c2 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
@@ -38,6 +38,7 @@
 import android.content.res.Configuration;
 import android.database.ContentObserver;
 import android.inputmethodservice.InputMethodService;
+import android.inputmethodservice.InputMethodService.ImeWindowVisibility;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
@@ -516,7 +517,7 @@
      * @return Whether the IME is shown on top of the screen given the {@code vis} flag of
      * {@link InputMethodService} and the keyguard states.
      */
-    public boolean isImeShown(int vis) {
+    public boolean isImeShown(@ImeWindowVisibility int vis) {
         View shadeWindowView =  mNotificationShadeWindowController.getWindowRootView();
         boolean isKeyguardShowing = mKeyguardStateController.isShowing();
         boolean imeVisibleOnShade = shadeWindowView != null && shadeWindowView.isAttachedToWindow()
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java
index e2ba761..a8b979e0 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java
@@ -16,11 +16,16 @@
 
 package com.android.systemui.navigationbar;
 
+import static com.android.systemui.Flags.enableViewCaptureTracing;
+import static com.android.systemui.util.ConvenienceExtensionsKt.toKotlinLazy;
+
 import android.content.Context;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.WindowManager;
 
+import com.android.app.viewcapture.ViewCapture;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.systemui.dagger.qualifiers.DisplayId;
 import com.android.systemui.navigationbar.NavigationBarComponent.NavigationBarScope;
 import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
@@ -28,6 +33,7 @@
 import com.android.systemui.navigationbar.views.NavigationBarView;
 import com.android.systemui.res.R;
 
+import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
 
@@ -73,4 +79,15 @@
     static WindowManager provideWindowManager(@DisplayId Context context) {
         return context.getSystemService(WindowManager.class);
     }
+
+    /** A ViewCaptureAwareWindowManager specific to the display's context. */
+    @Provides
+    @NavigationBarScope
+    @DisplayId
+    static ViewCaptureAwareWindowManager provideViewCaptureAwareWindowManager(
+            @DisplayId WindowManager windowManager, Lazy<ViewCapture> daggerLazyViewCapture) {
+        return new ViewCaptureAwareWindowManager(windowManager,
+                /* lazyViewCapture= */ toKotlinLazy(daggerLazyViewCapture),
+                /* isViewCaptureEnabled= */ enableViewCaptureTracing());
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 15b1e4d..cb0bb4a 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -42,6 +42,8 @@
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
 import android.inputmethodservice.InputMethodService;
+import android.inputmethodservice.InputMethodService.BackDispositionMode;
+import android.inputmethodservice.InputMethodService.ImeWindowVisibility;
 import android.os.RemoteException;
 import android.os.Trace;
 import android.util.Log;
@@ -424,8 +426,8 @@
     }
 
     @Override
-    public void setImeWindowStatus(int displayId, int vis, int backDisposition,
-            boolean showImeSwitcher) {
+    public void setImeWindowStatus(int displayId, @ImeWindowVisibility int vis,
+            @BackDispositionMode int backDisposition, boolean showImeSwitcher) {
         boolean imeShown = mNavBarHelper.isImeShown(vis);
         if (!imeShown) {
             // Count imperceptible changes as visible so we transition taskbar out quickly.
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 7b248eb..c706c3e 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
@@ -68,6 +68,8 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.Region;
+import android.inputmethodservice.InputMethodService.BackDispositionMode;
+import android.inputmethodservice.InputMethodService.ImeWindowVisibility;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
@@ -102,6 +104,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.UiEvent;
@@ -196,6 +199,7 @@
     private final Context mContext;
     private final Bundle mSavedState;
     private final WindowManager mWindowManager;
+    private final ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
     private final AccessibilityManager mAccessibilityManager;
     private final DeviceProvisionedController mDeviceProvisionedController;
     private final StatusBarStateController mStatusBarStateController;
@@ -556,6 +560,7 @@
             @Nullable Bundle savedState,
             @DisplayId Context context,
             @DisplayId WindowManager windowManager,
+            @DisplayId ViewCaptureAwareWindowManager viewCaptureAwareWindowManager,
             Lazy<AssistManager> assistManagerLazy,
             AccessibilityManager accessibilityManager,
             DeviceProvisionedController deviceProvisionedController,
@@ -601,6 +606,7 @@
         mContext = context;
         mSavedState = savedState;
         mWindowManager = windowManager;
+        mViewCaptureAwareWindowManager = viewCaptureAwareWindowManager;
         mAccessibilityManager = accessibilityManager;
         mDeviceProvisionedController = deviceProvisionedController;
         mStatusBarStateController = statusBarStateController;
@@ -721,7 +727,7 @@
 
         if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + mView);
 
-        mWindowManager.addView(mFrame,
+        mViewCaptureAwareWindowManager.addView(mFrame,
                 getBarLayoutParams(mContext.getResources().getConfiguration().windowConfiguration
                         .getRotation()));
         mDisplayId = mContext.getDisplayId();
@@ -764,7 +770,7 @@
             mCommandQueue.removeCallback(this);
             Trace.beginSection("NavigationBar#removeViewImmediate");
             try {
-                mWindowManager.removeViewImmediate(mView.getRootView());
+                mViewCaptureAwareWindowManager.removeViewImmediate(mView.getRootView());
             } finally {
                 Trace.endSection();
             }
@@ -866,7 +872,7 @@
         if (mOrientationHandle != null) {
             resetSecondaryHandle();
             getBarTransitions().removeDarkIntensityListener(mOrientationHandleIntensityListener);
-            mWindowManager.removeView(mOrientationHandle);
+            mViewCaptureAwareWindowManager.removeView(mOrientationHandle);
             mOrientationHandle.getViewTreeObserver().removeOnGlobalLayoutListener(
                     mOrientationHandleGlobalLayoutListener);
         }
@@ -937,7 +943,7 @@
         mOrientationParams.setTitle("SecondaryHomeHandle" + mContext.getDisplayId());
         mOrientationParams.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION
                 | WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
-        mWindowManager.addView(mOrientationHandle, mOrientationParams);
+        mViewCaptureAwareWindowManager.addView(mOrientationHandle, mOrientationParams);
         mOrientationHandle.setVisibility(View.GONE);
 
         logNavbarOrientation("initSecondaryHomeHandleForRotation");
@@ -1094,8 +1100,8 @@
     // ----- CommandQueue Callbacks -----
 
     @Override
-    public void setImeWindowStatus(int displayId, int vis, int backDisposition,
-            boolean showImeSwitcher) {
+    public void setImeWindowStatus(int displayId, @ImeWindowVisibility int vis,
+            @BackDispositionMode int backDisposition, boolean showImeSwitcher) {
         if (displayId != mDisplayId) {
             return;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonView.java
index 133d14d..111a2d4 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonView.java
@@ -21,6 +21,7 @@
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK;
 
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.res.Configuration;
@@ -49,6 +50,7 @@
 import android.view.ViewConfiguration;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.inputmethod.Flags;
 import android.widget.ImageView;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -231,15 +233,28 @@
     @Override
     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
         super.onInitializeAccessibilityNodeInfo(info);
-        if (mCode != KEYCODE_UNKNOWN) {
+        if (isClickable()) {
             info.addAction(new AccessibilityNodeInfo.AccessibilityAction(ACTION_CLICK, null));
             if (isLongClickable()) {
                 info.addAction(
-                        new AccessibilityNodeInfo.AccessibilityAction(ACTION_LONG_CLICK, null));
+                        new AccessibilityNodeInfo.AccessibilityAction(ACTION_LONG_CLICK,
+                                getAccessibilityLongClickActionLabel()));
             }
         }
     }
 
+    /**
+     * Gets the accessibility long click action label for the button, or {@code null} for no label.
+     */
+    @Nullable
+    private CharSequence getAccessibilityLongClickActionLabel() {
+        if (Flags.imeSwitcherRevamp() && getId() == R.id.ime_switcher) {
+            return getContext().getText(
+                    com.android.internal.R.string.input_method_ime_switch_long_click_action_desc);
+        }
+        return null;
+    }
+
     @Override
     protected void onWindowVisibilityChanged(int visibility) {
         super.onWindowVisibilityChanged(visibility);
diff --git a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneActionsViewModel.kt
similarity index 68%
rename from packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModel.kt
rename to packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneActionsViewModel.kt
index fa8e13a..9fb09c0 100644
--- a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneActionsViewModel.kt
@@ -20,23 +20,24 @@
 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.dagger.SysUISingleton
 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 javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.flowOf
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
 
-/** Models UI state and handles user input for the Notifications Shade scene. */
-@SysUISingleton
-class NotificationsShadeSceneViewModel
-@Inject
+/**
+ * Models the UI state for the user actions that the user can perform to navigate to other scenes.
+ */
+class NotificationsShadeSceneActionsViewModel
+@AssistedInject
 constructor(
-    shadeInteractor: ShadeInteractor,
-) {
-    val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
-        flowOf(
+    private val shadeInteractor: ShadeInteractor,
+) : SceneActionsViewModel() {
+
+    override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
+        setActions(
             mapOf(
                 if (shadeInteractor.shadeAlignment == ShadeAlignment.Top) {
                     Swipe.Up
@@ -46,4 +47,10 @@
                 Back to SceneFamilies.Home,
             )
         )
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(): NotificationsShadeSceneActionsViewModel
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index db0676e..1511f31 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -18,8 +18,6 @@
 
 import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
 
-import static com.android.systemui.Flags.centralizedStatusBarHeightFix;
-
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Path;
@@ -194,12 +192,7 @@
             QuickStatusBarHeaderController quickStatusBarHeaderController) {
         int topPadding = QSUtils.getQsHeaderSystemIconsAreaHeight(mContext);
         if (!LargeScreenUtils.shouldUseLargeScreenShadeHeader(mContext.getResources())) {
-            topPadding =
-                    centralizedStatusBarHeightFix()
-                            ? LargeScreenHeaderHelper.getLargeScreenHeaderHeight(mContext)
-                            : mContext.getResources()
-                                    .getDimensionPixelSize(
-                                            R.dimen.large_screen_shade_header_height);
+            topPadding = LargeScreenHeaderHelper.getLargeScreenHeaderHeight(mContext);
         }
         if (mQSPanelContainer != null) {
             mQSPanelContainer.setPaddingRelative(
@@ -266,6 +259,13 @@
     }
 
     /**
+     * @return height with the squishiness fraction applied.
+     */
+    int getSquishedQqsHeight() {
+        return mHeader.getSquishedHeight();
+    }
+
+    /**
      * Returns the size of QS (or the QSCustomizer), regardless of the measured size of this view
      * @return size in pixels of QS (or QSCustomizer)
      */
@@ -274,6 +274,13 @@
                 : mQSPanel.getMeasuredHeight();
     }
 
+    /**
+     * @return height with the squishiness fraction applied.
+     */
+    int getSquishedQsHeight() {
+        return mQSPanel.getSquishedHeight();
+    }
+
     public void setExpansion(float expansion) {
         mQsExpansion = expansion;
         if (mQSPanelContainer != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
index a6fd35a..0b37b5b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
@@ -992,11 +992,25 @@
         return mContainer.getQqsHeight();
     }
 
+    /**
+     * @return height with the squishiness fraction applied.
+     */
+    public int getSquishedQqsHeight() {
+        return mContainer.getSquishedQqsHeight();
+    }
+
     public int getQSHeight() {
         return mContainer.getQsHeight();
     }
 
     /**
+     * @return height with the squishiness fraction applied.
+     */
+    public int getSquishedQsHeight() {
+        return mContainer.getSquishedQsHeight();
+    }
+
+    /**
      * Pass the size of the navbar when it's at the bottom of the device so it can be used as
      * padding
      * @param padding size of the bottom nav bar in px
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 032891f..d3bed27 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -733,6 +733,30 @@
         mCanCollapse = canCollapse;
     }
 
+    /**
+     * @return height with the {@link QSPanel#setSquishinessFraction(float)} applied.
+     */
+    public int getSquishedHeight() {
+        if (mFooter != null) {
+            final ViewGroup.LayoutParams footerLayoutParams = mFooter.getLayoutParams();
+            final int footerBottomMargin;
+            if (footerLayoutParams instanceof MarginLayoutParams) {
+                footerBottomMargin = ((MarginLayoutParams) footerLayoutParams).bottomMargin;
+            } else {
+                footerBottomMargin = 0;
+            }
+            // This is the distance between the top of the QSPanel and the last view in the
+            // layout (which is the effective the bottom)
+            return mFooter.getBottom() + footerBottomMargin - getTop();
+        }
+        if (mTileLayout != null) {
+            // Footer absence means that the panel is in the QQS. In this case it's just height
+            // of the tiles + paddings.
+            return mTileLayout.getTilesHeight() + getPaddingBottom() + getPaddingTop();
+        }
+        return getHeight();
+    }
+
     @Nullable
     @VisibleForTesting
     View getMediaPlaceholder() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 9c88eb9..8fde52c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -17,8 +17,6 @@
 import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
-import static com.android.systemui.Flags.centralizedStatusBarHeightFix;
-
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -100,10 +98,7 @@
             qqsLP.topMargin = mContext.getResources()
                     .getDimensionPixelSize(R.dimen.qqs_layout_margin_top);
         } else {
-            qqsLP.topMargin = centralizedStatusBarHeightFix()
-                    ? LargeScreenHeaderHelper.getLargeScreenHeaderHeight(mContext)
-                    : mContext.getResources()
-                    .getDimensionPixelSize(R.dimen.large_screen_shade_header_min_height);
+            qqsLP.topMargin = LargeScreenHeaderHelper.getLargeScreenHeaderHeight(mContext);
         }
         mHeaderQsPanel.setLayoutParams(qqsLP);
     }
@@ -128,4 +123,11 @@
         lp.setMarginEnd(marginEnd);
         view.setLayoutParams(lp);
     }
+
+    /**
+     * @return height with the squishiness fraction applied.
+     */
+    public int getSquishedHeight() {
+        return mHeaderQsPanel.getSquishedHeight();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/UserSettingObserver.java b/packages/SystemUI/src/com/android/systemui/qs/UserSettingObserver.java
index 1b34c33..89be17b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/UserSettingObserver.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/UserSettingObserver.java
@@ -19,6 +19,7 @@
 import android.database.ContentObserver;
 import android.os.Handler;
 
+import com.android.systemui.Flags;
 import com.android.systemui.statusbar.policy.Listenable;
 import com.android.systemui.util.settings.SecureSettings;
 import com.android.systemui.util.settings.SystemSettings;
@@ -76,10 +77,20 @@
         mListening = listening;
         if (listening) {
             mObservedValue = getValueFromProvider();
-            mSettingsProxy.registerContentObserverForUserSync(
-                    mSettingsProxy.getUriFor(mSettingName), false, this, mUserId);
+            if (Flags.qsRegisterSettingObserverOnBgThread()) {
+                mSettingsProxy.registerContentObserverForUserAsync(
+                        mSettingsProxy.getUriFor(mSettingName), this, mUserId, () ->
+                                mObservedValue = getValueFromProvider());
+            } else {
+                mSettingsProxy.registerContentObserverForUserSync(
+                        mSettingsProxy.getUriFor(mSettingName), false, this, mUserId);
+            }
         } else {
-            mSettingsProxy.unregisterContentObserverSync(this);
+            if (Flags.qsRegisterSettingObserverOnBgThread()) {
+                mSettingsProxy.unregisterContentObserverAsync(this);
+            } else {
+                mSettingsProxy.unregisterContentObserverSync(this);
+            }
             mObservedValue = mDefaultValue;
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/flags/QSComposeFragment.kt b/packages/SystemUI/src/com/android/systemui/qs/flags/QSComposeFragment.kt
index 664d496..8772c51 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/flags/QSComposeFragment.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/flags/QSComposeFragment.kt
@@ -33,7 +33,7 @@
     /** Is the refactor enabled */
     @JvmStatic
     inline val isEnabled
-        get() = Flags.qsUiRefactorComposeFragment() && NewQsUI.isEnabled
+        get() = Flags.qsUiRefactorComposeFragment()
 
     /**
      * 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/qs/panels/domain/interactor/IconTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
index b18358c..6dcdea9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
@@ -40,15 +40,15 @@
     @Application private val applicationScope: CoroutineScope
 ) {
 
-    private val largeTilesSpecs =
+    val largeTilesSpecs =
         preferencesInteractor.largeTilesSpecs
             .onEach { logChange(it) }
             .stateIn(applicationScope, SharingStarted.Eagerly, repo.defaultLargeTiles)
 
     fun isIconTile(spec: TileSpec): Boolean = !largeTilesSpecs.value.contains(spec)
 
-    fun resize(spec: TileSpec, toIcon: Boolean) {
-        if (toIcon) {
+    fun resize(spec: TileSpec) {
+        if (largeTilesSpecs.value.contains(spec)) {
             preferencesInteractor.setLargeTilesSpecs(largeTilesSpecs.value - spec)
         } else {
             preferencesInteractor.setLargeTilesSpecs(largeTilesSpecs.value + spec)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt
index 0fe79af..874b3b0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt
@@ -19,6 +19,7 @@
 import android.util.Log
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.qs.panels.shared.model.SizedTile
+import com.android.systemui.qs.panels.shared.model.SizedTileImpl
 import com.android.systemui.qs.panels.shared.model.TileRow
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import javax.inject.Inject
@@ -38,17 +39,12 @@
     override fun reconcileTiles(tiles: List<TileSpec>): List<TileSpec> {
         val newTiles: MutableList<TileSpec> = mutableListOf()
         val row = TileRow<TileSpec>(columns = gridSizeInteractor.columns.value)
-        val tilesQueue =
+        val tilesQueue: ArrayDeque<SizedTile<TileSpec>> =
             ArrayDeque(
                 tiles.map {
-                    SizedTile(
+                    SizedTileImpl(
                         it,
-                        width =
-                            if (iconTilesInteractor.isIconTile(it)) {
-                                1
-                            } else {
-                                2
-                            }
+                        if (iconTilesInteractor.isIconTile(it)) 1 else 2,
                     )
                 }
             )
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/TileRow.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/TileRow.kt
index 7e4381b..17b73a2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/TileRow.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/TileRow.kt
@@ -17,7 +17,17 @@
 package com.android.systemui.qs.panels.shared.model
 
 /** Represents a tile of type [T] associated with a width */
-data class SizedTile<T>(val tile: T, val width: Int)
+interface SizedTile<T> {
+    val tile: T
+    val width: Int
+    val isIcon: Boolean
+        get() = width == 1
+}
+
+data class SizedTileImpl<T>(
+    override val tile: T,
+    override val width: Int,
+) : SizedTile<T>
 
 /** Represents a row of [SizedTile] with a maximum width of [columns] */
 class TileRow<T>(private val columns: Int) {
@@ -51,3 +61,26 @@
 
     fun isFull(): Boolean = availableColumns == 0
 }
+
+/**
+ * Converts a list of [SizedTile] to a sequence of rows based on the number of columns of the grid
+ */
+fun <T> splitInRowsSequence(
+    tiles: List<SizedTile<T>>,
+    columns: Int,
+): Sequence<List<SizedTile<T>>> = sequence {
+    val row = TileRow<T>(columns)
+    for (tile in tiles) {
+        check(tile.width <= columns)
+        if (!row.maybeAddTile(tile)) {
+            // Couldn't add tile to previous row, create a row with the current tiles
+            // and start a new one
+            yield(row.tiles)
+            row.clear()
+            row.maybeAddTile(tile)
+        }
+    }
+    if (row.tiles.isNotEmpty()) {
+        yield(row.tiles)
+    }
+}
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 71deeb6..2c57813 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
@@ -29,13 +29,14 @@
 import androidx.compose.ui.draganddrop.DragAndDropTarget
 import androidx.compose.ui.draganddrop.DragAndDropTransferData
 import androidx.compose.ui.draganddrop.mimeTypes
+import com.android.systemui.qs.panels.shared.model.SizedTile
 import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
 import com.android.systemui.qs.pipeline.shared.TileSpec
 
 @Composable
 fun rememberDragAndDropState(listState: EditTileListState): DragAndDropState {
-    val sourceSpec: MutableState<EditTileViewModel?> = remember { mutableStateOf(null) }
-    return remember(listState) { DragAndDropState(sourceSpec, listState) }
+    val draggedCell: MutableState<SizedTile<EditTileViewModel>?> = remember { mutableStateOf(null) }
+    return remember(listState) { DragAndDropState(draggedCell, listState) }
 }
 
 /**
@@ -43,37 +44,37 @@
  * drop events.
  */
 class DragAndDropState(
-    val sourceSpec: MutableState<EditTileViewModel?>,
-    private val listState: EditTileListState
+    val draggedCell: MutableState<SizedTile<EditTileViewModel>?>,
+    private val listState: EditTileListState,
 ) {
     val dragInProgress: Boolean
-        get() = sourceSpec.value != null
+        get() = draggedCell.value != null
 
     /** Returns index of the dragged tile if it's present in the list. Returns -1 if not. */
     fun currentPosition(): Int {
-        return sourceSpec.value?.let { listState.indexOf(it.tileSpec) } ?: -1
+        return draggedCell.value?.let { listState.indexOf(it.tile.tileSpec) } ?: -1
     }
 
     fun isMoving(tileSpec: TileSpec): Boolean {
-        return sourceSpec.value?.let { it.tileSpec == tileSpec } ?: false
+        return draggedCell.value?.let { it.tile.tileSpec == tileSpec } ?: false
     }
 
-    fun onStarted(tile: EditTileViewModel) {
-        sourceSpec.value = tile
+    fun onStarted(cell: SizedTile<EditTileViewModel>) {
+        draggedCell.value = cell
     }
 
     fun onMoved(targetSpec: TileSpec) {
-        sourceSpec.value?.let { listState.move(it, targetSpec) }
+        draggedCell.value?.let { listState.move(it, targetSpec) }
     }
 
     fun movedOutOfBounds() {
         // Removing the tiles from the current tile grid if it moves out of bounds. This clears
         // the spacer and makes it apparent that dropping the tile at that point would remove it.
-        sourceSpec.value?.let { listState.remove(it.tileSpec) }
+        draggedCell.value?.let { listState.remove(it.tile.tileSpec) }
     }
 
     fun onDrop() {
-        sourceSpec.value = null
+        draggedCell.value = null
     }
 }
 
@@ -97,8 +98,8 @@
         remember(dragAndDropState) {
             object : DragAndDropTarget {
                 override fun onDrop(event: DragAndDropEvent): Boolean {
-                    return dragAndDropState.sourceSpec.value?.let {
-                        onDrop(it.tileSpec, dragAndDropState.currentPosition())
+                    return dragAndDropState.draggedCell.value?.let {
+                        onDrop(it.tile.tileSpec, dragAndDropState.currentPosition())
                         dragAndDropState.onDrop()
                         true
                     } ?: false
@@ -112,7 +113,7 @@
     return dragAndDropTarget(
         shouldStartDragAndDrop = { event ->
             event.mimeTypes().contains(QsDragAndDrop.TILESPEC_MIME_TYPE) &&
-                dragAndDropState.sourceSpec.value?.let { acceptDrops(it.tileSpec) } ?: false
+                dragAndDropState.draggedCell.value?.let { acceptDrops(it.tile.tileSpec) } ?: false
         },
         target = target,
     )
@@ -134,8 +135,8 @@
         remember(dragAndDropState) {
             object : DragAndDropTarget {
                 override fun onDrop(event: DragAndDropEvent): Boolean {
-                    return dragAndDropState.sourceSpec.value?.let {
-                        onDrop(it.tileSpec)
+                    return dragAndDropState.draggedCell.value?.let {
+                        onDrop(it.tile.tileSpec)
                         dragAndDropState.onDrop()
                         true
                     } ?: false
@@ -176,8 +177,8 @@
                 }
 
                 override fun onDrop(event: DragAndDropEvent): Boolean {
-                    return dragAndDropState.sourceSpec.value?.let {
-                        onDrop(it.tileSpec, dragAndDropState.currentPosition())
+                    return dragAndDropState.draggedCell.value?.let {
+                        onDrop(it.tile.tileSpec, dragAndDropState.currentPosition())
                         dragAndDropState.onDrop()
                         true
                     } ?: false
@@ -188,23 +189,23 @@
         target = target,
         shouldStartDragAndDrop = { event ->
             event.mimeTypes().contains(QsDragAndDrop.TILESPEC_MIME_TYPE) &&
-                dragAndDropState.sourceSpec.value?.let { acceptDrops(it.tileSpec) } ?: false
+                dragAndDropState.draggedCell.value?.let { acceptDrops(it.tile.tileSpec) } ?: false
         },
     )
 }
 
 fun Modifier.dragAndDropTileSource(
-    tile: EditTileViewModel,
+    sizedTile: SizedTile<EditTileViewModel>,
     onTap: (TileSpec) -> Unit,
     onDoubleTap: (TileSpec) -> Unit,
     dragAndDropState: DragAndDropState
 ): Modifier {
     return dragAndDropSource {
         detectTapGestures(
-            onTap = { onTap(tile.tileSpec) },
-            onDoubleTap = { onDoubleTap(tile.tileSpec) },
+            onTap = { onTap(sizedTile.tile.tileSpec) },
+            onDoubleTap = { onDoubleTap(sizedTile.tile.tileSpec) },
             onLongPress = {
-                dragAndDropState.onStarted(tile)
+                dragAndDropState.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
@@ -214,7 +215,7 @@
                         ClipData(
                             QsDragAndDrop.CLIPDATA_LABEL,
                             arrayOf(QsDragAndDrop.TILESPEC_MIME_TYPE),
-                            ClipData.Item(tile.tileSpec.spec)
+                            ClipData.Item(sizedTile.tile.tileSpec.spec)
                         )
                     )
                 )
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
index e0fed28..fa3008e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
@@ -20,22 +20,23 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.snapshots.SnapshotStateList
 import androidx.compose.runtime.toMutableStateList
+import com.android.systemui.qs.panels.shared.model.SizedTile
 import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
 import com.android.systemui.qs.pipeline.shared.TileSpec
 
 @Composable
 fun rememberEditListState(
-    tiles: List<EditTileViewModel>,
+    tiles: List<SizedTile<EditTileViewModel>>,
 ): EditTileListState {
     return remember(tiles) { EditTileListState(tiles) }
 }
 
 /** Holds the temporary state of the tile list during a drag movement where we move tiles around. */
-class EditTileListState(tiles: List<EditTileViewModel>) {
-    val tiles: SnapshotStateList<EditTileViewModel> = tiles.toMutableStateList()
+class EditTileListState(tiles: List<SizedTile<EditTileViewModel>>) {
+    val tiles: SnapshotStateList<SizedTile<EditTileViewModel>> = tiles.toMutableStateList()
 
-    fun move(tile: EditTileViewModel, target: TileSpec) {
-        val fromIndex = indexOf(tile.tileSpec)
+    fun move(sizedTile: SizedTile<EditTileViewModel>, target: TileSpec) {
+        val fromIndex = indexOf(sizedTile.tile.tileSpec)
         val toIndex = indexOf(target)
 
         if (toIndex == -1 || fromIndex == toIndex) {
@@ -44,7 +45,7 @@
 
         if (fromIndex == -1) {
             // If tile isn't in the list, simply insert it
-            tiles.add(toIndex, tile)
+            tiles.add(toIndex, sizedTile)
         } else {
             // If tile is present in the list, move it
             tiles.apply { add(toIndex, removeAt(fromIndex)) }
@@ -52,10 +53,10 @@
     }
 
     fun remove(tileSpec: TileSpec) {
-        tiles.removeIf { it.tileSpec == tileSpec }
+        tiles.removeIf { it.tile.tileSpec == tileSpec }
     }
 
     fun indexOf(tileSpec: TileSpec): Int {
-        return tiles.indexOfFirst { it.tileSpec == tileSpec }
+        return tiles.indexOfFirst { it.tile.tileSpec == tileSpec }
     }
 }
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 add830e..bd925fe 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
@@ -22,12 +22,12 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.getValue
-import androidx.compose.runtime.rememberUpdatedState
+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.SizedTile
+import com.android.systemui.qs.panels.shared.model.SizedTileImpl
 import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.FixedColumnsSizeViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModel
@@ -56,13 +56,14 @@
             onDispose { tiles.forEach { it.stopListening(token) } }
         }
         val columns by gridSizeViewModel.columns.collectAsStateWithLifecycle()
+        val sizedTiles = tiles.map { SizedTileImpl(it, it.spec.width()) }
 
         TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) {
-            items(tiles.size, span = { index -> GridItemSpan(tiles[index].spec.width()) }) { index
-                ->
+            items(sizedTiles.size, span = { index -> GridItemSpan(sizedTiles[index].width) }) {
+                index ->
                 Tile(
-                    tile = tiles[index],
-                    iconOnly = iconTilesViewModel.isIconTile(tiles[index].spec),
+                    tile = sizedTiles[index].tile,
+                    iconOnly = iconTilesViewModel.isIconTile(sizedTiles[index].tile.spec),
                     modifier = Modifier.height(dimensionResource(id = R.dimen.qs_tile_height))
                 )
             }
@@ -77,13 +78,21 @@
         onRemoveTile: (TileSpec) -> Unit,
     ) {
         val columns by gridSizeViewModel.columns.collectAsStateWithLifecycle()
-        val isIcon: (TileSpec) -> Boolean by rememberUpdatedState { tileSpec ->
-            iconTilesViewModel.isIconTile(tileSpec)
-        }
+        val largeTiles by iconTilesViewModel.largeTiles.collectAsStateWithLifecycle()
+
+        // Non-current tiles should always be displayed as icon tiles.
+        val sizedTiles =
+            remember(tiles, largeTiles) {
+                tiles.map {
+                    SizedTileImpl(
+                        it,
+                        if (!it.isCurrent || !largeTiles.contains(it.tileSpec)) 1 else 2,
+                    )
+                }
+            }
 
         DefaultEditTileGrid(
-            tiles = tiles,
-            isIconOnly = isIcon,
+            sizedTiles = sizedTiles,
             columns = columns,
             modifier = modifier,
             onAddTile = onAddTile,
@@ -99,7 +108,7 @@
     ): List<List<TileViewModel>> {
 
         return PaginatableGridLayout.splitInRows(
-                tiles.map { SizedTile(it, it.spec.width()) },
+                tiles.map { SizedTileImpl(it, it.spec.width()) },
                 columns,
             )
             .chunked(rows)
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 9b4d10f..af3803b 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
@@ -52,7 +52,7 @@
         ) { index ->
             Tile(
                 tile = tiles[index],
-                iconOnly = sizedTiles[index].width == 1,
+                iconOnly = sizedTiles[index].isIcon,
                 modifier = Modifier.height(dimensionResource(id = R.dimen.qs_tile_height))
             )
         }
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 cb9d0f6..7e6ccd6 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
@@ -53,7 +53,6 @@
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.wrapContentSize
 import androidx.compose.foundation.lazy.grid.GridCells
-import androidx.compose.foundation.lazy.grid.GridItemSpan
 import androidx.compose.foundation.lazy.grid.LazyGridScope
 import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
 import androidx.compose.foundation.rememberScrollState
@@ -98,7 +97,8 @@
 import com.android.systemui.common.ui.compose.load
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.qs.panels.shared.model.SizedTile
-import com.android.systemui.qs.panels.shared.model.TileRow
+import com.android.systemui.qs.panels.ui.model.TileGridCell
+import com.android.systemui.qs.panels.ui.model.toTileGridCells
 import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.toUiState
@@ -107,12 +107,10 @@
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.res.R
 import java.util.function.Supplier
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.delay
 
 object TileType
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @Composable
 fun Tile(
     tile: TileViewModel,
@@ -286,15 +284,14 @@
 
 @Composable
 fun DefaultEditTileGrid(
-    tiles: List<EditTileViewModel>,
-    isIconOnly: (TileSpec) -> Boolean,
+    sizedTiles: List<SizedTile<EditTileViewModel>>,
     columns: Int,
     modifier: Modifier,
     onAddTile: (TileSpec, Int) -> Unit,
     onRemoveTile: (TileSpec) -> Unit,
-    onResize: (TileSpec, Boolean) -> Unit,
+    onResize: (TileSpec) -> Unit,
 ) {
-    val (currentTiles, otherTiles) = tiles.partition { it.isCurrent }
+    val (currentTiles, otherTiles) = sizedTiles.partition { it.tile.isCurrent }
     val currentListState = rememberEditListState(currentTiles)
     val dragAndDropState = rememberDragAndDropState(currentListState)
 
@@ -304,9 +301,6 @@
     val onDropAdd: (TileSpec, Int) -> Unit by rememberUpdatedState { tileSpec, position ->
         onAddTile(tileSpec, position)
     }
-    val onDoubleTap: (TileSpec) -> Unit by rememberUpdatedState { tileSpec ->
-        onResize(tileSpec, !isIconOnly(tileSpec))
-    }
     val tilePadding = dimensionResource(R.dimen.qs_tile_margin_vertical)
 
     CompositionLocalProvider(LocalOverscrollConfiguration provides null) {
@@ -332,9 +326,8 @@
                 currentListState.tiles,
                 columns,
                 tilePadding,
-                isIconOnly,
                 onRemoveTile,
-                onDoubleTap,
+                onResize,
                 dragAndDropState,
                 onDropAdd,
             )
@@ -422,48 +415,32 @@
 
 @Composable
 private fun CurrentTilesGrid(
-    tiles: List<EditTileViewModel>,
+    tiles: List<SizedTile<EditTileViewModel>>,
     columns: Int,
     tilePadding: Dp,
-    isIconOnly: (TileSpec) -> Boolean,
     onClick: (TileSpec) -> Unit,
-    onDoubleTap: (TileSpec) -> Unit,
+    onResize: (TileSpec) -> Unit,
     dragAndDropState: DragAndDropState,
     onDrop: (TileSpec, Int) -> Unit
 ) {
-    val tileHeight = tileHeight()
-    val currentRows =
-        remember(tiles) {
-            calculateRows(
-                tiles.map {
-                    SizedTile(
-                        it,
-                        if (isIconOnly(it.tileSpec)) {
-                            1
-                        } else {
-                            2
-                        }
-                    )
-                },
-                columns
-            )
-        }
-    val currentGridHeight = gridHeight(currentRows, tileHeight, tilePadding)
     // Current tiles
     CurrentTilesContainer {
+        val cells = tiles.toTileGridCells(columns)
+        val tileHeight = tileHeight()
+        val totalRows = cells.lastOrNull()?.row ?: 0
+        val totalHeight = gridHeight(totalRows + 1, tileHeight, tilePadding)
         TileLazyGrid(
             modifier =
-                Modifier.height(currentGridHeight)
+                Modifier.height(totalHeight)
                     .dragAndDropTileList(dragAndDropState, { true }, onDrop),
             columns = GridCells.Fixed(columns)
         ) {
             editTiles(
-                tiles,
+                cells,
                 ClickAction.REMOVE,
                 onClick,
-                isIconOnly,
                 dragAndDropState,
-                onDoubleTap = onDoubleTap,
+                onResize = onResize,
                 indicatePosition = true,
                 acceptDrops = { true },
                 onDrop = onDrop,
@@ -474,13 +451,15 @@
 
 @Composable
 private fun AvailableTileGrid(
-    tiles: List<EditTileViewModel>,
+    tiles: List<SizedTile<EditTileViewModel>>,
     columns: Int,
     tilePadding: Dp,
     onClick: (TileSpec) -> Unit,
     dragAndDropState: DragAndDropState,
 ) {
-    val (otherTilesStock, otherTilesCustom) = tiles.partition { it.appName == null }
+    // Available tiles aren't visible during drag and drop, so the row isn't needed
+    val (otherTilesStock, otherTilesCustom) =
+        tiles.map { TileGridCell(it, 0) }.partition { it.tile.appName == null }
     val availableTileHeight = tileHeight(true)
     val availableGridHeight = gridHeight(tiles.size, availableTileHeight, columns, tilePadding)
 
@@ -493,7 +472,6 @@
             otherTilesStock,
             ClickAction.ADD,
             onClick,
-            isIconOnly = { true },
             dragAndDropState = dragAndDropState,
             acceptDrops = { false },
             showLabels = true,
@@ -502,7 +480,6 @@
             otherTilesCustom,
             ClickAction.ADD,
             onClick,
-            isIconOnly = { true },
             dragAndDropState = dragAndDropState,
             acceptDrops = { false },
             showLabels = true,
@@ -519,52 +496,27 @@
     return ((tileHeight + padding) * rows) - padding
 }
 
-private fun calculateRows(tiles: List<SizedTile<EditTileViewModel>>, columns: Int): Int {
-    val row = TileRow<EditTileViewModel>(columns)
-    var count = 0
-
-    for (tile in tiles) {
-        if (row.maybeAddTile(tile)) {
-            if (row.isFull()) {
-                // Row is full, no need to stretch tiles
-                count += 1
-                row.clear()
-            }
-        } else {
-            count += 1
-            row.clear()
-            row.maybeAddTile(tile)
-        }
-    }
-    if (row.tiles.isNotEmpty()) {
-        count += 1
-    }
-    return count
-}
-
 fun LazyGridScope.editTiles(
-    tiles: List<EditTileViewModel>,
+    cells: List<TileGridCell>,
     clickAction: ClickAction,
     onClick: (TileSpec) -> Unit,
-    isIconOnly: (TileSpec) -> Boolean,
     dragAndDropState: DragAndDropState,
     acceptDrops: (TileSpec) -> Boolean,
-    onDoubleTap: (TileSpec) -> Unit = {},
+    onResize: (TileSpec) -> Unit = {},
     onDrop: (TileSpec, Int) -> Unit = { _, _ -> },
     showLabels: Boolean = false,
     indicatePosition: Boolean = false,
 ) {
     items(
-        count = tiles.size,
-        key = { tiles[it].tileSpec.spec },
-        span = { GridItemSpan(if (isIconOnly(tiles[it].tileSpec)) 1 else 2) },
+        count = cells.size,
+        key = { cells[it].key },
+        span = { cells[it].span },
         contentType = { TileType }
     ) { index ->
-        val viewModel = tiles[index]
-        val iconOnly = isIconOnly(viewModel.tileSpec)
-        val tileHeight = tileHeight(iconOnly && showLabels)
+        val cell = cells[index]
+        val tileHeight = tileHeight(cell.isIcon && showLabels)
 
-        if (!dragAndDropState.isMoving(viewModel.tileSpec)) {
+        if (!dragAndDropState.isMoving(cell.tile.tileSpec)) {
             val onClickActionName =
                 when (clickAction) {
                     ClickAction.ADD ->
@@ -579,8 +531,8 @@
                     ""
                 }
             EditTile(
-                tileViewModel = viewModel,
-                iconOnly = iconOnly,
+                tileViewModel = cell.tile,
+                iconOnly = cell.isIcon,
                 showLabels = showLabels,
                 modifier =
                     Modifier.height(tileHeight)
@@ -589,11 +541,11 @@
                             onClick(onClickActionName) { false }
                             this.stateDescription = stateDescription
                         }
-                        .dragAndDropTile(dragAndDropState, viewModel.tileSpec, acceptDrops, onDrop)
+                        .dragAndDropTile(dragAndDropState, cell.tile.tileSpec, acceptDrops, onDrop)
                         .dragAndDropTileSource(
-                            viewModel,
+                            cell,
                             onClick,
-                            onDoubleTap,
+                            onResize,
                             dragAndDropState,
                         )
             )
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt
new file mode 100644
index 0000000..c241fd8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.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.qs.panels.ui.model
+
+import androidx.compose.foundation.lazy.grid.GridItemSpan
+import androidx.compose.runtime.Immutable
+import com.android.systemui.qs.panels.shared.model.SizedTile
+import com.android.systemui.qs.panels.shared.model.splitInRowsSequence
+import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
+
+/**
+ * Represents a [EditTileViewModel] from a grid associated with a tile format and the row it's
+ * positioned at
+ */
+@Immutable
+data class TileGridCell(
+    override val tile: EditTileViewModel,
+    val row: Int,
+    val key: String = "${tile.tileSpec.spec}-$row",
+    override val width: Int,
+) : SizedTile<EditTileViewModel> {
+    constructor(
+        sizedTile: SizedTile<EditTileViewModel>,
+        row: Int
+    ) : this(
+        tile = sizedTile.tile,
+        row = row,
+        width = sizedTile.width,
+    )
+
+    val span = GridItemSpan(width)
+}
+
+fun List<SizedTile<EditTileViewModel>>.toTileGridCells(columns: Int): List<TileGridCell> {
+    return splitInRowsSequence(this, columns)
+        .flatMapIndexed { index, sizedTiles -> sizedTiles.map { TileGridCell(it, index) } }
+        .toList()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt
index 8d2d74a..b604e18 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt
@@ -20,17 +20,22 @@
 import com.android.systemui.qs.panels.domain.interactor.IconTilesInteractor
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
 
 interface IconTilesViewModel {
+    val largeTiles: StateFlow<Set<TileSpec>>
+
     fun isIconTile(spec: TileSpec): Boolean
 
-    fun resize(spec: TileSpec, toIcon: Boolean)
+    fun resize(spec: TileSpec)
 }
 
 @SysUISingleton
 class IconTilesViewModelImpl @Inject constructor(private val interactor: IconTilesInteractor) :
     IconTilesViewModel {
+    override val largeTiles = interactor.largeTilesSpecs
+
     override fun isIconTile(spec: TileSpec): Boolean = interactor.isIconTile(spec)
 
-    override fun resize(spec: TileSpec, toIcon: Boolean) = interactor.resize(spec, toIcon)
+    override fun resize(spec: TileSpec) = interactor.resize(spec)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
index bb00494..eee905f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
@@ -20,7 +20,8 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.qs.panels.domain.interactor.QuickQuickSettingsRowInteractor
 import com.android.systemui.qs.panels.shared.model.SizedTile
-import com.android.systemui.qs.panels.shared.model.TileRow
+import com.android.systemui.qs.panels.shared.model.SizedTileImpl
+import com.android.systemui.qs.panels.shared.model.splitInRowsSequence
 import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import javax.inject.Inject
@@ -59,7 +60,12 @@
             .flatMapLatest { columns ->
                 tilesInteractor.currentTiles.combine(rows, ::Pair).mapLatest { (tiles, rows) ->
                     tiles
-                        .map { SizedTile(TileViewModel(it.tile, it.spec), it.spec.width) }
+                        .map {
+                            SizedTileImpl(
+                                TileViewModel(it.tile, it.spec),
+                                it.spec.width,
+                            )
+                        }
                         .let { splitInRowsSequence(it, columns).take(rows).toList().flatten() }
                 }
             }
@@ -67,7 +73,12 @@
                 applicationScope,
                 SharingStarted.WhileSubscribed(),
                 tilesInteractor.currentTiles.value
-                    .map { SizedTile(TileViewModel(it.tile, it.spec), it.spec.width) }
+                    .map {
+                        SizedTileImpl(
+                            TileViewModel(it.tile, it.spec),
+                            it.spec.width,
+                        )
+                    }
                     .let {
                         splitInRowsSequence(it, columns.value).take(rows.value).toList().flatten()
                     }
@@ -75,26 +86,4 @@
 
     private val TileSpec.width: Int
         get() = if (iconTilesViewModel.isIconTile(this)) 1 else 2
-
-    companion object {
-        private fun splitInRowsSequence(
-            tiles: List<SizedTile<TileViewModel>>,
-            columns: Int,
-        ): Sequence<List<SizedTile<TileViewModel>>> = sequence {
-            val row = TileRow<TileViewModel>(columns)
-            for (tile in tiles) {
-                check(tile.width <= columns)
-                if (!row.maybeAddTile(tile)) {
-                    // Couldn't add tile to previous row, create a row with the current tiles
-                    // and start a new one
-                    yield(row.tiles)
-                    row.clear()
-                    row.maybeAddTile(tile)
-                }
-            }
-            if (row.tiles.isNotEmpty()) {
-                yield(row.tiles)
-            }
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt
index 9b8dba1..9fb1d46 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt
@@ -37,19 +37,22 @@
 
     override fun map(config: QSTileConfig, data: AirplaneModeTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
-            val icon =
+            iconRes =
+                if (data.isEnabled) {
+                    R.drawable.qs_airplane_icon_on
+                } else {
+                    R.drawable.qs_airplane_icon_off
+                }
+
+            icon = {
                 Icon.Loaded(
                     resources.getDrawable(
-                        if (data.isEnabled) {
-                            R.drawable.qs_airplane_icon_on
-                        } else {
-                            R.drawable.qs_airplane_icon_off
-                        },
+                        iconRes!!,
                         theme,
                     ),
                     contentDescription = null
                 )
-            this.icon = { icon }
+            }
             if (data.isEnabled) {
                 activationState = QSTileState.ActivationState.ACTIVE
                 secondaryLabel = resources.getStringArray(R.array.tile_states_airplane)[2]
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/CustomTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/CustomTileMapper.kt
index 875079c..984228d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/CustomTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/CustomTileMapper.kt
@@ -41,16 +41,25 @@
 ) : QSTileDataToStateMapper<CustomTileDataModel> {
 
     override fun map(config: QSTileConfig, data: CustomTileDataModel): QSTileState {
-        val userContext = context.createContextAsUser(UserHandle(data.user.identifier), 0)
+        val userContext =
+            try {
+                context.createContextAsUser(UserHandle(data.user.identifier), 0)
+            } catch (exception: IllegalStateException) {
+                null
+            }
 
         val iconResult =
-            getIconProvider(
-                userContext = userContext,
-                icon = data.tile.icon,
-                callingAppUid = data.callingAppUid,
-                packageName = data.componentName.packageName,
-                defaultIcon = data.defaultTileIcon,
-            )
+            if (userContext != null) {
+                getIconProvider(
+                    userContext = userContext,
+                    icon = data.tile.icon,
+                    callingAppUid = data.callingAppUid,
+                    packageName = data.componentName.packageName,
+                    defaultIcon = data.defaultTileIcon,
+                )
+            } else {
+                IconResult({ null }, true)
+            }
 
         return QSTileState.build(iconResult.iconProvider, data.tile.label) {
             var tileState: Int = data.tile.state
@@ -61,7 +70,7 @@
             icon = iconResult.iconProvider
             activationState =
                 if (iconResult.failedToLoad) {
-                    QSTileState.ActivationState.INACTIVE
+                    QSTileState.ActivationState.UNAVAILABLE
                 } else {
                     QSTileState.ActivationState.valueOf(tileState)
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt
index eec5d3d..204ead3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt
@@ -79,6 +79,7 @@
                 flowOf(
                     InternetTileModel.Active(
                         secondaryTitle = secondary,
+                        iconId = wifiIcon.icon.res,
                         icon = Icon.Loaded(context.getDrawable(wifiIcon.icon.res)!!, null),
                         stateDescription = wifiIcon.contentDescription,
                         contentDescription = ContentDescription.Loaded("$internetLabel,$secondary"),
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 ae2f32a..dfcf216 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
@@ -34,7 +34,6 @@
 import com.android.systemui.qs.QSContainerImpl
 import com.android.systemui.qs.QSImpl
 import com.android.systemui.qs.dagger.QSSceneComponent
-import com.android.systemui.qs.tiles.viewmodel.StubQSTileViewModel.state
 import com.android.systemui.res.R
 import com.android.systemui.settings.brightness.MirrorController
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -126,12 +125,18 @@
     /** The current height of QQS in the current [qsView], or 0 if there's no view. */
     val qqsHeight: Int
 
+    /** @return height with the squishiness fraction applied. */
+    val squishedQqsHeight: Int
+
     /**
      * The current height of QS in the current [qsView], or 0 if there's no view. If customizing, it
      * will return the height allocated to the customizer.
      */
     val qsHeight: Int
 
+    /** @return height with the squishiness fraction applied. */
+    val squishedQsHeight: Int
+
     /** Compatibility for use by LockscreenShadeTransitionController. Matches default from [QS] */
     val isQsFullyCollapsed: Boolean
         get() = true
@@ -273,9 +278,15 @@
     override val qqsHeight: Int
         get() = qsImpl.value?.qqsHeight ?: 0
 
+    override val squishedQqsHeight: Int
+        get() = qsImpl.value?.squishedQqsHeight ?: 0
+
     override val qsHeight: Int
         get() = qsImpl.value?.qsHeight ?: 0
 
+    override val squishedQsHeight: Int
+        get() = qsImpl.value?.squishedQsHeight ?: 0
+
     // If value is null, there's no QS and therefore it's fully collapsed.
     override val isQsFullyCollapsed: Boolean
         get() = qsImpl.value?.isFullyCollapsed ?: true
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
index b1cc55d..7258882 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
@@ -34,7 +34,6 @@
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel
 import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
-import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
 import java.util.concurrent.atomic.AtomicBoolean
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
@@ -48,10 +47,9 @@
 class QuickSettingsSceneViewModel
 @Inject
 constructor(
-    val brightnessMirrorViewModel: BrightnessMirrorViewModel,
-    val shadeHeaderViewModel: ShadeHeaderViewModel,
+    val brightnessMirrorViewModelFactory: BrightnessMirrorViewModel.Factory,
+    val shadeHeaderViewModelFactory: ShadeHeaderViewModel.Factory,
     val qsSceneAdapter: QSSceneAdapter,
-    val notifications: NotificationsPlaceholderViewModel,
     private val footerActionsViewModelFactory: FooterActionsViewModel.Factory,
     private val footerActionsController: FooterActionsController,
     sceneBackInteractor: SceneBackInteractor,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModel.kt
new file mode 100644
index 0000000..d2967b8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModel.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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+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.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 dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.map
+
+/**
+ * Models the UI state for the user actions that the user can perform to navigate to other scenes.
+ *
+ * Different from the [QuickSettingsShadeSceneContentViewModel] which models the _content_ of the
+ * scene.
+ */
+class QuickSettingsShadeSceneActionsViewModel
+@AssistedInject
+constructor(
+    private val shadeInteractor: ShadeInteractor,
+    val quickSettingsContainerViewModel: QuickSettingsContainerViewModel,
+) : SceneActionsViewModel() {
+
+    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)
+                    )
+                    if (!editing) {
+                        put(Back, UserActionResult(SceneFamilies.Home))
+                    }
+                }
+            }
+            .collectLatest { actions -> setActions(actions) }
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(): QuickSettingsShadeSceneActionsViewModel
+    }
+}
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
new file mode 100644
index 0000000..abfca4b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneContentViewModel.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.
+ */
+
+@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
+ * be performed to navigate to other scenes.
+ */
+class QuickSettingsShadeSceneContentViewModel
+@AssistedInject
+constructor(
+    val overlayShadeViewModelFactory: OverlayShadeViewModel.Factory,
+    val quickSettingsContainerViewModel: QuickSettingsContainerViewModel,
+) : SysUiViewModel() {
+
+    @AssistedFactory
+    interface Factory {
+        fun create(): QuickSettingsShadeSceneContentViewModel
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt
deleted file mode 100644
index e012f2c..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.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.dagger.SysUISingleton
-import com.android.systemui.scene.shared.model.SceneFamilies
-import com.android.systemui.shade.domain.interactor.ShadeInteractor
-import com.android.systemui.shade.shared.model.ShadeAlignment
-import com.android.systemui.shade.ui.viewmodel.OverlayShadeViewModel
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.map
-
-/** Models UI state and handles user input for the Quick Settings Shade scene. */
-@SysUISingleton
-class QuickSettingsShadeSceneViewModel
-@Inject
-constructor(
-    private val shadeInteractor: ShadeInteractor,
-    val overlayShadeViewModel: OverlayShadeViewModel,
-    val quickSettingsContainerViewModel: QuickSettingsContainerViewModel,
-) {
-
-    val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
-        quickSettingsContainerViewModel.editModeViewModel.isEditing.map { editing ->
-            buildMap {
-                put(
-                    if (shadeInteractor.shadeAlignment == ShadeAlignment.Top) {
-                        Swipe.Up
-                    } else {
-                        Swipe.Down
-                    },
-                    UserActionResult(SceneFamilies.Home)
-                )
-                if (!editing) {
-                    put(Back, UserActionResult(SceneFamilies.Home))
-                }
-            }
-        }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
index 8d0a386..432a35a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
@@ -16,7 +16,9 @@
 
 package com.android.systemui.recents;
 
+import static com.android.systemui.Flags.enableViewCaptureTracing;
 import static com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen;
+import static com.android.systemui.util.ConvenienceExtensionsKt.toKotlinLazy;
 import static com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE;
 import static com.android.systemui.util.leak.RotationUtils.ROTATION_NONE;
 import static com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE;
@@ -53,6 +55,8 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.app.viewcapture.ViewCapture;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
@@ -83,6 +87,7 @@
     private final Lazy<NavigationBarController> mNavigationBarControllerLazy;
     private final AccessibilityManager mAccessibilityService;
     private final WindowManager mWindowManager;
+    private final ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final UserTracker mUserTracker;
 
@@ -106,13 +111,16 @@
             NavigationModeController navigationModeController,
             Lazy<NavigationBarController> navigationBarControllerLazy,
             BroadcastDispatcher broadcastDispatcher,
-            UserTracker userTracker) {
+            UserTracker userTracker,
+            Lazy<ViewCapture> daggerLazyViewCapture) {
         mContext = context;
         mNavigationBarControllerLazy = navigationBarControllerLazy;
         mAccessibilityService = (AccessibilityManager)
                 mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
         mWindowManager = (WindowManager)
                 mContext.getSystemService(Context.WINDOW_SERVICE);
+        mViewCaptureAwareWindowManager = new ViewCaptureAwareWindowManager(mWindowManager,
+                toKotlinLazy(daggerLazyViewCapture), enableViewCaptureTracing());
         mNavBarMode = navigationModeController.addListener(this);
         mBroadcastDispatcher = broadcastDispatcher;
         mUserTracker = userTracker;
@@ -123,7 +131,7 @@
 
     public void clearPrompt() {
         if (mRequestWindow != null) {
-            mWindowManager.removeView(mRequestWindow);
+            mViewCaptureAwareWindowManager.removeView(mRequestWindow);
             mRequestWindow = null;
         }
     }
@@ -144,7 +152,7 @@
 
         // show the confirmation
         WindowManager.LayoutParams lp = getWindowLayoutParams();
-        mWindowManager.addView(mRequestWindow, lp);
+        mViewCaptureAwareWindowManager.addView(mRequestWindow, lp);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/CustomTraceSettingsDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/CustomTraceSettingsDialogDelegate.kt
index 56270ce..a42bd0a 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/CustomTraceSettingsDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/CustomTraceSettingsDialogDelegate.kt
@@ -81,9 +81,11 @@
                         }
                 setOnClickListener { showCategorySelector(this) }
             }
+            val attachToBRLabel = context.getString(T.string.attach_to_bug_report)
             requireViewById<Switch>(R.id.attach_to_bugreport_switch).apply {
                 isChecked = builder.attachToBugreport
                 setOnCheckedChangeListener { _, isChecked -> builder.attachToBugreport = isChecked }
+                contentDescription = attachToBRLabel
             }
             requireViewById<TextView>(R.id.cpu_buffer_size).setupSingleChoiceText(
                 T.array.buffer_size_values,
@@ -111,6 +113,7 @@
                 ) {
                     builder.maxLongTraceDurationMinutes = it
                 }
+            val longTracesLabel = context.getString(T.string.long_traces)
             requireViewById<Switch>(R.id.long_traces_switch).apply {
                 isChecked = builder.longTrace
                 val disabledAlpha by lazy { getDisabledAlpha(context) }
@@ -127,23 +130,24 @@
                     longTraceDurationText.alpha = newAlpha
                     longTraceSizeText.alpha = newAlpha
                 }
+                contentDescription = longTracesLabel
             }
+            val winscopeLabel = context.getString(T.string.winscope_tracing)
             requireViewById<Switch>(R.id.winscope_switch).apply {
                 isChecked = builder.winscope
                 setOnCheckedChangeListener { _, isChecked -> builder.winscope = isChecked }
+                contentDescription = winscopeLabel
             }
+            val debuggableAppsLabel = context.getString(T.string.trace_debuggable_applications)
             requireViewById<Switch>(R.id.trace_debuggable_apps_switch).apply {
                 isChecked = builder.apps
                 setOnCheckedChangeListener { _, isChecked -> builder.apps = isChecked }
+                contentDescription = debuggableAppsLabel
             }
-            requireViewById<TextView>(R.id.long_traces_switch_label).text =
-                context.getString(T.string.long_traces)
-            requireViewById<TextView>(R.id.debuggable_apps_switch_label).text =
-                context.getString(T.string.trace_debuggable_applications)
-            requireViewById<TextView>(R.id.winscope_switch_label).text =
-                context.getString(T.string.winscope_tracing)
-            requireViewById<TextView>(R.id.attach_to_bugreport_switch_label).text =
-                context.getString(T.string.attach_to_bug_report)
+            requireViewById<TextView>(R.id.long_traces_switch_label).text = longTracesLabel
+            requireViewById<TextView>(R.id.debuggable_apps_switch_label).text = debuggableAppsLabel
+            requireViewById<TextView>(R.id.winscope_switch_label).text = winscopeLabel
+            requireViewById<TextView>(R.id.attach_to_bugreport_switch_label).text = attachToBRLabel
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
index 98a61df..863a899 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.util.Log
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.dagger.qualifiers.LongRunning
@@ -71,6 +72,7 @@
     override fun provideRecordingServiceStrings(): RecordingServiceStrings = IrsStrings(resources)
 
     override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+        Log.d(getTag(), "handling action: ${intent?.action}")
         when (intent?.action) {
             ACTION_START -> {
                 bgExecutor.execute {
@@ -95,7 +97,7 @@
                 bgExecutor.execute {
                     mNotificationManager.cancelAsUser(
                         null,
-                        mNotificationId,
+                        intent.getIntExtra(EXTRA_NOTIFICATION_ID, mNotificationId),
                         UserHandle(mUserContextTracker.userContext.userId)
                     )
 
diff --git a/packages/SystemUI/src/com/android/systemui/scene/OWNERS b/packages/SystemUI/src/com/android/systemui/scene/OWNERS
new file mode 100644
index 0000000..2ffcad4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/OWNERS
@@ -0,0 +1,13 @@
+set noparent
+
+# Bug component: 1215786
+
+justinweir@google.com
+nijamkin@google.com
+
+# SysUI Dr No's.
+# Don't send reviews here.
+cinek@google.com
+dsandler@android.com
+juliacr@google.com
+pixel@google.com
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 5b50133..e73664d 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
@@ -25,6 +25,7 @@
 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.bouncer.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
 import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor
 import com.android.systemui.bouncer.shared.logging.BouncerUiEvent
@@ -132,6 +133,7 @@
     private val keyguardEnabledInteractor: KeyguardEnabledInteractor,
     private val dismissCallbackRegistry: DismissCallbackRegistry,
     private val statusBarStateController: SysuiStatusBarStateController,
+    private val alternateBouncerInteractor: AlternateBouncerInteractor,
 ) : CoreStartable {
     private val centralSurfaces: CentralSurfaces?
         get() = centralSurfacesOptLazy.get().getOrNull()
@@ -228,13 +230,16 @@
                                 },
                                 headsUpInteractor.isHeadsUpOrAnimatingAway,
                                 occlusionInteractor.invisibleDueToOcclusion,
+                                alternateBouncerInteractor.isVisible,
                             ) {
                                 visibilityForTransitionState,
                                 isHeadsUpOrAnimatingAway,
                                 invisibleDueToOcclusion,
+                                isAlternateBouncerVisible,
                                 ->
                                 when {
                                     isHeadsUpOrAnimatingAway -> true to "showing a HUN"
+                                    isAlternateBouncerVisible -> true to "showing alternate bouncer"
                                     invisibleDueToOcclusion -> false to "invisible due to occlusion"
                                     else -> visibilityForTransitionState
                                 }
@@ -351,9 +356,10 @@
                                 )
                         }
                     val isOnLockscreen = renderedScenes.contains(Scenes.Lockscreen)
-                    val isOnBouncer = renderedScenes.contains(Scenes.Bouncer)
+                    val isAlternateBouncerVisible = alternateBouncerInteractor.isVisibleState()
+                    val isOnPrimaryBouncer = renderedScenes.contains(Scenes.Bouncer)
                     if (!deviceUnlockStatus.isUnlocked) {
-                        return@mapNotNull if (isOnLockscreen || isOnBouncer) {
+                        return@mapNotNull if (isOnLockscreen || isOnPrimaryBouncer) {
                             // Already on lockscreen or bouncer, no need to change scenes.
                             null
                         } else {
@@ -365,26 +371,44 @@
                     }
 
                     if (
-                        isOnBouncer &&
+                        isOnPrimaryBouncer &&
                             deviceUnlockStatus.deviceUnlockSource == DeviceUnlockSource.TrustAgent
                     ) {
                         uiEventLogger.log(BouncerUiEvent.BOUNCER_DISMISS_EXTENDED_ACCESS)
                     }
                     when {
-                        isOnBouncer ->
-                            // When the device becomes unlocked in Bouncer, go to previous scene,
-                            // or Gone.
+                        isAlternateBouncerVisible -> {
+                            // When the device becomes unlocked when the alternate bouncer is
+                            // showing, always hide the alternate bouncer...
+                            alternateBouncerInteractor.hide()
+
+                            // ... and go to Gone or stay on the current scene
+                            if (
+                                isOnLockscreen ||
+                                    !statusBarStateController.leaveOpenOnKeyguardHide()
+                            ) {
+                                Scenes.Gone to
+                                    "device was unlocked with alternate bouncer showing" +
+                                        " and shade didn't need to be left open"
+                            } else {
+                                null
+                            }
+                        }
+                        isOnPrimaryBouncer ->
+                            // When the device becomes unlocked in primary Bouncer,
+                            // go to previous scene or Gone.
                             if (
                                 previousScene.value == Scenes.Lockscreen ||
                                     !statusBarStateController.leaveOpenOnKeyguardHide()
                             ) {
                                 Scenes.Gone to
-                                    "device was unlocked in Bouncer scene and shade" +
+                                    "device was unlocked with bouncer showing and shade" +
                                         " didn't need to be left open"
                             } else {
                                 val prevScene = previousScene.value
                                 (prevScene ?: Scenes.Gone) to
-                                    "device was unlocked in Bouncer scene, from sceneKey=$prevScene"
+                                    "device was unlocked with primary bouncer showing," +
+                                        " from sceneKey=$prevScene"
                             }
                         isOnLockscreen ->
                             // The lockscreen should be dismissed automatically in 2 scenarios:
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 bccbb11..f6924f2 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
@@ -5,15 +5,18 @@
 import android.view.MotionEvent
 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.viewmodel.SceneContainerViewModel
 import com.android.systemui.shade.TouchLogger
 import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 
 /** A root view of the main SysUI window that supports scenes. */
+@ExperimentalCoroutinesApi
 class SceneWindowRootView(
     context: Context,
     attrs: AttributeSet?,
@@ -35,6 +38,7 @@
         scenes: Set<Scene>,
         layoutInsetController: LayoutInsetsController,
         sceneDataSourceDelegator: SceneDataSourceDelegator,
+        alternateBouncerDependencies: AlternateBouncerDependencies,
     ) {
         this.viewModel = viewModel
         setLayoutInsetsController(layoutInsetController)
@@ -49,6 +53,7 @@
                 super.setVisibility(if (isVisible) View.VISIBLE else View.INVISIBLE)
             },
             dataSourceDelegator = sceneDataSourceDelegator,
+            alternateBouncerDependencies = alternateBouncerDependencies,
         )
     }
 
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 d31d6f4..73a8e4c 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
@@ -37,6 +37,8 @@
 import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
 import com.android.systemui.common.ui.compose.windowinsets.DisplayCutout
 import com.android.systemui.common.ui.compose.windowinsets.ScreenDecorProvider
+import com.android.systemui.keyguard.ui.composable.AlternateBouncer
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
@@ -48,12 +50,14 @@
 import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
 import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
 
+@ExperimentalCoroutinesApi
 object SceneWindowRootViewBinder {
 
     /** Binds between the view and view-model pertaining to a specific scene container. */
@@ -66,6 +70,7 @@
         scenes: Set<Scene>,
         onVisibilityChangedInternal: (isVisible: Boolean) -> Unit,
         dataSourceDelegator: SceneDataSourceDelegator,
+        alternateBouncerDependencies: AlternateBouncerDependencies,
     ) {
         val unsortedSceneByKey: Map<SceneKey, Scene> = scenes.associateBy { scene -> scene.key }
         val sortedSceneByKey: Map<SceneKey, Scene> = buildMap {
@@ -120,6 +125,14 @@
                             sharedNotificationContainer
                         )
                         view.addView(sharedNotificationContainer)
+
+                        // TODO (b/358354906): use an overlay for the alternate bouncer
+                        view.addView(
+                            createAlternateBouncerView(
+                                context = view.context,
+                                alternateBouncerDependencies = alternateBouncerDependencies,
+                            )
+                        )
                     }
 
                     launch {
@@ -164,6 +177,19 @@
         }
     }
 
+    private fun createAlternateBouncerView(
+        context: Context,
+        alternateBouncerDependencies: AlternateBouncerDependencies,
+    ): ComposeView {
+        return ComposeView(context).apply {
+            setContent {
+                AlternateBouncer(
+                    alternateBouncerDependencies = alternateBouncerDependencies,
+                )
+            }
+        }
+    }
+
     // TODO(b/298525212): remove once Compose exposes window inset bounds.
     private fun displayCutoutFromWindowInsets(
         scope: CoroutineScope,
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
new file mode 100644
index 0000000..b707a5a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneActionsViewModel.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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/GoneSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt
deleted file mode 100644
index b739ffe..0000000
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt
+++ /dev/null
@@ -1,83 +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.dagger.SysUISingleton
-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 javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.map
-
-@SysUISingleton
-class GoneSceneViewModel
-@Inject
-constructor(
-    private val shadeInteractor: ShadeInteractor,
-) {
-    val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
-        shadeInteractor.shadeMode.map { shadeMode ->
-            buildMap {
-                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 }
-                        )
-                    )
-                }
-            }
-        }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModel.kt
new file mode 100644
index 0000000..c2fd65b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModel.kt
@@ -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.scene.ui.viewmodel
+
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.lifecycle.SysUiViewModel
+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.
+ *
+ * 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() {
+
+    private val _actions = MutableStateFlow<Map<UserAction, UserActionResult>>(emptyMap())
+    /**
+     * [UserActionResult] by [UserAction] to be collected by the scene container to enable the right
+     * user input/gestures.
+     */
+    val actions: StateFlow<Map<UserAction, UserActionResult>> = _actions.asStateFlow()
+
+    final override suspend fun onActivated() {
+        try {
+            hydrateActions { state -> _actions.value = state }
+            awaitCancellation()
+        } finally {
+            _actions.value = emptyMap()
+        }
+    }
+
+    /**
+     * Keeps the user actions up-to-date (AKA "hydrated").
+     *
+     * Subclasses should implement this `suspend fun` by running coroutine work and calling
+     * [setActions] each time the actions should be published/updated. The work can safely suspend
+     * forever; the base class will take care of canceling it as needed. There's no need to handle
+     * cancellation in this method.
+     *
+     * The base class will also take care of resetting the [actions] flow back to the default value
+     * when this happens.
+     */
+    protected abstract suspend fun hydrateActions(
+        setActions: (Map<UserAction, UserActionResult>) -> Unit,
+    )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index cbb61b3..700253ba 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -63,7 +63,9 @@
     protected static final int NOTIF_BASE_ID = 4273;
     private static final String TAG = "RecordingService";
     private static final String CHANNEL_ID = "screen_record";
-    private static final String GROUP_KEY = "screen_record_saved";
+    @VisibleForTesting static final String GROUP_KEY_SAVED = "screen_record_saved";
+    private static final String GROUP_KEY_ERROR_STARTING = "screen_record_error_starting";
+    @VisibleForTesting static final String GROUP_KEY_ERROR_SAVING = "screen_record_error_saving";
     private static final String EXTRA_RESULT_CODE = "extra_resultCode";
     protected static final String EXTRA_PATH = "extra_path";
     private static final String EXTRA_AUDIO_SOURCE = "extra_useAudio";
@@ -78,6 +80,7 @@
             "com.android.systemui.screenrecord.STOP_FROM_NOTIF";
     protected static final String ACTION_SHARE = "com.android.systemui.screenrecord.SHARE";
     private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
+    protected static final String EXTRA_NOTIFICATION_ID = "notification_id";
 
     private final RecordingController mController;
     protected final KeyguardDismissUtil mKeyguardDismissUtil;
@@ -181,7 +184,7 @@
                     mUiEventLogger.log(Events.ScreenRecordEvent.SCREEN_RECORD_START);
                 } else {
                     updateState(false);
-                    createErrorNotification();
+                    createErrorStartingNotification(currentUser);
                     stopForeground(STOP_FOREGROUND_DETACH);
                     stopSelf();
                     return Service.START_NOT_STICKY;
@@ -272,17 +275,35 @@
     }
 
     /**
-     * Simple error notification, needed since startForeground must be called to avoid errors
+     * Simple "error starting" notification, needed since startForeground must be called to avoid
+     * errors.
      */
     @VisibleForTesting
-    protected void createErrorNotification() {
+    protected void createErrorStartingNotification(UserHandle currentUser) {
+        createErrorNotification(currentUser, strings().getStartError(), GROUP_KEY_ERROR_STARTING);
+    }
+
+    /**
+     * Simple "error saving" notification, needed since startForeground must be called to avoid
+     * errors.
+     */
+    @VisibleForTesting
+    protected void createErrorSavingNotification(UserHandle currentUser) {
+        createErrorNotification(currentUser, strings().getSaveError(), GROUP_KEY_ERROR_SAVING);
+    }
+
+    private void createErrorNotification(
+            UserHandle currentUser, String notificationContentTitle, String groupKey) {
+        // Make sure error notifications get their own group.
+        postGroupSummaryNotification(currentUser, notificationContentTitle, groupKey);
+
         Bundle extras = new Bundle();
         extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, strings().getTitle());
-        String notificationTitle = strings().getStartError();
 
         Notification.Builder builder = new Notification.Builder(this, getChannelId())
                 .setSmallIcon(R.drawable.ic_screenrecord)
-                .setContentTitle(notificationTitle)
+                .setContentTitle(notificationContentTitle)
+                .setGroup(groupKey)
                 .addExtras(extras);
         startForeground(mNotificationId, builder.build());
     }
@@ -337,7 +358,7 @@
                 .setContentText(
                         strings().getBackgroundProcessingLabel())
                 .setSmallIcon(R.drawable.ic_screenrecord)
-                .setGroup(GROUP_KEY)
+                .setGroup(GROUP_KEY_SAVED)
                 .addExtras(extras);
         return builder.build();
     }
@@ -374,7 +395,7 @@
                         PendingIntent.FLAG_IMMUTABLE))
                 .addAction(shareAction)
                 .setAutoCancel(true)
-                .setGroup(GROUP_KEY)
+                .setGroup(GROUP_KEY_SAVED)
                 .addExtras(extras);
 
         // Add thumbnail if available
@@ -389,21 +410,28 @@
     }
 
     /**
-     * Adds a group notification so that save notifications from multiple recordings are
-     * grouped together, and the foreground service recording notification is not
+     * Adds a group summary notification for save notifications so that save notifications from
+     * multiple recordings are grouped together, and the foreground service recording notification
+     * is not.
      */
-    private void postGroupNotification(UserHandle currentUser) {
+    private void postGroupSummaryNotificationForSaves(UserHandle currentUser) {
+        postGroupSummaryNotification(currentUser, strings().getSaveTitle(), GROUP_KEY_SAVED);
+    }
+
+    /** Posts a group summary notification for the given group. */
+    private void postGroupSummaryNotification(
+            UserHandle currentUser, String notificationContentTitle, String groupKey) {
         Bundle extras = new Bundle();
         extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
                 strings().getTitle());
         Notification groupNotif = new Notification.Builder(this, getChannelId())
                 .setSmallIcon(R.drawable.ic_screenrecord)
-                .setContentTitle(strings().getSaveTitle())
-                .setGroup(GROUP_KEY)
+                .setContentTitle(notificationContentTitle)
+                .setGroup(groupKey)
                 .setGroupSummary(true)
                 .setExtras(extras)
                 .build();
-        mNotificationManager.notifyAsUser(getTag(), NOTIF_BASE_ID, groupNotif, currentUser);
+        mNotificationManager.notifyAsUser(getTag(), mNotificationId, groupNotif, currentUser);
     }
 
     private void stopService() {
@@ -414,6 +442,7 @@
         if (userId == USER_ID_NOT_SPECIFIED) {
             userId = mUserContextTracker.getUserContext().getUserId();
         }
+        UserHandle currentUser = new UserHandle(userId);
         Log.d(getTag(), "notifying for user " + userId);
         setTapsVisible(mOriginalShowTaps);
         try {
@@ -427,11 +456,11 @@
                 // let's release the recorder and delete all temporary files in this case
                 getRecorder().release();
             }
-            showErrorToast(R.string.screenrecord_start_error);
+            showErrorToast(R.string.screenrecord_save_error);
             Log.e(getTag(), "stopRecording called, but there was an error when ending"
                     + "recording");
             exception.printStackTrace();
-            createErrorNotification();
+            createErrorSavingNotification(currentUser);
         } catch (Throwable throwable) {
             if (getRecorder() != null) {
                 // Something unexpected happen, SystemUI will crash but let's delete
@@ -455,7 +484,7 @@
                 Log.d(getTag(), "saving recording");
                 Notification notification = createSaveNotification(
                         getRecorder() != null ? getRecorder().save() : null);
-                postGroupNotification(currentUser);
+                postGroupSummaryNotificationForSaves(currentUser);
                 mNotificationManager.notifyAsUser(null, mNotificationId,  notification,
                         currentUser);
             } catch (IOException | IllegalStateException e) {
@@ -514,7 +543,8 @@
 
     private Intent getShareIntent(Context context, Uri path) {
         return new Intent(context, this.getClass()).setAction(ACTION_SHARE)
-                .putExtra(EXTRA_PATH, path);
+                .putExtra(EXTRA_PATH, path)
+                .putExtra(EXTRA_NOTIFICATION_ID, mNotificationId);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt
index b54bf6c..f3357ee 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt
@@ -146,6 +146,10 @@
                     hostUserHandle
                 )
                 intent.putExtra(MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_UID, hostUid)
+                intent.putExtra(
+                    MediaProjectionAppSelectorActivity.EXTRA_SCREEN_SHARE_TYPE,
+                    MediaProjectionAppSelectorActivity.ScreenShareType.ScreenRecord.name,
+                )
                 activityStarter.startActivity(intent, /* dismissShade= */ true)
             }
             dialog.dismiss()
@@ -270,15 +274,18 @@
             return listOf(
                 ScreenShareOption(
                     SINGLE_APP,
-                    R.string.screen_share_permission_dialog_option_single_app,
+                    R.string.screenrecord_permission_dialog_option_text_single_app,
                     R.string.screenrecord_permission_dialog_warning_single_app,
-                    startButtonText = R.string.screenrecord_permission_dialog_continue,
+                    startButtonText =
+                        R.string
+                            .media_projection_entry_generic_permission_dialog_continue_single_app,
                 ),
                 ScreenShareOption(
                     ENTIRE_SCREEN,
-                    R.string.screen_share_permission_dialog_option_entire_screen,
+                    R.string.screenrecord_permission_dialog_option_text_entire_screen,
                     R.string.screenrecord_permission_dialog_warning_entire_screen,
-                    startButtonText = R.string.screenrecord_permission_dialog_continue,
+                    startButtonText =
+                        R.string.screenrecord_permission_dialog_continue_entire_screen,
                 )
             )
         }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 540d4c4..7b802a2 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -17,7 +17,6 @@
 package com.android.systemui.screenshot;
 
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
 
 import static com.android.systemui.Flags.screenshotSaveImageExporter;
 import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM;
@@ -31,7 +30,6 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
@@ -52,17 +50,12 @@
 import android.util.Log;
 import android.view.Display;
 import android.view.ScrollCaptureResponse;
-import android.view.View;
-import android.view.ViewGroup;
 import android.view.ViewRootImpl;
-import android.view.ViewTreeObserver;
-import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.widget.Toast;
 import android.window.WindowContext;
 
 import com.android.internal.logging.UiEventLogger;
-import com.android.internal.policy.PhoneWindow;
 import com.android.settingslib.applications.InterestingConfigChanges;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.broadcast.BroadcastSender;
@@ -115,11 +108,9 @@
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final ScreenshotActionsController mActionsController;
 
-    private final WindowManager mWindowManager;
-    private final WindowManager.LayoutParams mWindowLayoutParams;
     @Nullable
     private final ScreenshotSoundController mScreenshotSoundController;
-    private final PhoneWindow mWindow;
+    private final ScreenshotWindow mWindow;
     private final Display mDisplay;
     private final ScrollCaptureExecutor mScrollCaptureExecutor;
     private final ScreenshotNotificationSmartActionsProvider
@@ -135,8 +126,6 @@
     private Bitmap mScreenBitmap;
     private SaveImageInBackgroundTask mSaveInBgTask;
     private boolean mScreenshotTakenInPortrait;
-    private boolean mAttachRequested;
-    private boolean mDetachRequested;
     private Animator mScreenshotAnimation;
     private RequestCallback mCurrentRequestCallback;
     private String mPackageName = "";
@@ -155,7 +144,7 @@
     @AssistedInject
     ScreenshotController(
             Context context,
-            WindowManager windowManager,
+            ScreenshotWindow.Factory screenshotWindowFactory,
             FeatureFlags flags,
             ScreenshotShelfViewProxy.Factory viewProxyFactory,
             ScreenshotSmartActions screenshotSmartActions,
@@ -195,9 +184,8 @@
         mScreenshotHandler.setDefaultTimeoutMillis(SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS);
 
         mDisplay = display;
-        mWindowManager = windowManager;
-        final Context displayContext = context.createDisplayContext(display);
-        mContext = (WindowContext) displayContext.createWindowContext(TYPE_SCREENSHOT, null);
+        mWindow = screenshotWindowFactory.create(mDisplay);
+        mContext = mWindow.getContext();
         mFlags = flags;
         mUserManager = userManager;
         mMessageContainerController = messageContainerController;
@@ -213,17 +201,10 @@
             mViewProxy.requestDismissal(SCREENSHOT_INTERACTION_TIMEOUT);
         });
 
-        // Setup the window that we are going to use
-        mWindowLayoutParams = FloatingWindowUtil.getFloatingWindowParams();
-        mWindowLayoutParams.setTitle("ScreenshotAnimation");
-
-        mWindow = FloatingWindowUtil.getFloatingWindow(mContext);
-        mWindow.setWindowManager(mWindowManager, null, null);
-
         mConfigChanges.applyNewConfig(context.getResources());
         reloadAssets();
 
-        mActionExecutor = actionExecutorFactory.create(mWindow, mViewProxy,
+        mActionExecutor = actionExecutorFactory.create(mWindow.getWindow(), mViewProxy,
                 () -> {
                     finishDismiss();
                     return Unit.INSTANCE;
@@ -318,12 +299,12 @@
         }
 
         // The window is focusable by default
-        setWindowFocusable(true);
+        mWindow.setFocusable(true);
         mViewProxy.requestFocus();
 
         enqueueScrollCaptureRequest(requestId, screenshot.getUserHandle());
 
-        attachWindow();
+        mWindow.attachWindow();
 
         boolean showFlash;
         if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE) {
@@ -347,13 +328,10 @@
 
         mViewProxy.setScreenshot(screenshot);
 
-        // ignore system bar insets for the purpose of window layout
-        mWindow.getDecorView().setOnApplyWindowInsetsListener(
-                (v, insets) -> WindowInsets.CONSUMED);
     }
 
     void prepareViewForNewScreenshot(@NonNull ScreenshotData screenshot, String oldPackageName) {
-        withWindowAttached(() -> {
+        mWindow.whenWindowAttached(() -> {
             mAnnouncementResolver.getScreenshotAnnouncement(
                     screenshot.getUserHandle().getIdentifier(),
                     announcement -> {
@@ -444,7 +422,7 @@
             @Override
             public void onTouchOutside() {
                 // TODO(159460485): Remove this when focus is handled properly in the system
-                setWindowFocusable(false);
+                mWindow.setFocusable(false);
             }
         });
 
@@ -457,9 +435,9 @@
     private void enqueueScrollCaptureRequest(UUID requestId, UserHandle owner) {
         // Wait until this window is attached to request because it is
         // the reference used to locate the target window (below).
-        withWindowAttached(() -> {
+        mWindow.whenWindowAttached(() -> {
             requestScrollCapture(requestId, owner);
-            mWindow.peekDecorView().getViewRootImpl().setActivityConfigCallback(
+            mWindow.setActivityConfigCallback(
                     new ViewRootImpl.ActivityConfigCallback() {
                         @Override
                         public void onConfigurationChanged(Configuration overrideConfig,
@@ -472,8 +450,7 @@
                                 // to set up in the new orientation.
                                 mScreenshotHandler.postDelayed(
                                         () -> requestScrollCapture(requestId, owner), 150);
-                                mViewProxy.updateInsets(
-                                        mWindowManager.getCurrentWindowMetrics().getWindowInsets());
+                                mViewProxy.updateInsets(mWindow.getWindowInsets());
                                 // Screenshot animation calculations won't be valid anymore,
                                 // so just end
                                 if (mScreenshotAnimation != null
@@ -489,7 +466,7 @@
     private void requestScrollCapture(UUID requestId, UserHandle owner) {
         mScrollCaptureExecutor.requestScrollCapture(
                 mDisplay.getDisplayId(),
-                mWindow.getDecorView().getWindowToken(),
+                mWindow.getWindowToken(),
                 (response) -> {
                     mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_IMPRESSION,
                             0, response.getPackageName());
@@ -528,61 +505,9 @@
                 mViewProxy::startLongScreenshotTransition);
     }
 
-    private void withWindowAttached(Runnable action) {
-        View decorView = mWindow.getDecorView();
-        if (decorView.isAttachedToWindow()) {
-            action.run();
-        } else {
-            decorView.getViewTreeObserver().addOnWindowAttachListener(
-                    new ViewTreeObserver.OnWindowAttachListener() {
-                        @Override
-                        public void onWindowAttached() {
-                            mAttachRequested = false;
-                            decorView.getViewTreeObserver().removeOnWindowAttachListener(this);
-                            action.run();
-                        }
-
-                        @Override
-                        public void onWindowDetached() {
-                        }
-                    });
-
-        }
-    }
-
-    @MainThread
-    private void attachWindow() {
-        View decorView = mWindow.getDecorView();
-        if (decorView.isAttachedToWindow() || mAttachRequested) {
-            return;
-        }
-        if (DEBUG_WINDOW) {
-            Log.d(TAG, "attachWindow");
-        }
-        mAttachRequested = true;
-        mWindowManager.addView(decorView, mWindowLayoutParams);
-        decorView.requestApplyInsets();
-
-        ViewGroup layout = decorView.requireViewById(android.R.id.content);
-        layout.setClipChildren(false);
-        layout.setClipToPadding(false);
-    }
-
     @Override
     public void removeWindow() {
-        final View decorView = mWindow.peekDecorView();
-        if (decorView != null && decorView.isAttachedToWindow()) {
-            if (DEBUG_WINDOW) {
-                Log.d(TAG, "Removing screenshot window");
-            }
-            mWindowManager.removeViewImmediate(decorView);
-            mDetachRequested = false;
-        }
-        if (mAttachRequested && !mDetachRequested) {
-            mDetachRequested = true;
-            withWindowAttached(this::removeWindow);
-        }
-
+        mWindow.removeWindow();
         mViewProxy.stopInputListening();
     }
 
@@ -759,33 +684,6 @@
                 .getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
     }
 
-    /**
-     * Updates the window focusability.  If the window is already showing, then it updates the
-     * window immediately, otherwise the layout params will be applied when the window is next
-     * shown.
-     */
-    private void setWindowFocusable(boolean focusable) {
-        if (DEBUG_WINDOW) {
-            Log.d(TAG, "setWindowFocusable: " + focusable);
-        }
-        int flags = mWindowLayoutParams.flags;
-        if (focusable) {
-            mWindowLayoutParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-        } else {
-            mWindowLayoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-        }
-        if (mWindowLayoutParams.flags == flags) {
-            if (DEBUG_WINDOW) {
-                Log.d(TAG, "setWindowFocusable: skipping, already " + focusable);
-            }
-            return;
-        }
-        final View decorView = mWindow.peekDecorView();
-        if (decorView != null && decorView.isAttachedToWindow()) {
-            mWindowManager.updateViewLayout(decorView, mWindowLayoutParams);
-        }
-    }
-
     private Rect getFullScreenRect() {
         DisplayMetrics displayMetrics = new DisplayMetrics();
         mDisplay.getRealMetrics(displayMetrics);
@@ -826,6 +724,6 @@
          *
          * @param display                 display to capture
          */
-        LegacyScreenshotController create(Display display);
+        ScreenshotController create(Display display);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotWindow.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotWindow.kt
new file mode 100644
index 0000000..644e12c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotWindow.kt
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.R
+import android.annotation.MainThread
+import android.content.Context
+import android.graphics.PixelFormat
+import android.os.IBinder
+import android.util.Log
+import android.view.Display
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewRootImpl
+import android.view.ViewTreeObserver.OnWindowAttachListener
+import android.view.Window
+import android.view.WindowInsets
+import android.view.WindowManager
+import android.window.WindowContext
+import com.android.internal.policy.PhoneWindow
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+/** Creates and manages the window in which the screenshot UI is displayed. */
+class ScreenshotWindow
+@AssistedInject
+constructor(
+    private val windowManager: WindowManager,
+    private val context: Context,
+    @Assisted private val display: Display,
+) {
+
+    val window: PhoneWindow =
+        PhoneWindow(
+            context
+                .createDisplayContext(display)
+                .createWindowContext(WindowManager.LayoutParams.TYPE_SCREENSHOT, null)
+        )
+    private val params =
+        WindowManager.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                0, /* xpos */
+                0, /* ypos */
+                WindowManager.LayoutParams.TYPE_SCREENSHOT,
+                WindowManager.LayoutParams.FLAG_FULLSCREEN or
+                    WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or
+                    WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
+                    WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or
+                    WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
+                    WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
+                PixelFormat.TRANSLUCENT
+            )
+            .apply {
+                layoutInDisplayCutoutMode =
+                    WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+                setFitInsetsTypes(0)
+                // This is needed to let touches pass through outside the touchable areas
+                privateFlags =
+                    privateFlags or WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
+                title = "ScreenshotUI"
+            }
+    private var attachRequested: Boolean = false
+    private var detachRequested: Boolean = false
+
+    init {
+        window.requestFeature(Window.FEATURE_NO_TITLE)
+        window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)
+        window.setBackgroundDrawableResource(R.color.transparent)
+        window.setWindowManager(windowManager, null, null)
+    }
+
+    @MainThread
+    fun attachWindow() {
+        val decorView: View = window.getDecorView()
+        if (decorView.isAttachedToWindow || attachRequested) {
+            return
+        }
+        if (LogConfig.DEBUG_WINDOW) {
+            Log.d(TAG, "attachWindow")
+        }
+        attachRequested = true
+        windowManager.addView(decorView, params)
+
+        decorView.requestApplyInsets()
+        decorView.requireViewById<ViewGroup>(R.id.content).apply {
+            clipChildren = false
+            clipToPadding = false
+            // ignore system bar insets for the purpose of window layout
+            setOnApplyWindowInsetsListener { _, _ -> WindowInsets.CONSUMED }
+        }
+    }
+
+    fun whenWindowAttached(action: Runnable) {
+        val decorView: View = window.getDecorView()
+        if (decorView.isAttachedToWindow) {
+            action.run()
+        } else {
+            decorView
+                .getViewTreeObserver()
+                .addOnWindowAttachListener(
+                    object : OnWindowAttachListener {
+                        override fun onWindowAttached() {
+                            attachRequested = false
+                            decorView.getViewTreeObserver().removeOnWindowAttachListener(this)
+                            action.run()
+                        }
+
+                        override fun onWindowDetached() {}
+                    }
+                )
+        }
+    }
+
+    fun removeWindow() {
+        val decorView: View? = window.peekDecorView()
+        if (decorView != null && decorView.isAttachedToWindow) {
+            if (LogConfig.DEBUG_WINDOW) {
+                Log.d(TAG, "Removing screenshot window")
+            }
+            windowManager.removeViewImmediate(decorView)
+            detachRequested = false
+        }
+        if (attachRequested && !detachRequested) {
+            detachRequested = true
+            whenWindowAttached { removeWindow() }
+        }
+    }
+
+    /**
+     * Updates the window focusability. If the window is already showing, then it updates the window
+     * immediately, otherwise the layout params will be applied when the window is next shown.
+     */
+    fun setFocusable(focusable: Boolean) {
+        if (LogConfig.DEBUG_WINDOW) {
+            Log.d(TAG, "setWindowFocusable: $focusable")
+        }
+        val flags: Int = params.flags
+        if (focusable) {
+            params.flags = params.flags and WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE.inv()
+        } else {
+            params.flags = params.flags or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+        }
+        if (params.flags == flags) {
+            if (LogConfig.DEBUG_WINDOW) {
+                Log.d(TAG, "setWindowFocusable: skipping, already $focusable")
+            }
+            return
+        }
+        window.peekDecorView()?.also {
+            if (it.isAttachedToWindow) {
+                windowManager.updateViewLayout(it, params)
+            }
+        }
+    }
+
+    fun getContext(): WindowContext = window.context as WindowContext
+
+    fun getWindowToken(): IBinder = window.decorView.windowToken
+
+    fun getWindowInsets(): WindowInsets = windowManager.currentWindowMetrics.windowInsets
+
+    fun setContentView(view: View) {
+        window.setContentView(view)
+    }
+
+    fun setActivityConfigCallback(callback: ViewRootImpl.ActivityConfigCallback) {
+        window.peekDecorView().viewRootImpl.setActivityConfigCallback(callback)
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(display: Display): ScreenshotWindow
+    }
+
+    companion object {
+        private const val TAG = "ScreenshotWindow"
+    }
+}
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 79e8b87..7f8c146 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
@@ -19,25 +19,25 @@
 import android.content.res.Resources
 import android.util.Log
 import android.view.View
-import com.android.systemui.dagger.SysUISingleton
 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
 import com.android.systemui.settings.brightness.ToggleSlider
 import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor
-import javax.inject.Inject
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
 
-@SysUISingleton
 class BrightnessMirrorViewModel
-@Inject
+@AssistedInject
 constructor(
     private val brightnessMirrorShowingInteractor: BrightnessMirrorShowingInteractor,
     @Main private val resources: Resources,
     val sliderControllerFactory: BrightnessSliderController.Factory,
-) : MirrorController {
+) : SysUiViewModel(), MirrorController {
 
     private val tempPosition = IntArray(2)
 
@@ -99,6 +99,11 @@
 
     override fun removeCallback(listener: MirrorController.BrightnessMirrorListener) {}
 
+    @AssistedFactory
+    interface Factory {
+        fun create(): BrightnessMirrorViewModel
+    }
+
     companion object {
         private const val TAG = "BrightnessMirrorViewModel"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index 05c50fe..22f62fc 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -55,10 +55,12 @@
 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.media.controls.ui.controller.KeyguardMediaController
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
 import com.android.systemui.util.kotlin.collectFlow
@@ -88,6 +90,8 @@
     private val communalContent: CommunalContent,
     @Communal private val dataSourceDelegator: SceneDataSourceDelegator,
     private val notificationStackScrollLayoutController: NotificationStackScrollLayoutController,
+    private val keyguardMediaController: KeyguardMediaController,
+    private val lockscreenSmartspaceController: LockscreenSmartspaceController
 ) : LifecycleOwner {
 
     private class CommunalWrapper(context: Context) : FrameLayout(context) {
@@ -293,7 +297,7 @@
                     )
 
                 containerView.systemGestureExclusionRects =
-                    if (Flags.hubmodeFullscreenVerticalSwipe()) {
+                    if (Flags.hubmodeFullscreenVerticalSwipeFix()) {
                         listOf(
                             // Disable back gestures on the left side of the screen, to avoid
                             // conflicting with scene transitions.
@@ -445,7 +449,12 @@
         // the touch.
         if (
             !hubShowing &&
-                !notificationStackScrollLayoutController.isBelowLastNotification(ev.x, ev.y)
+                (!notificationStackScrollLayoutController.isBelowLastNotification(ev.x, ev.y) ||
+                    keyguardMediaController.isWithinMediaViewBounds(ev.x.toInt(), ev.y.toInt()) ||
+                    lockscreenSmartspaceController.isWithinSmartspaceBounds(
+                        ev.x.toInt(),
+                        ev.y.toInt()
+                    ))
         ) {
             return false
         }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 257390f..65a59f5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1097,14 +1097,6 @@
             }
 
             @Override
-            public void onPulseExpansionAmountChanged(boolean expandingChanged) {
-                if (mKeyguardBypassController.getBypassEnabled()) {
-                    // Position the notifications while dragging down while pulsing
-                    requestScrollerTopPaddingUpdate(false /* animate */);
-                }
-            }
-
-            @Override
             public void onDelayedDozeAmountAnimationRunning(boolean running) {
                 // On running OR finished, the animation is no longer waiting to play
                 setWillPlayDelayedDozeAmountAnimation(false);
@@ -1347,6 +1339,10 @@
                 "NotificationPanelViewController.updateResources");
 
         if (splitShadeChanged) {
+            if (isPanelVisibleBecauseOfHeadsUp()) {
+                // workaround for b/324642496, because HUNs set state to OPENING
+                onPanelStateChanged(STATE_CLOSED);
+            }
             onSplitShadeEnabledChanged();
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
index 8b88da1..348b6ba 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
@@ -27,7 +27,6 @@
 import androidx.constraintlayout.widget.ConstraintSet.START
 import androidx.constraintlayout.widget.ConstraintSet.TOP
 import androidx.lifecycle.lifecycleScope
-import com.android.systemui.Flags.centralizedStatusBarHeightFix
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.fragments.FragmentService
@@ -191,11 +190,7 @@
     }
 
     private fun calculateLargeShadeHeaderHeight(): Int {
-        return if (centralizedStatusBarHeightFix()) {
-            largeScreenHeaderHelperLazy.get().getLargeScreenHeaderHeight()
-        } else {
-            resources.getDimensionPixelSize(R.dimen.large_screen_shade_header_height)
-        }
+        return largeScreenHeaderHelperLazy.get().getLargeScreenHeaderHeight()
     }
 
     private fun calculateShadeHeaderHeight(): Int {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
index 9f61d4e..16aef65 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
@@ -19,7 +19,6 @@
 
 import static android.view.WindowInsets.Type.ime;
 
-import static com.android.systemui.Flags.centralizedStatusBarHeightFix;
 import static com.android.systemui.classifier.Classifier.QS_COLLAPSE;
 import static com.android.systemui.shade.NotificationPanelViewController.COUNTER_PANEL_OPEN_QS;
 import static com.android.systemui.shade.NotificationPanelViewController.FLING_COLLAPSE;
@@ -444,10 +443,7 @@
         mUseLargeScreenShadeHeader =
                 LargeScreenUtils.shouldUseLargeScreenShadeHeader(mPanelView.getResources());
         mLargeScreenShadeHeaderHeight =
-                centralizedStatusBarHeightFix()
-                        ? mLargeScreenHeaderHelperLazy.get().getLargeScreenHeaderHeight()
-                        : mResources.getDimensionPixelSize(
-                                R.dimen.large_screen_shade_header_height);
+                mLargeScreenHeaderHelperLazy.get().getLargeScreenHeaderHeight();
         int topMargin = mUseLargeScreenShadeHeader ? mLargeScreenShadeHeaderHeight :
                 mResources.getDimensionPixelSize(R.dimen.notification_panel_margin_top);
         mShadeHeaderController.setLargeScreenActive(mUseLargeScreenShadeHeader);
@@ -2256,8 +2252,11 @@
             // panel, mQs will not need to be null cause it will be tied to the same lifecycle.
             if (fragment == mQs) {
                 // Clear it to remove bindings to mQs from the provider.
-                mNotificationStackScrollLayoutController.setQsHeaderBoundsProvider(null);
-                mNotificationStackScrollLayoutController.setQsHeader(null);
+                if (QSComposeFragment.isEnabled()) {
+                    mNotificationStackScrollLayoutController.setQsHeaderBoundsProvider(null);
+                } else {
+                    mNotificationStackScrollLayoutController.setQsHeader(null);
+                }
                 mQs = null;
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
index 37da114..c49cfbd 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
@@ -38,7 +38,6 @@
 import com.android.app.animation.Interpolators
 import com.android.settingslib.Utils
 import com.android.systemui.Dumpable
-import com.android.systemui.Flags.centralizedStatusBarHeightFix
 import com.android.systemui.animation.ShadeInterpolation
 import com.android.systemui.battery.BatteryMeterView
 import com.android.systemui.battery.BatteryMeterViewController
@@ -231,10 +230,12 @@
     private val demoModeReceiver =
         object : DemoMode {
             override fun demoCommands() = listOf(DemoMode.COMMAND_CLOCK)
+
             override fun dispatchDemoCommand(command: String, args: Bundle) =
                 clock.dispatchDemoCommand(command, args)
 
             override fun onDemoModeStarted() = clock.onDemoModeStarted()
+
             override fun onDemoModeFinished() = clock.onDemoModeFinished()
         }
 
@@ -442,9 +443,7 @@
             changes += combinedShadeHeadersConstraintManager.emptyCutoutConstraints()
         }
 
-        if (centralizedStatusBarHeightFix()) {
-            view.setPadding(view.paddingLeft, sbInsets.top, view.paddingRight, view.paddingBottom)
-        }
+        view.setPadding(view.paddingLeft, sbInsets.top, view.paddingRight, view.paddingBottom)
         view.updateAllConstraints(changes)
         updateBatteryMode()
     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
index bc23778..21bbaa5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.keyguard.ui.view.KeyguardRootView
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies
 import com.android.systemui.privacy.OngoingPrivacyChip
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
@@ -83,6 +84,7 @@
             scenesProvider: Provider<Set<@JvmSuppressWildcards Scene>>,
             layoutInsetController: NotificationInsetsController,
             sceneDataSourceDelegator: Provider<SceneDataSourceDelegator>,
+            alternateBouncerDependencies: Provider<AlternateBouncerDependencies>,
         ): WindowRootView {
             return if (SceneContainerFlag.isEnabled) {
                 checkNoSceneDuplicates(scenesProvider.get())
@@ -96,6 +98,7 @@
                     scenes = scenesProvider.get(),
                     layoutInsetController = layoutInsetController,
                     sceneDataSourceDelegator = sceneDataSourceDelegator.get(),
+                    alternateBouncerDependencies = alternateBouncerDependencies.get(),
                 )
                 sceneWindowRootView
             } else {
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 684a484..d64b21f 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
@@ -55,7 +55,7 @@
     keyguardRepository: KeyguardRepository,
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
     powerInteractor: PowerInteractor,
-    shadeRepository: ShadeRepository,
+    private val shadeRepository: ShadeRepository,
     userSetupRepository: UserSetupRepository,
     userSwitcherInteractor: UserSwitcherInteractor,
     private val baseShadeInteractor: BaseShadeInteractor,
@@ -114,11 +114,13 @@
                 initialValue = determineShadeMode(isShadeLayoutWide.value)
             )
 
-    override val shadeAlignment: ShadeAlignment =
-        if (shadeRepository.isDualShadeAlignedToBottom) {
-            ShadeAlignment.Bottom
-        } else {
-            ShadeAlignment.Top
+    override val shadeAlignment: ShadeAlignment
+        get() {
+            return if (shadeRepository.isDualShadeAlignedToBottom) {
+                ShadeAlignment.Bottom
+            } else {
+                ShadeAlignment.Top
+            }
         }
 
     override val isExpandToQsEnabled: Flow<Boolean> =
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
index 9e221d3..f48e31e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.shade.data.repository.ShadeRepository
 import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
 import javax.inject.Inject
@@ -46,6 +47,10 @@
     sharedNotificationContainerInteractor: SharedNotificationContainerInteractor,
     repository: ShadeRepository,
 ) : BaseShadeInteractor {
+    init {
+        SceneContainerFlag.assertInLegacyMode()
+    }
+
     /**
      * The amount [0-1] that the shade has been opened. Uses stateIn to avoid redundant calculations
      * in downstream flows.
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 9617b54..6a21531 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
@@ -22,8 +22,9 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.SceneFamilies
-import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
+import com.android.systemui.shade.data.repository.ShadeRepository
 import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -43,8 +44,12 @@
 constructor(
     @Application scope: CoroutineScope,
     sceneInteractor: SceneInteractor,
-    sharedNotificationContainerInteractor: SharedNotificationContainerInteractor,
+    shadeRepository: ShadeRepository,
 ) : BaseShadeInteractor {
+    init {
+        SceneContainerFlag.assertInNewMode()
+    }
+
     override val shadeExpansion: StateFlow<Float> =
         sceneBasedExpansion(sceneInteractor, SceneFamilies.NotifShade)
             .traceAsCounter("panel_expansion") { (it * 100f).toInt() }
@@ -55,7 +60,7 @@
 
     override val qsExpansion: StateFlow<Float> =
         combine(
-                sharedNotificationContainerInteractor.isSplitShadeEnabled,
+                shadeRepository.isShadeLayoutWide,
                 shadeExpansion,
                 sceneBasedQsExpansion,
             ) { isSplitShadeEnabled, shadeExpansion, qsExpansion ->
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 e1289af..2f98488 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModel.kt
@@ -17,8 +17,11 @@
 package com.android.systemui.shade.ui.viewmodel
 
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
 import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
+import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.map
@@ -29,7 +32,11 @@
 @Inject
 constructor(
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    keyguardInteractor: KeyguardInteractor,
 ) {
     val isKeyguardOccluded: Flow<Boolean> =
-        keyguardTransitionInteractor.transitionValue(OCCLUDED).map { it == 1f }
+        anyOf(
+            keyguardTransitionInteractor.transitionValue(OCCLUDED).map { it == 1f },
+            keyguardTransitionInteractor.transitionValue(DREAMING).map { it == 1f },
+        )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt
index 6551854..566bc16 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt
@@ -17,43 +17,39 @@
 package com.android.systemui.shade.ui.viewmodel
 
 import com.android.compose.animation.scene.SceneKey
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
+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 javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharingStarted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.stateIn
+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.
  */
-@SysUISingleton
 class OverlayShadeViewModel
-@Inject
-constructor(
-    @Application applicationScope: CoroutineScope,
-    private val sceneInteractor: SceneInteractor,
-    shadeInteractor: ShadeInteractor
-) {
+@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> =
-        sceneInteractor
-            .resolveSceneFamily(SceneFamilies.Home)
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = Scenes.Lockscreen,
-            )
+    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() {
+        sceneInteractor.resolveSceneFamily(SceneFamilies.Home).collectLatest { sceneKey ->
+            _backgroundScene.value = sceneKey
+        }
+    }
+
     /** Notifies that the user has clicked the semi-transparent background scrim. */
     fun onScrimClicked() {
         sceneInteractor.changeScene(
@@ -61,4 +57,9 @@
             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 b2e0cd0..03fdfa9 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,8 +24,7 @@
 import android.os.UserHandle
 import android.provider.Settings
 import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.lifecycle.SysUiViewModel
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.privacy.OngoingPrivacyChip
 import com.android.systemui.privacy.PrivacyItem
@@ -38,44 +37,40 @@
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
 import java.util.Date
 import java.util.Locale
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.SharingStarted
 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
-import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
 
 /** Models UI state for the shade header. */
-@SysUISingleton
 class ShadeHeaderViewModel
-@Inject
+@AssistedInject
 constructor(
-    @Application private val applicationScope: CoroutineScope,
-    context: Context,
+    private val context: Context,
     private val activityStarter: ActivityStarter,
     private val sceneInteractor: SceneInteractor,
-    shadeInteractor: ShadeInteractor,
-    mobileIconsInteractor: MobileIconsInteractor,
+    private val shadeInteractor: ShadeInteractor,
+    private val mobileIconsInteractor: MobileIconsInteractor,
     val mobileIconsViewModel: MobileIconsViewModel,
     private val privacyChipInteractor: PrivacyChipInteractor,
     private val clockInteractor: ShadeHeaderClockInteractor,
-    broadcastDispatcher: BroadcastDispatcher,
-) {
+    private val broadcastDispatcher: BroadcastDispatcher,
+) : SysUiViewModel() {
     /** True if there is exactly one mobile connection. */
     val isSingleCarrier: StateFlow<Boolean> = mobileIconsInteractor.isSingleCarrier
 
+    private val _mobileSubIds = MutableStateFlow(emptyList<Int>())
     /** The list of subscription Ids for current mobile connections. */
-    val mobileSubIds =
-        mobileIconsInteractor.filteredSubscriptions
-            .map { list -> list.map { it.subscriptionId } }
-            .stateIn(applicationScope, SharingStarted.WhileSubscribed(), emptyList())
+    val mobileSubIds: StateFlow<List<Int>> = _mobileSubIds.asStateFlow()
 
     /** The list of PrivacyItems to be displayed by the privacy chip. */
     val privacyItems: StateFlow<List<PrivacyItem>> = privacyChipInteractor.privacyItems
@@ -94,11 +89,9 @@
     /** Whether or not the privacy chip is enabled in the device privacy config. */
     val isPrivacyChipEnabled: StateFlow<Boolean> = privacyChipInteractor.isChipEnabled
 
+    private val _isDisabled = MutableStateFlow(false)
     /** Whether or not the Shade Header should be disabled based on disableFlags. */
-    val isDisabled: StateFlow<Boolean> =
-        shadeInteractor.isQsEnabled
-            .map { !it }
-            .stateIn(applicationScope, SharingStarted.WhileSubscribed(), false)
+    val isDisabled: StateFlow<Boolean> = _isDisabled.asStateFlow()
 
     private val longerPattern = context.getString(R.string.abbrev_wday_month_day_no_year_alarm)
     private val shorterPattern = context.getString(R.string.abbrev_month_day_no_year)
@@ -111,26 +104,40 @@
     private val _longerDateText: MutableStateFlow<String> = MutableStateFlow("")
     val longerDateText: StateFlow<String> = _longerDateText.asStateFlow()
 
-    init {
-        broadcastDispatcher
-            .broadcastFlow(
-                filter =
-                    IntentFilter().apply {
-                        addAction(Intent.ACTION_TIME_TICK)
-                        addAction(Intent.ACTION_TIME_CHANGED)
-                        addAction(Intent.ACTION_TIMEZONE_CHANGED)
-                        addAction(Intent.ACTION_LOCALE_CHANGED)
-                    },
-                user = UserHandle.SYSTEM,
-                map = { intent, _ ->
-                    intent.action == Intent.ACTION_TIMEZONE_CHANGED ||
-                        intent.action == Intent.ACTION_LOCALE_CHANGED
-                }
-            )
-            .onEach { invalidateFormats -> updateDateTexts(invalidateFormats) }
-            .launchIn(applicationScope)
+    override suspend fun onActivated() {
+        coroutineScope {
+            launch {
+                broadcastDispatcher
+                    .broadcastFlow(
+                        filter =
+                            IntentFilter().apply {
+                                addAction(Intent.ACTION_TIME_TICK)
+                                addAction(Intent.ACTION_TIME_CHANGED)
+                                addAction(Intent.ACTION_TIMEZONE_CHANGED)
+                                addAction(Intent.ACTION_LOCALE_CHANGED)
+                            },
+                        user = UserHandle.SYSTEM,
+                        map = { intent, _ ->
+                            intent.action == Intent.ACTION_TIMEZONE_CHANGED ||
+                                intent.action == Intent.ACTION_LOCALE_CHANGED
+                        }
+                    )
+                    .onEach { invalidateFormats -> updateDateTexts(invalidateFormats) }
+                    .launchIn(this)
+            }
 
-        applicationScope.launch { updateDateTexts(false) }
+            launch { updateDateTexts(false) }
+
+            launch {
+                mobileIconsInteractor.filteredSubscriptions
+                    .map { list -> list.map { it.subscriptionId } }
+                    .collectLatest { _mobileSubIds.value = it }
+            }
+
+            launch {
+                shadeInteractor.isQsEnabled.map { !it }.collectLatest { _isDisabled.value = it }
+            }
+        }
     }
 
     /** Notifies that the privacy chip was clicked. */
@@ -182,4 +189,9 @@
         format.setContext(DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE)
         return format
     }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(): ShadeHeaderViewModel
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModel.kt
new file mode 100644
index 0000000..bdc0fdb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModel.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.shade.ui.viewmodel
+
+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.qs.ui.adapter.QSSceneAdapter
+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.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
+
+/**
+ * Models the UI state for the user actions that the user can perform to navigate to other scenes.
+ *
+ * Different from the [ShadeSceneContentViewModel] which models the _content_ of the scene.
+ */
+class ShadeSceneActionsViewModel
+@AssistedInject
+constructor(
+    private val qsSceneAdapter: QSSceneAdapter,
+    private val shadeInteractor: ShadeInteractor,
+) : SceneActionsViewModel() {
+
+    override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
+        combine(
+                shadeInteractor.shadeMode,
+                qsSceneAdapter.isCustomizerShowing,
+            ) { shadeMode, isCustomizerShowing ->
+                buildMap<UserAction, UserActionResult> {
+                    if (!isCustomizerShowing) {
+                        set(
+                            Swipe(SwipeDirection.Up),
+                            UserActionResult(
+                                SceneFamilies.Home,
+                                ToSplitShade.takeIf { shadeMode is ShadeMode.Split }
+                            )
+                        )
+                    }
+
+                    // TODO(b/330200163) Add an else to be able to collapse the shade while
+                    // customizing
+                    if (shadeMode is ShadeMode.Single) {
+                        set(Swipe(SwipeDirection.Down), UserActionResult(Scenes.QuickSettings))
+                    }
+                }
+            }
+            .collectLatest { actions -> setActions(actions) }
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(): ShadeSceneActionsViewModel
+    }
+}
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
new file mode 100644
index 0000000..a4d3416
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt
@@ -0,0 +1,110 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.shade.ui.viewmodel
+
+import androidx.lifecycle.LifecycleOwner
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.lifecycle.SysUiViewModel
+import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
+import com.android.systemui.qs.FooterActionsController
+import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
+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.unfold.domain.interactor.UnfoldTransitionInteractor
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.util.concurrent.atomic.AtomicBoolean
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+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
+ * to navigate to other scenes.
+ */
+class ShadeSceneContentViewModel
+@AssistedInject
+constructor(
+    val qsSceneAdapter: QSSceneAdapter,
+    val shadeHeaderViewModelFactory: ShadeHeaderViewModel.Factory,
+    val brightnessMirrorViewModelFactory: BrightnessMirrorViewModel.Factory,
+    val mediaCarouselInteractor: MediaCarouselInteractor,
+    shadeInteractor: ShadeInteractor,
+    private val footerActionsViewModelFactory: FooterActionsViewModel.Factory,
+    private val footerActionsController: FooterActionsController,
+    private val unfoldTransitionInteractor: UnfoldTransitionInteractor,
+    private val deviceEntryInteractor: DeviceEntryInteractor,
+    private val sceneInteractor: SceneInteractor,
+) : SysUiViewModel() {
+
+    val shadeMode: StateFlow<ShadeMode> = shadeInteractor.shadeMode
+
+    private val _isEmptySpaceClickable =
+        MutableStateFlow(!deviceEntryInteractor.isDeviceEntered.value)
+    /** Whether clicking on the empty area of the shade does something */
+    val isEmptySpaceClickable: StateFlow<Boolean> = _isEmptySpaceClickable.asStateFlow()
+
+    val isMediaVisible: StateFlow<Boolean> = mediaCarouselInteractor.hasActiveMediaOrRecommendation
+
+    private val footerActionsControllerInitialized = AtomicBoolean(false)
+
+    override suspend fun onActivated() {
+        deviceEntryInteractor.isDeviceEntered.collectLatest { isDeviceEntered ->
+            _isEmptySpaceClickable.value = !isDeviceEntered
+        }
+    }
+
+    /**
+     * Amount of X-axis translation to apply to various elements as the unfolded foldable is folded
+     * slightly, in pixels.
+     */
+    fun unfoldTranslationX(isOnStartSide: Boolean): Flow<Float> {
+        return unfoldTransitionInteractor.unfoldTranslationX(isOnStartSide)
+    }
+
+    fun getFooterActionsViewModel(lifecycleOwner: LifecycleOwner): FooterActionsViewModel {
+        if (footerActionsControllerInitialized.compareAndSet(false, true)) {
+            footerActionsController.init()
+        }
+        return footerActionsViewModelFactory.create(lifecycleOwner)
+    }
+
+    /** Notifies that the empty space in the shade has been clicked. */
+    fun onEmptySpaceClicked() {
+        if (!isEmptySpaceClickable.value) {
+            return
+        }
+
+        sceneInteractor.changeScene(Scenes.Lockscreen, "Shade empty space clicked.")
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(): ShadeSceneContentViewModel
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
deleted file mode 100644
index 06298ef..0000000
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
+++ /dev/null
@@ -1,152 +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.shade.ui.viewmodel
-
-import androidx.lifecycle.LifecycleOwner
-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.UserAction
-import com.android.compose.animation.scene.UserActionResult
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.lifecycle.Activatable
-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.SceneFamilies
-import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
-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.unfold.domain.interactor.UnfoldTransitionInteractor
-import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
-import java.util.concurrent.atomic.AtomicBoolean
-import javax.inject.Inject
-import kotlinx.coroutines.coroutineScope
-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
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.launch
-
-/** Models UI state and handles user input for the shade scene. */
-@SysUISingleton
-class ShadeSceneViewModel
-@Inject
-constructor(
-    val qsSceneAdapter: QSSceneAdapter,
-    val shadeHeaderViewModel: ShadeHeaderViewModel,
-    val brightnessMirrorViewModel: BrightnessMirrorViewModel,
-    val mediaCarouselInteractor: MediaCarouselInteractor,
-    shadeInteractor: ShadeInteractor,
-    private val footerActionsViewModelFactory: FooterActionsViewModel.Factory,
-    private val footerActionsController: FooterActionsController,
-    private val sceneInteractor: SceneInteractor,
-    private val unfoldTransitionInteractor: UnfoldTransitionInteractor,
-) : Activatable {
-    val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
-        combine(
-            shadeInteractor.shadeMode,
-            qsSceneAdapter.isCustomizerShowing,
-        ) { shadeMode, isCustomizerShowing ->
-            buildMap {
-                if (!isCustomizerShowing) {
-                    set(
-                        Swipe(SwipeDirection.Up),
-                        UserActionResult(
-                            SceneFamilies.Home,
-                            ToSplitShade.takeIf { shadeMode is ShadeMode.Split }
-                        )
-                    )
-                }
-
-                // TODO(b/330200163) Add an else to be able to collapse the shade while customizing
-                if (shadeMode is ShadeMode.Single) {
-                    set(Swipe(SwipeDirection.Down), UserActionResult(Scenes.QuickSettings))
-                }
-            }
-        }
-
-    private val upDestinationSceneKey: Flow<SceneKey?> =
-        destinationScenes.map { it[Swipe(SwipeDirection.Up)]?.toScene }
-
-    private val _isClickable = MutableStateFlow(false)
-    /** Whether or not the shade container should be clickable. */
-    val isClickable: StateFlow<Boolean> = _isClickable.asStateFlow()
-
-    /**
-     * Activates the view-model.
-     *
-     * Serves as an entrypoint to kick off coroutine work that the view-model requires in order to
-     * keep its state fresh and/or perform side-effects.
-     *
-     * Suspends the caller forever as it will keep doing work until canceled.
-     *
-     * **Must be invoked** when the scene becomes the current scene or when it becomes visible
-     * during a transition (the choice is the responsibility of the parent). Similarly, the work
-     * must be canceled when the scene stops being visible or the current scene.
-     */
-    override suspend fun activate() {
-        coroutineScope {
-            launch {
-                upDestinationSceneKey
-                    .flatMapLatestConflated { key ->
-                        key?.let { sceneInteractor.resolveSceneFamily(key) } ?: flowOf(null)
-                    }
-                    .map { it == Scenes.Lockscreen }
-                    .collectLatest { _isClickable.value = it }
-            }
-        }
-    }
-
-    val shadeMode: StateFlow<ShadeMode> = shadeInteractor.shadeMode
-
-    val isMediaVisible: StateFlow<Boolean> = mediaCarouselInteractor.hasActiveMediaOrRecommendation
-
-    /**
-     * Amount of X-axis translation to apply to various elements as the unfolded foldable is folded
-     * slightly, in pixels.
-     */
-    fun unfoldTranslationX(isOnStartSide: Boolean): Flow<Float> {
-        return unfoldTransitionInteractor.unfoldTranslationX(isOnStartSide)
-    }
-
-    /** Notifies that some content in the shade was clicked. */
-    fun onContentClicked() {
-        if (!isClickable.value) {
-            return
-        }
-
-        sceneInteractor.changeScene(Scenes.Lockscreen, "Shade empty content clicked")
-    }
-
-    private val footerActionsControllerInitialized = AtomicBoolean(false)
-
-    fun getFooterActionsViewModel(lifecycleOwner: LifecycleOwner): FooterActionsViewModel {
-        if (footerActionsControllerInitialized.compareAndSet(false, true)) {
-            footerActionsController.init()
-        }
-        return footerActionsViewModelFactory.create(lifecycleOwner)
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index cea97d6..a1477b5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -19,7 +19,6 @@
 import static android.app.StatusBarManager.DISABLE2_NONE;
 import static android.app.StatusBarManager.DISABLE_NONE;
 import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT;
-import static android.inputmethodservice.InputMethodService.IME_INVISIBLE;
 import static android.view.Display.INVALID_DISPLAY;
 
 import android.annotation.Nullable;
@@ -38,6 +37,7 @@
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
 import android.inputmethodservice.InputMethodService.BackDispositionMode;
+import android.inputmethodservice.InputMethodService.ImeWindowVisibility;
 import android.media.INearbyMediaDevicesProvider;
 import android.media.MediaRoute2Info;
 import android.os.Binder;
@@ -258,10 +258,10 @@
          *
          * @param displayId The id of the display to notify.
          * @param vis IME visibility.
-         * @param backDisposition Disposition mode of back button. It should be one of below flags:
+         * @param backDisposition Disposition mode of back button.
          * @param showImeSwitcher {@code true} to show IME switch button.
          */
-        default void setImeWindowStatus(int displayId, int vis,
+        default void setImeWindowStatus(int displayId, @ImeWindowVisibility int vis,
                 @BackDispositionMode int backDisposition, boolean showImeSwitcher) { }
         default void showRecentApps(boolean triggeredFromAltTab) { }
         default void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) { }
@@ -744,8 +744,8 @@
     }
 
     @Override
-    public void setImeWindowStatus(int displayId, int vis, int backDisposition,
-            boolean showImeSwitcher) {
+    public void setImeWindowStatus(int displayId, @ImeWindowVisibility int vis,
+            @BackDispositionMode int backDisposition, boolean showImeSwitcher) {
         synchronized (mLock) {
             mHandler.removeMessages(MSG_SHOW_IME_BUTTON);
             SomeArgs args = SomeArgs.obtain();
@@ -1206,8 +1206,8 @@
         }
     }
 
-    private void handleShowImeButton(int displayId, int vis, int backDisposition,
-            boolean showImeSwitcher) {
+    private void handleShowImeButton(int displayId, @ImeWindowVisibility int vis,
+            @BackDispositionMode int backDisposition, boolean showImeSwitcher) {
         if (displayId == INVALID_DISPLAY) return;
 
         boolean isConcurrentMultiUserModeEnabled = UserManager.isVisibleBackgroundUsersEnabled()
@@ -1219,7 +1219,7 @@
                 && mLastUpdatedImeDisplayId != INVALID_DISPLAY) {
             // Set previous NavBar's IME window status as invisible when IME
             // window switched to another display for single-session IME case.
-            sendImeInvisibleStatusForPrevNavBar();
+            sendImeNotVisibleStatusForPrevNavBar();
         }
         for (int i = 0; i < mCallbacks.size(); i++) {
             mCallbacks.get(i).setImeWindowStatus(displayId, vis, backDisposition, showImeSwitcher);
@@ -1227,9 +1227,9 @@
         mLastUpdatedImeDisplayId = displayId;
     }
 
-    private void sendImeInvisibleStatusForPrevNavBar() {
+    private void sendImeNotVisibleStatusForPrevNavBar() {
         for (int i = 0; i < mCallbacks.size(); i++) {
-            mCallbacks.get(i).setImeWindowStatus(mLastUpdatedImeDisplayId, IME_INVISIBLE,
+            mCallbacks.get(i).setImeWindowStatus(mLastUpdatedImeDisplayId, 0 /* vis */,
                     BACK_DISPOSITION_DEFAULT, false /* showImeSwitcher */);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index c1eb8bc..5eef8ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -785,7 +785,8 @@
 
     private void updateLockScreenAdaptiveAuthMsg(int userId) {
         final boolean deviceLocked = mKeyguardUpdateMonitor.isDeviceLockedByAdaptiveAuth(userId);
-        if (deviceLocked) {
+        final boolean canSkipBouncer = mKeyguardUpdateMonitor.getUserCanSkipBouncer(userId);
+        if (deviceLocked && !canSkipBouncer) {
             mRotateTextViewController.updateIndication(
                     INDICATION_TYPE_ADAPTIVE_AUTH,
                     new KeyguardIndication.Builder()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
index 49743bf..ccea254 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -35,7 +35,6 @@
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.PipelineDumpable;
 import com.android.systemui.statusbar.notification.collection.PipelineDumper;
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins;
 import com.android.systemui.util.time.SystemClock;
@@ -66,7 +65,6 @@
     private final SystemClock mSystemClock;
     private final Executor mMainExecutor;
     private final List<NotificationHandler> mNotificationHandlers = new ArrayList<>();
-    private final ArrayList<NotificationSettingsListener> mSettingsListeners = new ArrayList<>();
 
     private final Deque<RankingMap> mRankingMapQueue = new ConcurrentLinkedDeque<>();
     private final Runnable mDispatchRankingUpdateRunnable = this::dispatchRankingUpdate;
@@ -99,13 +97,6 @@
         mNotificationHandlers.add(handler);
     }
 
-    /** Registers a listener that's notified when any notification-related settings change. */
-    @Deprecated
-    public void addNotificationSettingsListener(NotificationSettingsListener listener) {
-        NotificationIconContainerRefactor.assertInLegacyMode();
-        mSettingsListeners.add(listener);
-    }
-
     @Override
     public void onListenerConnected() {
         if (DEBUG) Log.d(TAG, "onListenerConnected");
@@ -237,13 +228,7 @@
 
     @Override
     public void onSilentStatusBarIconsVisibilityChanged(boolean hideSilentStatusIcons) {
-        if (NotificationIconContainerRefactor.isEnabled()) {
-            mStatusIconInteractor.setHideSilentStatusIcons(hideSilentStatusIcons);
-        } else {
-            for (NotificationSettingsListener listener : mSettingsListeners) {
-                listener.onStatusBarIconsBehaviorChanged(hideSilentStatusIcons);
-            }
-        }
+        mStatusIconInteractor.setHideSilentStatusIcons(hideSilentStatusIcons);
     }
 
     public final void unsnoozeNotification(@NonNull String key) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 28e3a83..696e222 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -48,7 +48,6 @@
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
 import com.android.systemui.statusbar.notification.stack.AmbientState;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
@@ -153,11 +152,7 @@
                 R.dimen.notification_corner_animation_distance);
         mEnableNotificationClipping = res.getBoolean(R.bool.notification_enable_clipping);
 
-        if (NotificationIconContainerRefactor.isEnabled()) {
-            mShelfIcons.setOverrideIconColor(true);
-        } else {
-            mShelfIcons.setInNotificationIconShelf(true);
-        }
+        mShelfIcons.setOverrideIconColor(true);
         if (!mShowNotificationShelf) {
             setVisibility(GONE);
         }
@@ -228,9 +223,6 @@
             } else {
                 viewState.setAlpha(1f - ambientState.getHideAmount());
             }
-            if (!NotificationIconContainerRefactor.isEnabled()) {
-                viewState.belowSpeedBump = getSpeedBumpIndex() == 0;
-            }
             viewState.hideSensitive = false;
             viewState.setXTranslation(getTranslationX());
             viewState.hasItemsInStableShelf = lastViewState.inShelf;
@@ -276,30 +268,7 @@
         }
     }
 
-    private int getSpeedBumpIndex() {
-        NotificationIconContainerRefactor.assertInLegacyMode();
-        return mHostLayout.getSpeedBumpIndex();
-    }
-
-    /**
-     * @param fractionToShade Fraction of lockscreen to shade transition
-     * @param shortestWidth   Shortest width to use for lockscreen shelf
-     */
-    @VisibleForTesting
-    public void updateActualWidth(float fractionToShade, float shortestWidth) {
-        NotificationIconContainerRefactor.assertInLegacyMode();
-        final float actualWidth = mAmbientState.isOnKeyguard()
-                ? MathUtils.lerp(shortestWidth, getWidth(), fractionToShade)
-                : getWidth();
-        setBackgroundWidth((int) actualWidth);
-        if (mShelfIcons != null) {
-            mShelfIcons.setActualLayoutWidth((int) actualWidth);
-        }
-        mActualWidth = actualWidth;
-    }
-
     private void setActualWidth(float actualWidth) {
-        if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return;
         setBackgroundWidth((int) actualWidth);
         if (mShelfIcons != null) {
             mShelfIcons.setActualLayoutWidth((int) actualWidth);
@@ -482,25 +451,17 @@
         final float fractionToShade = Interpolators.STANDARD.getInterpolation(
                 mAmbientState.getFractionToShade());
 
-        if (NotificationIconContainerRefactor.isEnabled()) {
-            if (mAmbientState.isOnKeyguard()) {
-                float numViews = MathUtils.min(numViewsInShelf, mMaxIconsOnLockscreen + 1);
-                float shortestWidth = mShelfIcons.calculateWidthFor(numViews);
-                float actualWidth = MathUtils.lerp(shortestWidth, getWidth(), fractionToShade);
-                setActualWidth(actualWidth);
-            } else {
-                setActualWidth(getWidth());
-            }
+        if (mAmbientState.isOnKeyguard()) {
+            float numViews = MathUtils.min(numViewsInShelf, mMaxIconsOnLockscreen + 1);
+            float shortestWidth = mShelfIcons.calculateWidthFor(numViews);
+            float actualWidth = MathUtils.lerp(shortestWidth, getWidth(), fractionToShade);
+            setActualWidth(actualWidth);
         } else {
-            final float shortestWidth = mShelfIcons.calculateWidthFor(numViewsInShelf);
-            updateActualWidth(fractionToShade, shortestWidth);
+            setActualWidth(getWidth());
         }
 
         // TODO(b/172289889) transition last icon in shelf to notification icon and vice versa.
         setVisibility(isHidden ? View.INVISIBLE : View.VISIBLE);
-        if (!NotificationIconContainerRefactor.isEnabled()) {
-            mShelfIcons.setSpeedBumpIndex(getSpeedBumpIndex());
-        }
         mShelfIcons.calculateIconXTranslations();
         mShelfIcons.applyIconStates();
         for (int i = 0; i < getHostLayoutChildCount(); i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS b/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS
index 69ebb76..c4f539a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS
@@ -5,3 +5,12 @@
 caitlinshk@google.com
 evanlaird@google.com
 pixel@google.com
+
+per-file *Biometrics* = set noparent
+per-file *Biometrics* = file:../keyguard/OWNERS
+per-file *Doze* = set noparent
+per-file *Doze* = file:../keyguard/OWNERS
+per-file *Keyboard* = set noparent
+per-file *Keyboard* = file:../keyguard/OWNERS
+per-file *Keyguard* = set noparent
+per-file *Keyguard* = file:../keyguard/OWNERS
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index bbf0ae1..3068460 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -64,7 +64,6 @@
 import com.android.systemui.statusbar.notification.NotificationContentDescription;
 import com.android.systemui.statusbar.notification.NotificationDozeHelper;
 import com.android.systemui.statusbar.notification.NotificationUtils;
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
 import com.android.systemui.util.drawable.DrawableSize;
 
 import java.lang.annotation.Retention;
@@ -906,12 +905,7 @@
         return mDotAppearAmount;
     }
 
-    public void setDozing(boolean dozing, boolean animate, long delay) {
-        setDozing(dozing, animate, delay, /* onChildCompleted= */ null);
-    }
-
     public void setTintAlpha(float tintAlpha) {
-        if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return;
         setDozeAmount(tintAlpha);
     }
 
@@ -921,15 +915,6 @@
         updateIconColor();
     }
 
-    public void setDozing(boolean dozing, boolean animate, long delay,
-            @Nullable Runnable endRunnable) {
-        NotificationIconContainerRefactor.assertInLegacyMode();
-        mDozer.setDozing(f -> {
-            setDozeAmount(f);
-            updateAllowAnimation();
-        }, dozing, animate, delay, this, endRunnable);
-    }
-
     private void updateAllowAnimation() {
         if (mDozeAmount == 0 || mDozeAmount == 1) {
             setAllowAnimation(mDozeAmount == 0);
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 6ba4fef..9e6cacb 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
@@ -41,6 +41,7 @@
 import com.android.systemui.statusbar.chips.ui.viewmodel.ChipTransitionHelper
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener
+import com.android.systemui.util.kotlin.pairwise
 import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -64,7 +65,8 @@
     @StatusBarChipsLog private val logger: LogBuffer,
 ) : OngoingActivityChipViewModel {
 
-    private val internalChip =
+    /** A direct mapping from [ScreenRecordChipModel] to [OngoingActivityChipModel]. */
+    private val simpleChip =
         interactor.screenRecordState
             .map { state ->
                 when (state) {
@@ -105,10 +107,31 @@
             // See b/347726238 for [SharingStarted.Lazily] reasoning.
             .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden())
 
+    /**
+     * The screen record chip to show that also ensures that the start time doesn't change once we
+     * enter the recording state. If we change the start time while we're recording, the chronometer
+     * could skip a second. See b/349620526.
+     */
+    private val chipWithConsistentTimer: StateFlow<OngoingActivityChipModel> =
+        simpleChip
+            .pairwise(initialValue = OngoingActivityChipModel.Hidden())
+            .map { (old, new) ->
+                if (
+                    old is OngoingActivityChipModel.Shown.Timer &&
+                        new is OngoingActivityChipModel.Shown.Timer
+                ) {
+                    new.copy(startTimeMs = old.startTimeMs)
+                } else {
+                    new
+                }
+            }
+            // See b/347726238 for [SharingStarted.Lazily] reasoning.
+            .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden())
+
     private val chipTransitionHelper = ChipTransitionHelper(scope)
 
     override val chip: StateFlow<OngoingActivityChipModel> =
-        chipTransitionHelper.createChipFlow(internalChip)
+        chipTransitionHelper.createChipFlow(chipWithConsistentTimer)
 
     private fun createDelegate(
         recordedTask: ActivityManager.RunningTaskInfo?
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 130b117..8a5165d8 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
@@ -18,7 +18,6 @@
 
 import android.content.Context
 import android.content.res.ColorStateList
-import android.view.ContextThemeWrapper
 import androidx.annotation.ColorInt
 import com.android.settingslib.Utils
 import com.android.systemui.res.R
@@ -43,9 +42,7 @@
     /** The chip should have a red background with white text. */
     data object Red : ColorsModel {
         override fun background(context: Context): ColorStateList {
-            val themedContext =
-                ContextThemeWrapper(context, com.android.internal.R.style.Theme_DeviceDefault_Light)
-            return Utils.getColorAttr(themedContext, com.android.internal.R.attr.materialColorError)
+            return ColorStateList.valueOf(context.getColor(R.color.GM2_red_700))
         }
 
         override fun text(context: Context) = context.getColor(android.R.color.white)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GesturePointerEventListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GesturePointerEventListener.kt
index 0d5ade7..c7b3c9c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GesturePointerEventListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GesturePointerEventListener.kt
@@ -103,7 +103,7 @@
         mSwipeDistanceThreshold =
             r.getDimensionPixelSize(R.dimen.system_gestures_distance_threshold)
         val display = DisplayManagerGlobal.getInstance().getRealDisplay(mContext.displayId)
-        val displayCutout = display.cutout
+        val displayCutout = display?.cutout
         if (displayCutout != null) {
             // Expand swipe start threshold such that we can catch touches that just start beyond
             // the notch area
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index ef4dffad..97add30 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -26,6 +26,7 @@
 import android.content.Context
 import android.content.Intent
 import android.database.ContentObserver
+import android.graphics.Rect
 import android.net.Uri
 import android.os.Handler
 import android.os.UserHandle
@@ -570,6 +571,20 @@
         plugin?.unregisterListener(listener)
     }
 
+    fun isWithinSmartspaceBounds(x: Int, y: Int): Boolean {
+        smartspaceViews.forEach {
+            val bounds = Rect()
+            with(it as View) {
+                this.getBoundsOnScreen(bounds)
+                if (bounds.contains(x, y)) {
+                    return true
+                }
+            }
+        }
+
+        return false
+    }
+
     private fun filterSmartspaceTarget(t: SmartspaceTarget): Boolean {
         if (isDateWeatherDecoupled && t.featureType == SmartspaceTarget.FEATURE_WEATHER) {
             return false
@@ -587,7 +602,7 @@
                 // Only the primary user can have an associated managed profile, so only show
                 // content for the managed profile if the primary user is active
                 userTracker.userHandle.identifier == UserHandle.USER_SYSTEM &&
-                        (!t.isSensitive || showSensitiveContentForManagedUser)
+                    (!t.isSensitive || showSensitiveContentForManagedUser)
             }
             else -> {
                 false
@@ -705,4 +720,3 @@
         }
     }
 }
-
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
index 9240c1c..22c537c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -34,7 +34,6 @@
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator
 import com.android.systemui.statusbar.phone.DozeParameters
@@ -223,11 +222,6 @@
             val nowExpanding = isPulseExpanding()
             val changed = nowExpanding != pulseExpanding
             pulseExpanding = nowExpanding
-            if (!NotificationIconContainerRefactor.isEnabled) {
-                for (listener in wakeUpListeners) {
-                    listener.onPulseExpansionAmountChanged(changed)
-                }
-            }
             if (changed) {
                 for (listener in wakeUpListeners) {
                     listener.onPulseExpandingChanged(pulseExpanding)
@@ -683,17 +677,6 @@
         fun onFullyHiddenChanged(isFullyHidden: Boolean) {}
 
         /**
-         * Called whenever the pulseExpansion changes
-         *
-         * @param expandingChanged if the user has started or stopped expanding
-         */
-        @Deprecated(
-            message = "Use onPulseExpandedChanged instead.",
-            replaceWith = ReplaceWith("onPulseExpandedChanged"),
-        )
-        fun onPulseExpansionAmountChanged(expandingChanged: Boolean) {}
-
-        /**
          * Called when the animator started by [scheduleDelayedDozeAmountAnimation] begins running
          * after the start delay, or after it ends/is cancelled.
          */
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 1511abd..f74c9a6 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
@@ -28,9 +28,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.shared.NotificationIconContainerRefactor
 import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
-import com.android.systemui.statusbar.phone.NotificationIconAreaController
 import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController
 import javax.inject.Inject
 
@@ -43,7 +41,6 @@
 @Inject
 internal constructor(
     private val groupExpansionManagerImpl: GroupExpansionManagerImpl,
-    private val notificationIconAreaController: NotificationIconAreaController,
     private val renderListInteractor: RenderNotificationListInteractor,
     private val activeNotificationsInteractor: ActiveNotificationsInteractor,
     private val sensitiveNotificationProtectionController:
@@ -63,12 +60,7 @@
             } else {
                 controller.setNotifStats(notifStats)
             }
-            if (NotificationIconContainerRefactor.isEnabled || FooterViewRefactor.isEnabled) {
-                renderListInteractor.setRenderedList(entries)
-            }
-            if (!NotificationIconContainerRefactor.isEnabled) {
-                notificationIconAreaController.updateNotificationIcons(entries)
-            }
+            renderListInteractor.setRenderedList(entries)
         }
 
     private fun calculateNotifStats(entries: List<ListEntry>): NotifStats {
@@ -76,9 +68,10 @@
         var hasClearableAlertingNotifs = false
         var hasNonClearableSilentNotifs = false
         var hasClearableSilentNotifs = false
-        val isSensitiveContentProtectionActive = screenshareNotificationHiding() &&
-            screenshareNotificationHidingBugFix() &&
-            sensitiveNotificationProtectionController.isSensitiveStateActive
+        val isSensitiveContentProtectionActive =
+            screenshareNotificationHiding() &&
+                screenshareNotificationHidingBugFix() &&
+                sensitiveNotificationProtectionController.isSensitiveStateActive
         entries.forEach {
             val section = checkNotNull(it.section) { "Null section for ${it.key}" }
             val entry = checkNotNull(it.representativeEntry) { "Null notif entry for ${it.key}" }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
index 1cb59f1..9b382e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
@@ -81,11 +81,16 @@
      * [CallType.Ongoing].
      */
     val ongoingCallNotification: Flow<ActiveNotificationModel?> =
-        allRepresentativeNotifications.map { notifMap ->
-            // Once a call has started, its `whenTime` should stay the same, so we can use it as a
-            // stable sort value.
-            notifMap.values.filter { it.callType == CallType.Ongoing }.minByOrNull { it.whenTime }
-        }
+        allRepresentativeNotifications
+            .map { notifMap ->
+                // Once a call has started, its `whenTime` should stay the same, so we can use it as
+                // a stable sort value.
+                notifMap.values
+                    .filter { it.callType == CallType.Ongoing }
+                    .minByOrNull { it.whenTime }
+            }
+            .distinctUntilChanged()
+            .flowOn(backgroundDispatcher)
 
     /** Are any notifications being actively presented in the notification stack? */
     val areAnyNotificationsPresent: Flow<Boolean> =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt
deleted file mode 100644
index 1bcab3f..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt
+++ /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.systemui.statusbar.notification.icon.ui.viewbinder
-
-import android.content.Context
-import android.graphics.Rect
-import android.view.View
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.StatusBarIconView
-import com.android.systemui.statusbar.notification.collection.ListEntry
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
-import com.android.systemui.statusbar.phone.NotificationIconAreaController
-import com.android.systemui.statusbar.phone.NotificationIconContainer
-import javax.inject.Inject
-
-/**
- * Controller class for [NotificationIconContainer]. This implementation serves as a temporary
- * wrapper around [NotificationIconContainerViewBinder], so that external code can continue to
- * depend on the [NotificationIconAreaController] interface. Once
- * [LegacyNotificationIconAreaControllerImpl] is removed, this class can go away and the ViewBinder
- * can be used directly.
- */
-@SysUISingleton
-class NotificationIconAreaControllerViewBinderWrapperImpl @Inject constructor() :
-    NotificationIconAreaController {
-
-    /** Called by the Keyguard*ViewController whose view contains the aod icons. */
-    override fun setupAodIcons(aodIcons: NotificationIconContainer?) = unsupported
-
-    override fun setShelfIcons(icons: NotificationIconContainer) = unsupported
-
-    override fun onDensityOrFontScaleChanged(context: Context) = unsupported
-
-    /** Returns the view that represents the notification area. */
-    override fun getNotificationInnerAreaView(): View? = unsupported
-
-    /** Updates the notifications with the given list of notifications to display. */
-    override fun updateNotificationIcons(entries: List<ListEntry>) = unsupported
-
-    override fun updateAodNotificationIcons() = unsupported
-
-    override fun showIconIsolated(icon: StatusBarIconView?, animated: Boolean) = unsupported
-
-    override fun setIsolatedIconLocation(iconDrawingRect: Rect, requireStateUpdate: Boolean) =
-        unsupported
-
-    override fun setAnimationsEnabled(enabled: Boolean) = unsupported
-
-    override fun onThemeChanged() = unsupported
-
-    override fun getHeight(): Int = unsupported
-
-    companion object {
-        val unsupported: Nothing
-            get() =
-                error(
-                    "Code path not supported when ${NotificationIconContainerRefactor.FLAG_NAME}" +
-                        " is disabled"
-                )
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBindingFailureTracker.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBindingFailureTracker.kt
index 0c114a2..931381f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBindingFailureTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBindingFailureTracker.kt
@@ -17,7 +17,6 @@
 
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
 import com.android.systemui.util.asIndenting
 import com.android.systemui.util.printCollection
 import dagger.Binds
@@ -40,7 +39,6 @@
     }
 
     override fun dump(pw: PrintWriter, args: Array<out String>) {
-        if (!NotificationIconContainerRefactor.isEnabled) return
         pw.asIndenting().run {
             printCollection("AOD Icon binding failures:", aodFailures)
             printCollection("Status Bar Icon binding failures:", statusBarFailures)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index d2d0aaa..560028c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -46,7 +46,6 @@
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.SourceType;
 import com.android.systemui.statusbar.notification.shared.NotificationHeadsUpCycling;
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
 import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
@@ -236,16 +235,6 @@
         setOutlineAlpha(alpha);
     }
 
-    @Override
-    public void setBelowSpeedBump(boolean below) {
-        NotificationIconContainerRefactor.assertInLegacyMode();
-        super.setBelowSpeedBump(below);
-        if (below != mIsBelowSpeedBump) {
-            mIsBelowSpeedBump = below;
-            updateBackgroundTint();
-        }
-    }
-
     /**
      * Sets the tint color of the background
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 9d13a17..cb3e26b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -1804,6 +1804,20 @@
                 NotificationEntry childEntry,
                 NotificationEntry containerEntry
         );
+
+        /**
+         * Called when resetting the alpha value for content views
+         */
+        void logResetAllContentAlphas(
+                NotificationEntry entry
+        );
+
+        /**
+         * Called when resetting the alpha value for content views is skipped
+         */
+        void logSkipResetAllContentAlphas(
+                NotificationEntry entry
+        );
     }
 
     /**
@@ -3001,6 +3015,8 @@
                     mChildrenContainer.animate().cancel();
                 }
                 resetAllContentAlphas();
+            } else {
+                mLogger.logSkipResetAllContentAlphas(getEntry());
             }
             mPublicLayout.setVisibility(mShowingPublic ? View.VISIBLE : View.INVISIBLE);
             updateChildrenVisibility();
@@ -3186,6 +3202,7 @@
 
     @Override
     protected void resetAllContentAlphas() {
+        mLogger.logResetAllContentAlphas(getEntry());
         mPrivateLayout.setAlpha(1f);
         mPrivateLayout.setLayerType(LAYER_TYPE_NONE, null);
         mPublicLayout.setAlpha(1f);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index 4c76e328..c31a2cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -195,6 +195,20 @@
                 ) {
                     mLogBufferLogger.logRemoveTransientRow(childEntry, containerEntry);
                 }
+
+                @Override
+                public void logResetAllContentAlphas(
+                        NotificationEntry entry
+                ) {
+                    mLogBufferLogger.logResetAllContentAlphas(entry);
+                }
+
+                @Override
+                public void logSkipResetAllContentAlphas(
+                        NotificationEntry entry
+                ) {
+                    mLogBufferLogger.logSkipResetAllContentAlphas(entry);
+                }
             };
 
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 6becbd2..afda426 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -40,7 +40,6 @@
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.notification.Roundable;
 import com.android.systemui.statusbar.notification.RoundableState;
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
 import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.util.Compile;
@@ -394,14 +393,6 @@
     public abstract void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear,
             Runnable onEndRunnable);
 
-    /**
-     * Set the notification appearance to be below the speed bump.
-     * @param below true if it is below.
-     */
-    public void setBelowSpeedBump(boolean below) {
-        NotificationIconContainerRefactor.assertInLegacyMode();
-    }
-
     public int getPinnedHeadsUpHeight() {
         return getIntrinsicHeight();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt
index 4f5a04f..b1e9032 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt
@@ -128,6 +128,24 @@
             { "removeTransientRow from row: childKey: $str1 -- containerKey: $str2" }
         )
     }
+
+    fun logResetAllContentAlphas(entry: NotificationEntry) {
+        notificationRenderBuffer.log(
+            TAG,
+            LogLevel.INFO,
+            { str1 = entry.logKey },
+            { "resetAllContentAlphas: $str1" }
+        )
+    }
+
+    fun logSkipResetAllContentAlphas(entry: NotificationEntry) {
+        notificationRenderBuffer.log(
+            TAG,
+            LogLevel.INFO,
+            { str1 = entry.logKey },
+            { "Skip resetAllContentAlphas: $str1" }
+        )
+    }
 }
 
 private const val TAG = "NotifRow"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationIconContainerRefactor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationIconContainerRefactor.kt
deleted file mode 100644
index a08af75..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationIconContainerRefactor.kt
+++ /dev/null
@@ -1,52 +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.statusbar.notification.shared
-
-import com.android.systemui.Flags
-import com.android.systemui.flags.FlagToken
-import com.android.systemui.flags.RefactorFlagUtils
-
-/** Helper for reading or using the NotificationIconContainer refactor flag state. */
-@Suppress("NOTHING_TO_INLINE")
-object NotificationIconContainerRefactor {
-    /** The aconfig flag name */
-    const val FLAG_NAME = Flags.FLAG_NOTIFICATIONS_ICON_CONTAINER_REFACTOR
-
-    /** A token used for dependency declaration */
-    val token: FlagToken
-        get() = FlagToken(FLAG_NAME, isEnabled)
-
-    /** Is the refactor enabled? */
-    @JvmStatic
-    inline val isEnabled
-        get() = Flags.notificationsIconContainerRefactor()
-
-    /**
-     * Called to ensure code is only run when the flag is enabled. This protects users from the
-     * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
-     * build to ensure that the refactor author catches issues in testing.
-     */
-    @JvmStatic
-    inline fun isUnexpectedlyInLegacyMode() =
-        RefactorFlagUtils.isUnexpectedlyInLegacyMode(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
-    inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
index 819527e..15dc115 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
@@ -21,9 +21,7 @@
 import com.android.systemui.statusbar.NotificationShelf
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerShelfViewBinder
 import com.android.systemui.statusbar.notification.row.ui.viewbinder.ActivatableNotificationViewBinder
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
 import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel
-import com.android.systemui.statusbar.phone.NotificationIconAreaController
 import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.launch
@@ -35,17 +33,10 @@
         viewModel: NotificationShelfViewModel,
         falsingManager: FalsingManager,
         nicBinder: NotificationIconContainerShelfViewBinder,
-        notificationIconAreaController: NotificationIconAreaController,
     ): Unit = coroutineScope {
         ActivatableNotificationViewBinder.bind(viewModel, shelf, falsingManager)
         shelf.apply {
-            traceSection("NotifShelf#bindShelfIcons") {
-                if (NotificationIconContainerRefactor.isEnabled) {
-                    launch { nicBinder.bind(shelfIcons) }
-                } else {
-                    notificationIconAreaController.setShelfIcons(shelfIcons)
-                }
-            }
+            traceSection("NotifShelf#bindShelfIcons") { launch { nicBinder.bind(shelfIcons) } }
             launch {
                 viewModel.canModifyColorOfNotifications.collect(::setCanModifyColorOfNotifications)
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
index 83de226..69c9a4b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
@@ -26,7 +26,6 @@
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
 
 /**
 * A state of an expandable view
@@ -157,11 +156,6 @@
             expandableView.setHideSensitive(
                     this.hideSensitive, false /* animated */, 0 /* delay */, 0 /* duration */);
 
-            // apply below shelf speed bump
-            if (!NotificationIconContainerRefactor.isEnabled()) {
-                expandableView.setBelowSpeedBump(this.belowSpeedBump);
-            }
-
             // apply clipping
             final float oldClipTopAmount = expandableView.getClipTopAmount();
             if (oldClipTopAmount != this.clipTopAmount) {
@@ -211,11 +205,6 @@
             abortAnimation(child, TAG_ANIMATOR_BOTTOM_INSET);
         }
 
-        // apply below the speed bump
-        if (!NotificationIconContainerRefactor.isEnabled()) {
-            expandableView.setBelowSpeedBump(this.belowSpeedBump);
-        }
-
         // start hiding sensitive animation
         expandableView.setHideSensitive(this.hideSensitive, animationFilter.animateHideSensitive,
                 properties.delay, properties.duration);
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 608fe95..41195aa 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
@@ -1426,6 +1426,7 @@
     }
 
     public void setHeadsUpBoundaries(int height, int bottomBarHeight) {
+        SceneContainerFlag.assertInLegacyMode();
         mView.setHeadsUpBoundaries(height, bottomBarHeight);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt
index 9b21fa9..5d37476 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt
@@ -18,13 +18,14 @@
 package com.android.systemui.statusbar.notification.stack.domain.interactor
 
 import android.content.Context
-import com.android.systemui.Flags.centralizedStatusBarHeightFix
 import com.android.systemui.common.ui.data.repository.ConfigurationRepository
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.shade.LargeScreenHeaderHelper
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.policy.SplitShadeStateController
 import dagger.Lazy
 import javax.inject.Inject
@@ -44,7 +45,8 @@
 constructor(
     configurationRepository: ConfigurationRepository,
     private val context: Context,
-    private val splitShadeStateController: SplitShadeStateController,
+    private val splitShadeStateController: Lazy<SplitShadeStateController>,
+    private val shadeInteractor: Lazy<ShadeInteractor>,
     keyguardInteractor: KeyguardInteractor,
     deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
     largeScreenHeaderHelperLazy: Lazy<LargeScreenHeaderHelper>,
@@ -57,16 +59,33 @@
     /** An internal modification was made to notifications */
     val notificationStackChanged = _notificationStackChanged.debounce(20L)
 
+    private val configurationChangeEvents =
+        configurationRepository.onAnyConfigurationChange.onStart { emit(Unit) }
+
+    /* Warning: Even though the value it emits only contains the split shade status, this flow must
+     * emit a value whenever the configuration *or* the split shade status changes. Adding a
+     * distinctUntilChanged() to this would cause configurationBasedDimensions to miss configuration
+     * updates that affect other resources, like margins or the large screen header flag.
+     */
+    private val dimensionsUpdateEventsWithShouldUseSplitShade: Flow<Boolean> =
+        if (SceneContainerFlag.isEnabled) {
+            combine(configurationChangeEvents, shadeInteractor.get().isShadeLayoutWide) {
+                _,
+                isShadeLayoutWide ->
+                isShadeLayoutWide
+            }
+        } else {
+            configurationChangeEvents.map {
+                splitShadeStateController.get().shouldUseSplitNotificationShade(context.resources)
+            }
+        }
+
     val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> =
-        configurationRepository.onAnyConfigurationChange
-            .onStart { emit(Unit) }
-            .map { _ ->
+        dimensionsUpdateEventsWithShouldUseSplitShade
+            .map { shouldUseSplitShade ->
                 with(context.resources) {
                     ConfigurationBasedDimensions(
-                        useSplitShade =
-                            splitShadeStateController.shouldUseSplitNotificationShade(
-                                context.resources
-                            ),
+                        useSplitShade = shouldUseSplitShade,
                         useLargeScreenHeader =
                             getBoolean(R.bool.config_use_large_screen_shade_header),
                         marginHorizontal =
@@ -75,11 +94,7 @@
                             getDimensionPixelSize(R.dimen.notification_panel_margin_bottom),
                         marginTop = getDimensionPixelSize(R.dimen.notification_panel_margin_top),
                         marginTopLargeScreen =
-                            if (centralizedStatusBarHeightFix()) {
-                                largeScreenHeaderHelperLazy.get().getLargeScreenHeaderHeight()
-                            } else {
-                                getDimensionPixelSize(R.dimen.large_screen_shade_header_height)
-                            },
+                            largeScreenHeaderHelperLazy.get().getLargeScreenHeaderHeight(),
                         keyguardSplitShadeTopMargin =
                             getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin),
                     )
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 5544f93..5572f8e 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
@@ -46,7 +46,6 @@
 import com.android.systemui.statusbar.notification.stack.ui.viewbinder.HideNotificationsBinder.bindHideList
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel
 import com.android.systemui.statusbar.notification.ui.viewbinder.HeadsUpNotificationViewBinder
-import com.android.systemui.statusbar.phone.NotificationIconAreaController
 import com.android.systemui.util.kotlin.awaitCancellationThenDispose
 import com.android.systemui.util.kotlin.getOrNull
 import com.android.systemui.util.ui.isAnimating
@@ -74,7 +73,6 @@
     private val configuration: ConfigurationState,
     private val falsingManager: FalsingManager,
     private val hunBinder: HeadsUpNotificationViewBinder,
-    private val iconAreaController: NotificationIconAreaController,
     private val loggerOptional: Optional<NotificationStatsLogger>,
     private val metricsLogger: MetricsLogger,
     private val nicBinder: NotificationIconContainerShelfViewBinder,
@@ -128,7 +126,6 @@
             viewModel.shelf,
             falsingManager,
             nicBinder,
-            iconAreaController,
         )
     }
 
@@ -183,12 +180,12 @@
                 launchNotificationSettings = { view ->
                     notificationActivityStarter
                         .get()
-                        .startHistoryIntent(view, /* showHistory = */ false)
+                        .startHistoryIntent(view, /* showHistory= */ false)
                 },
                 launchNotificationHistory = { view ->
                     notificationActivityStarter
                         .get()
-                        .startHistoryIntent(view, /* showHistory = */ true)
+                        .startHistoryIntent(view, /* showHistory= */ true)
                 },
             )
         launch {
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 fd08e89..a30b877 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
@@ -17,14 +17,14 @@
 package com.android.systemui.statusbar.notification.stack.ui.viewbinder
 
 import android.util.Log
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.repeatOnLifecycle
 import com.android.systemui.common.ui.ConfigurationState
 import com.android.systemui.common.ui.view.onLayoutChanged
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.lifecycle.WindowLifecycleState
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.lifecycle.viewModel
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationScrollViewModel
@@ -33,7 +33,6 @@
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.DisposableHandle
-import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.launch
@@ -46,7 +45,7 @@
     dumpManager: DumpManager,
     @Main private val mainImmediateDispatcher: CoroutineDispatcher,
     private val view: NotificationScrollView,
-    private val viewModel: NotificationScrollViewModel,
+    private val viewModelFactory: NotificationScrollViewModel.Factory,
     private val configuration: ConfigurationState,
 ) : FlowDumperImpl(dumpManager) {
 
@@ -61,38 +60,42 @@
     }
 
     fun bindWhileAttached(): DisposableHandle {
-        return view.asView().repeatWhenAttached(mainImmediateDispatcher) {
-            repeatOnLifecycle(Lifecycle.State.CREATED) { bind() }
-        }
+        return view.asView().repeatWhenAttached(mainImmediateDispatcher) { bind() }
     }
 
-    suspend fun bind() = coroutineScope {
-        launchAndDispose {
-            updateViewPosition()
-            view.asView().onLayoutChanged { updateViewPosition() }
-        }
+    suspend fun bind(): Nothing =
+        view.asView().viewModel(
+            minWindowLifecycleState = WindowLifecycleState.ATTACHED,
+            factory = viewModelFactory::create,
+        ) { viewModel ->
+            launchAndDispose {
+                updateViewPosition()
+                view.asView().onLayoutChanged { updateViewPosition() }
+            }
 
-        launch {
-            viewModel
-                .shadeScrimShape(cornerRadius = scrimRadius, viewLeftOffset = viewLeftOffset)
-                .collect { view.setScrimClippingShape(it) }
-        }
+            launch {
+                viewModel
+                    .shadeScrimShape(cornerRadius = scrimRadius, viewLeftOffset = viewLeftOffset)
+                    .collect { view.setScrimClippingShape(it) }
+            }
 
-        launch { viewModel.maxAlpha.collect { view.setMaxAlpha(it) } }
-        launch { viewModel.scrolledToTop.collect { view.setScrolledToTop(it) } }
-        launch { viewModel.expandFraction.collect { view.setExpandFraction(it.coerceIn(0f, 1f)) } }
-        launch { viewModel.isScrollable.collect { view.setScrollingEnabled(it) } }
-        launch { viewModel.isDozing.collect { isDozing -> view.setDozing(isDozing) } }
+            launch { viewModel.maxAlpha.collect { view.setMaxAlpha(it) } }
+            launch { viewModel.scrolledToTop.collect { view.setScrolledToTop(it) } }
+            launch {
+                viewModel.expandFraction.collect { view.setExpandFraction(it.coerceIn(0f, 1f)) }
+            }
+            launch { viewModel.isScrollable.collect { view.setScrollingEnabled(it) } }
+            launch { viewModel.isDozing.collect { isDozing -> view.setDozing(isDozing) } }
 
-        launchAndDispose {
-            view.setSyntheticScrollConsumer(viewModel.syntheticScrollConsumer)
-            view.setCurrentGestureOverscrollConsumer(viewModel.currentGestureOverscrollConsumer)
-            DisposableHandle {
-                view.setSyntheticScrollConsumer(null)
-                view.setCurrentGestureOverscrollConsumer(null)
+            launchAndDispose {
+                view.setSyntheticScrollConsumer(viewModel.syntheticScrollConsumer)
+                view.setCurrentGestureOverscrollConsumer(viewModel.currentGestureOverscrollConsumer)
+                DisposableHandle {
+                    view.setSyntheticScrollConsumer(null)
+                    view.setCurrentGestureOverscrollConsumer(null)
+                }
             }
         }
-    }
 
     /** flow of the scrim clipping radius */
     private val scrimRadius: Flow<Int>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index 0541550..aa1911e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -99,19 +99,20 @@
         disposables +=
             view.repeatWhenAttached(mainImmediateDispatcher) {
                 repeatOnLifecycle(Lifecycle.State.CREATED) {
-                    launch {
-                        // Only temporarily needed, until flexi notifs go live
-                        viewModel.shadeCollapseFadeIn.collect { fadeIn ->
-                            if (fadeIn) {
-                                android.animation.ValueAnimator.ofFloat(0f, 1f).apply {
-                                    duration = 250
-                                    addUpdateListener { animation ->
-                                        controller.setMaxAlphaForKeyguard(
-                                            animation.animatedFraction,
-                                            "SharedNotificationContainerVB (collapseFadeIn)"
-                                        )
+                    if (!SceneContainerFlag.isEnabled) {
+                        launch {
+                            viewModel.shadeCollapseFadeIn.collect { fadeIn ->
+                                if (fadeIn) {
+                                    android.animation.ValueAnimator.ofFloat(0f, 1f).apply {
+                                        duration = 250
+                                        addUpdateListener { animation ->
+                                            controller.setMaxAlphaForKeyguard(
+                                                animation.animatedFraction,
+                                                "SharedNotificationContainerVB (collapseFadeIn)"
+                                            )
+                                        }
+                                        start()
                                     }
-                                    start()
                                 }
                             }
                         }
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 2ba79a8..bfb624a 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
@@ -19,9 +19,9 @@
 
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.compose.animation.scene.SceneKey
-import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.lifecycle.SysUiViewModel
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.SceneFamilies
@@ -33,9 +33,11 @@
 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationTransitionThresholds.EXPANSION_FOR_DELAYED_STACK_FADE_IN
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationTransitionThresholds.EXPANSION_FOR_MAX_SCRIM_ALPHA
-import com.android.systemui.util.kotlin.FlowDumperImpl
+import com.android.systemui.util.kotlin.ActivatableFlowDumper
+import com.android.systemui.util.kotlin.ActivatableFlowDumperImpl
 import dagger.Lazy
-import javax.inject.Inject
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
@@ -43,9 +45,8 @@
 import kotlinx.coroutines.flow.map
 
 /** ViewModel which represents the state of the NSSL/Controller in the world of flexiglass */
-@SysUISingleton
 class NotificationScrollViewModel
-@Inject
+@AssistedInject
 constructor(
     dumpManager: DumpManager,
     stackAppearanceInteractor: NotificationStackAppearanceInteractor,
@@ -54,7 +55,14 @@
     // TODO(b/336364825) Remove Lazy when SceneContainerFlag is released -
     // while the flag is off, creating this object too early results in a crash
     keyguardInteractor: Lazy<KeyguardInteractor>,
-) : FlowDumperImpl(dumpManager) {
+) :
+    ActivatableFlowDumper by ActivatableFlowDumperImpl(dumpManager, "NotificationScrollViewModel"),
+    SysUiViewModel() {
+
+    override suspend fun onActivated() {
+        activateFlowDumper()
+    }
+
     /**
      * The expansion fraction of the notification stack. It should go from 0 to 1 when transitioning
      * from Gone to Shade scenes, and remain at 1 when in Lockscreen or Shade scenes and while
@@ -186,4 +194,9 @@
             keyguardInteractor.get().isDozing.dumpWhileCollecting("isDozing")
         }
     }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(): NotificationScrollViewModel
+    }
 }
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 e8a7840..d179888 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
@@ -22,7 +22,6 @@
 import com.android.systemui.flags.Flags
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
-import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel
 import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
@@ -30,7 +29,6 @@
 import com.android.systemui.util.kotlin.FlowDumperImpl
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.StateFlow
 
 /**
  * ViewModel used by the Notification placeholders inside the scene container to update the
@@ -43,7 +41,6 @@
     dumpManager: DumpManager,
     private val interactor: NotificationStackAppearanceInteractor,
     shadeInteractor: ShadeInteractor,
-    private val shadeSceneViewModel: ShadeSceneViewModel,
     private val headsUpNotificationInteractor: HeadsUpNotificationInteractor,
     featureFlags: FeatureFlagsClassic,
 ) : FlowDumperImpl(dumpManager) {
@@ -63,19 +60,11 @@
         interactor.setConstrainedAvailableSpace(height)
     }
 
-    /** Notifies that empty space on the notification scrim has been clicked. */
-    fun onEmptySpaceClicked() {
-        shadeSceneViewModel.onContentClicked()
-    }
-
     /** Sets the content alpha for the current state of the brightness mirror */
     fun setAlphaForBrightnessMirror(alpha: Float) {
         interactor.setAlphaForBrightnessMirror(alpha)
     }
 
-    /** Whether or not the notification scrim should be clickable. */
-    val isClickable: StateFlow<Boolean> = shadeSceneViewModel.isClickable
-
     /** True when a HUN is pinned or animating away. */
     val isHeadsUpOrAnimatingAway: Flow<Boolean> =
         headsUpNotificationInteractor.isHeadsUpOrAnimatingAway
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 3a3b05b..c4fbc37 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -30,6 +30,7 @@
 import static com.android.systemui.Flags.keyboardShortcutHelperRewrite;
 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.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;
@@ -209,7 +210,6 @@
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
@@ -237,10 +237,10 @@
 import com.android.wm.shell.startingsurface.SplashscreenContentDrawer;
 import com.android.wm.shell.startingsurface.StartingSurface;
 
-import dalvik.annotation.optimization.NeverCompile;
-
 import dagger.Lazy;
 
+import dalvik.annotation.optimization.NeverCompile;
+
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.Map;
@@ -546,7 +546,6 @@
     protected final BatteryController mBatteryController;
     private UiModeManager mUiModeManager;
     private LogMaker mStatusBarStateLog;
-    protected final NotificationIconAreaController mNotificationIconAreaController;
     @Nullable private View mAmbientIndicationContainer;
     private final SysuiColorExtractor mColorExtractor;
     private final ScreenLifecycle mScreenLifecycle;
@@ -683,7 +682,6 @@
             DemoModeController demoModeController,
             Lazy<NotificationShadeDepthController> notificationShadeDepthControllerLazy,
             StatusBarTouchableRegionManager statusBarTouchableRegionManager,
-            NotificationIconAreaController notificationIconAreaController,
             BrightnessSliderController.Factory brightnessSliderFactory,
             ScreenOffAnimationController screenOffAnimationController,
             WallpaperController wallpaperController,
@@ -787,7 +785,6 @@
         mUserInfoControllerImpl = userInfoControllerImpl;
         mIconPolicy = phoneStatusBarPolicy;
         mDemoModeController = demoModeController;
-        mNotificationIconAreaController = notificationIconAreaController;
         mBrightnessSliderFactory = brightnessSliderFactory;
         mWallpaperController = wallpaperController;
         mStatusBarSignalPolicy = statusBarSignalPolicy;
@@ -2273,7 +2270,10 @@
         // applying the dimming effect twice.
         mUiBgExecutor.execute(() -> {
             float dimAmount = 0f;
-            if (mWallpaperManager.lockScreenWallpaperExists()) {
+            // Note that access to WallpaperManager APIs should be guarded by a check into
+            // WallpaperManager#isWallpaperSupported. Form factors that do not use wallpaper
+            // may crash SysUI during improper access. ref: b/355307617
+            if (!mWallpaperSupported || mWallpaperManager.lockScreenWallpaperExists()) {
                 dimAmount = mWallpaperManager.getWallpaperDimAmount();
             }
             final float scrimDimAmount = dimAmount;
@@ -2353,8 +2353,14 @@
             } else if (mState == StatusBarState.KEYGUARD
                     && !mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()
                     && mStatusBarKeyguardViewManager.isSecure()) {
-                Log.d(TAG, "showBouncerOrLockScreenIfKeyguard, showingBouncer");
-                mStatusBarKeyguardViewManager.showBouncer(true /* scrimmed */);
+                if (!relockWithPowerButtonImmediately()) {
+                    Log.d(TAG, "showBouncerOrLockScreenIfKeyguard, showingBouncer");
+                    if (SceneContainerFlag.isEnabled()) {
+                        mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
+                    } else {
+                        mStatusBarKeyguardViewManager.showBouncer(true /* scrimmed */);
+                    }
+                }
             }
         }
     }
@@ -2652,9 +2658,6 @@
                 !mDozeServiceHost.isPulsing());
 
         mShadeSurface.setTouchAndAnimationDisabled(disabled);
-        if (!NotificationIconContainerRefactor.isEnabled()) {
-            mNotificationIconAreaController.setAnimationsEnabled(!disabled);
-        }
     }
 
     final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
@@ -2989,7 +2992,7 @@
                 @Override
                 public void onFalse() {
                     // Hides quick settings, bouncer, and quick-quick settings.
-                    mStatusBarKeyguardViewManager.reset(true);
+                    mStatusBarKeyguardViewManager.reset(true, /* isFalsingReset= */true);
                 }
             };
 
@@ -3053,9 +3056,6 @@
             }
             // TODO: Bring these out of CentralSurfaces.
             mUserInfoControllerImpl.onDensityOrFontScaleChanged();
-            if (!NotificationIconContainerRefactor.isEnabled()) {
-                mNotificationIconAreaController.onDensityOrFontScaleChanged(mContext);
-            }
         }
 
         @Override
@@ -3073,9 +3073,6 @@
             if (mAmbientIndicationContainer instanceof AutoReinflateContainer) {
                 ((AutoReinflateContainer) mAmbientIndicationContainer).inflateLayout();
             }
-            if (!NotificationIconContainerRefactor.isEnabled()) {
-                mNotificationIconAreaController.onThemeChanged();
-            }
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index ca1fb78b..ac10155 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -52,7 +52,6 @@
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -106,7 +105,6 @@
     private final NotificationWakeUpCoordinator mNotificationWakeUpCoordinator;
     private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
     private final AuthController mAuthController;
-    private final NotificationIconAreaController mNotificationIconAreaController;
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private final ShadeLockscreenInteractor mShadeLockscreenInteractor;
     private View mAmbientIndicationContainer;
@@ -129,7 +127,6 @@
             NotificationShadeWindowController notificationShadeWindowController,
             NotificationWakeUpCoordinator notificationWakeUpCoordinator,
             AuthController authController,
-            NotificationIconAreaController notificationIconAreaController,
             ShadeLockscreenInteractor shadeLockscreenInteractor,
             DozeInteractor dozeInteractor) {
         super();
@@ -149,7 +146,6 @@
         mNotificationShadeWindowController = notificationShadeWindowController;
         mNotificationWakeUpCoordinator = notificationWakeUpCoordinator;
         mAuthController = authController;
-        mNotificationIconAreaController = notificationIconAreaController;
         mShadeLockscreenInteractor = shadeLockscreenInteractor;
         mHeadsUpManager.addListener(mOnHeadsUpChangedListener);
         mDozeInteractor = dozeInteractor;
@@ -196,13 +192,8 @@
 
     void fireNotificationPulse(NotificationEntry entry) {
         Runnable pulseSuppressedListener = () -> {
-            if (NotificationIconContainerRefactor.isEnabled()) {
-                mHeadsUpManager.removeNotification(
-                        entry.getKey(), /* releaseImmediately= */ true, /* animate= */ false);
-            } else {
-                entry.setPulseSuppressed(true);
-                mNotificationIconAreaController.updateAodNotificationIcons();
-            }
+            mHeadsUpManager.removeNotification(
+                    entry.getKey(), /* releaseImmediately= */ true, /* animate= */ false);
         };
         Assert.isMainThread();
         for (Callback callback : mCallbacks) {
@@ -433,14 +424,12 @@
 
     @Override
     public void setDozeScreenBrightness(int brightness) {
-        mDozeLog.traceDozeScreenBrightness(brightness);
         mNotificationShadeWindowController.setDozeScreenBrightness(brightness);
     }
 
 
     @Override
     public void setDozeScreenBrightnessFloat(float brightness) {
-        mDozeLog.traceDozeScreenBrightnessFloat(brightness);
         mNotificationShadeWindowController.setDozeScreenBrightnessFloat(brightness);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index f99a81e..8f94c06 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -42,7 +42,6 @@
 import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
 import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentScope;
@@ -74,7 +73,6 @@
 
     private static final SourceType HEADS_UP = SourceType.from("HeadsUp");
     private static final SourceType PULSING = SourceType.from("Pulsing");
-    private final NotificationIconAreaController mNotificationIconAreaController;
     private final HeadsUpManager mHeadsUpManager;
     private final NotificationStackScrollLayoutController mStackScrollerController;
 
@@ -114,7 +112,6 @@
     @VisibleForTesting
     @Inject
     public HeadsUpAppearanceController(
-            NotificationIconAreaController notificationIconAreaController,
             HeadsUpManager headsUpManager,
             StatusBarStateController stateController,
             PhoneStatusBarTransitions phoneStatusBarTransitions,
@@ -132,7 +129,6 @@
             HeadsUpNotificationIconInteractor headsUpNotificationIconInteractor,
             @Named(OPERATOR_NAME_FRAME_VIEW) Optional<View> operatorNameViewOptional) {
         super(headsUpStatusBarView);
-        mNotificationIconAreaController = notificationIconAreaController;
         mNotificationRoundnessManager = notificationRoundnessManager;
         mHeadsUpManager = headsUpManager;
 
@@ -178,11 +174,8 @@
     @Override
     protected void onViewAttached() {
         mHeadsUpManager.addListener(this);
-        mView.setOnDrawingRectChangedListener(
-                () -> updateIsolatedIconLocation(true /* requireUpdate */));
-        if (NotificationIconContainerRefactor.isEnabled()) {
-            updateIsolatedIconLocation(true);
-        }
+        mView.setOnDrawingRectChangedListener(this::updateIsolatedIconLocation);
+        updateIsolatedIconLocation();
         mWakeUpCoordinator.addListener(this);
         getShadeHeadsUpTracker().addTrackingHeadsUpListener(mSetTrackingHeadsUp);
         getShadeHeadsUpTracker().setHeadsUpAppearanceController(this);
@@ -198,9 +191,7 @@
     protected void onViewDetached() {
         mHeadsUpManager.removeListener(this);
         mView.setOnDrawingRectChangedListener(null);
-        if (NotificationIconContainerRefactor.isEnabled()) {
-            mHeadsUpNotificationIconInteractor.setIsolatedIconLocation(null);
-        }
+        mHeadsUpNotificationIconInteractor.setIsolatedIconLocation(null);
         mWakeUpCoordinator.removeListener(this);
         getShadeHeadsUpTracker().removeTrackingHeadsUpListener(mSetTrackingHeadsUp);
         getShadeHeadsUpTracker().setHeadsUpAppearanceController(null);
@@ -208,14 +199,8 @@
         mDarkIconDispatcher.removeDarkReceiver(this);
     }
 
-    private void updateIsolatedIconLocation(boolean requireStateUpdate) {
-        if (NotificationIconContainerRefactor.isEnabled()) {
-            mHeadsUpNotificationIconInteractor
-                    .setIsolatedIconLocation(mView.getIconDrawingRect());
-        } else {
-            mNotificationIconAreaController.setIsolatedIconLocation(
-                    mView.getIconDrawingRect(), requireStateUpdate);
-        }
+    private void updateIsolatedIconLocation() {
+        mHeadsUpNotificationIconInteractor.setIsolatedIconLocation(mView.getIconDrawingRect());
     }
 
     @Override
@@ -251,14 +236,8 @@
                 setShown(true);
                 animateIsolation = !isExpanded();
             }
-            if (NotificationIconContainerRefactor.isEnabled()) {
-                mHeadsUpNotificationIconInteractor.setIsolatedIconNotificationKey(
-                        newEntry == null ? null : newEntry.getRepresentativeEntry().getKey());
-            } else {
-                updateIsolatedIconLocation(false /* requireUpdate */);
-                mNotificationIconAreaController.showIconIsolated(newEntry == null ? null
-                        : newEntry.getIcons().getStatusBarIcon(), animateIsolation);
-            }
+            mHeadsUpNotificationIconInteractor.setIsolatedIconNotificationKey(
+                    newEntry == null ? null : newEntry.getRepresentativeEntry().getKey());
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index 0adc1b0..013903a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.phone;
 
-import static com.android.systemui.Flags.centralizedStatusBarHeightFix;
 import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
 import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInScale;
 import static com.android.systemui.statusbar.notification.NotificationUtils.interpolate;
@@ -169,9 +168,7 @@
         mStatusViewBottomMargin =
                 res.getDimensionPixelSize(R.dimen.keyguard_status_view_bottom_margin);
         mSplitShadeTopNotificationsMargin =
-                centralizedStatusBarHeightFix()
-                        ? LargeScreenHeaderHelper.getLargeScreenHeaderHeight(context)
-                        : res.getDimensionPixelSize(R.dimen.large_screen_shade_header_height);
+                LargeScreenHeaderHelper.getLargeScreenHeaderHeight(context);
         mSplitShadeTargetTopMargin =
                 res.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin);
 
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 84e6018..c3da7fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.phone;
 
-import static com.android.systemui.Flags.centralizedStatusBarHeightFix;
 import static com.android.systemui.ScreenDecorations.DisplayCutoutView.boundsFromDirection;
 import static com.android.systemui.util.Utils.getStatusBarHeaderHeightKeyguard;
 
@@ -133,9 +132,6 @@
         mUserSwitcherContainer = findViewById(R.id.user_switcher_container);
         mIsPrivacyDotEnabled = mContext.getResources().getBoolean(R.bool.config_enablePrivacyDot);
         loadDimens();
-        if (!centralizedStatusBarHeightFix()) {
-            setGravity(Gravity.CENTER_VERTICAL);
-        }
     }
 
     /**
@@ -322,7 +318,7 @@
         final int minRight = (!isLayoutRtl() && mIsPrivacyDotEnabled)
                 ? Math.max(mMinDotWidth, mPadding.right) : mPadding.right;
 
-        int top = centralizedStatusBarHeightFix() ? waterfallTop + mPadding.top : waterfallTop;
+        int top = waterfallTop + mPadding.top;
         setPadding(minLeft, top, minRight, 0);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
deleted file mode 100644
index f84efbb..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
+++ /dev/null
@@ -1,700 +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.statusbar.phone;
-
-import static com.android.systemui.Flags.newAodTransition;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.os.Trace;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.FrameLayout;
-
-import androidx.annotation.ColorInt;
-import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
-import androidx.collection.ArrayMap;
-
-import com.android.app.animation.Interpolators;
-import com.android.internal.statusbar.StatusBarIcon;
-import com.android.internal.util.ContrastColorUtil;
-import com.android.settingslib.Utils;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.demomode.DemoMode;
-import com.android.systemui.demomode.DemoModeController;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.keyguard.MigrateClocksToBlueprint;
-import com.android.systemui.plugins.DarkIconDispatcher;
-import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.res.R;
-import com.android.systemui.statusbar.CrossFadeHelper;
-import com.android.systemui.statusbar.NotificationListener;
-import com.android.systemui.statusbar.NotificationMediaManager;
-import com.android.systemui.statusbar.StatusBarIconView;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.NotificationUtils;
-import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
-import com.android.systemui.statusbar.notification.collection.ListEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider;
-import com.android.systemui.statusbar.window.StatusBarWindowController;
-import com.android.wm.shell.bubbles.Bubbles;
-
-import org.jetbrains.annotations.NotNull;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Optional;
-import java.util.function.Function;
-
-import javax.inject.Inject;
-
-/**
- * A controller for the space in the status bar to the left of the system icons. This area is
- * normally reserved for notifications.
- */
-@SysUISingleton
-public class LegacyNotificationIconAreaControllerImpl implements
-        NotificationIconAreaController,
-        DarkReceiver,
-        StatusBarStateController.StateListener,
-        NotificationWakeUpCoordinator.WakeUpListener,
-        DemoMode {
-
-    private static final long AOD_ICONS_APPEAR_DURATION = 200;
-    @ColorInt
-    private static final int DEFAULT_AOD_ICON_COLOR = 0xffffffff;
-
-    private final ContrastColorUtil mContrastColorUtil;
-    private final Runnable mUpdateStatusBarIcons = this::updateStatusBarIcons;
-    private final StatusBarStateController mStatusBarStateController;
-    private final NotificationMediaManager mMediaManager;
-    private final NotificationWakeUpCoordinator mWakeUpCoordinator;
-    private final KeyguardBypassController mBypassController;
-    private final DozeParameters mDozeParameters;
-    private final SectionStyleProvider mSectionStyleProvider;
-    private final Optional<Bubbles> mBubblesOptional;
-    private final StatusBarWindowController mStatusBarWindowController;
-    private final ScreenOffAnimationController mScreenOffAnimationController;
-
-    private int mIconSize;
-    private int mIconHPadding;
-    private int mIconTint = Color.WHITE;
-
-    private List<ListEntry> mNotificationEntries = List.of();
-    protected View mNotificationIconArea;
-    private NotificationIconContainer mNotificationIcons;
-    private NotificationIconContainer mShelfIcons;
-    private NotificationIconContainer mAodIcons;
-    private final ArrayList<Rect> mTintAreas = new ArrayList<>();
-    private final Context mContext;
-    private int mAodIconAppearTranslation;
-
-    private boolean mAnimationsEnabled;
-    private int mAodIconTint;
-    private boolean mAodIconsVisible;
-    private boolean mShowLowPriority = true;
-
-    @VisibleForTesting
-    final NotificationListener.NotificationSettingsListener mSettingsListener =
-            new NotificationListener.NotificationSettingsListener() {
-                @Override
-                public void onStatusBarIconsBehaviorChanged(boolean hideSilentStatusIcons) {
-                    mShowLowPriority = !hideSilentStatusIcons;
-                    updateStatusBarIcons();
-                }
-            };
-
-    @Inject
-    public LegacyNotificationIconAreaControllerImpl(
-            Context context,
-            StatusBarStateController statusBarStateController,
-            NotificationWakeUpCoordinator wakeUpCoordinator,
-            KeyguardBypassController keyguardBypassController,
-            NotificationMediaManager notificationMediaManager,
-            NotificationListener notificationListener,
-            DozeParameters dozeParameters,
-            SectionStyleProvider sectionStyleProvider,
-            Optional<Bubbles> bubblesOptional,
-            DemoModeController demoModeController,
-            DarkIconDispatcher darkIconDispatcher,
-            FeatureFlags featureFlags,
-            StatusBarWindowController statusBarWindowController,
-            ScreenOffAnimationController screenOffAnimationController) {
-        mContrastColorUtil = ContrastColorUtil.getInstance(context);
-        mContext = context;
-        mStatusBarStateController = statusBarStateController;
-        mStatusBarStateController.addCallback(this);
-        mMediaManager = notificationMediaManager;
-        mDozeParameters = dozeParameters;
-        mSectionStyleProvider = sectionStyleProvider;
-        mWakeUpCoordinator = wakeUpCoordinator;
-        wakeUpCoordinator.addListener(this);
-        mBypassController = keyguardBypassController;
-        mBubblesOptional = bubblesOptional;
-        demoModeController.addCallback(this);
-        mStatusBarWindowController = statusBarWindowController;
-        mScreenOffAnimationController = screenOffAnimationController;
-        notificationListener.addNotificationSettingsListener(mSettingsListener);
-        initializeNotificationAreaViews(context);
-        reloadAodColor();
-        darkIconDispatcher.addDarkReceiver(this);
-    }
-
-    protected View inflateIconArea(LayoutInflater inflater) {
-        return inflater.inflate(R.layout.notification_icon_area, null);
-    }
-
-    /**
-     * Initializes the views that will represent the notification area.
-     */
-    protected void initializeNotificationAreaViews(Context context) {
-        reloadDimens(context);
-
-        LayoutInflater layoutInflater = LayoutInflater.from(context);
-        mNotificationIconArea = inflateIconArea(layoutInflater);
-        mNotificationIcons = mNotificationIconArea.findViewById(R.id.notificationIcons);
-    }
-
-    /**
-     * Called by the Keyguard*ViewController whose view contains the aod icons.
-     */
-    public void setupAodIcons(@NonNull NotificationIconContainer aodIcons) {
-        boolean changed = mAodIcons != null && aodIcons != mAodIcons;
-        if (changed) {
-            mAodIcons.setAnimationsEnabled(false);
-            mAodIcons.removeAllViews();
-        }
-        mAodIcons = aodIcons;
-        mAodIcons.setOnLockScreen(true);
-        updateAodIconsVisibility(false /* animate */, changed);
-        updateAnimations();
-        if (changed) {
-            updateAodNotificationIcons();
-        }
-        updateIconLayoutParams(mContext);
-    }
-
-    public void setShelfIcons(NotificationIconContainer icons) {
-        mShelfIcons = icons;
-    }
-
-    public void onDensityOrFontScaleChanged(@NotNull Context context) {
-        updateIconLayoutParams(context);
-    }
-
-    private void updateIconLayoutParams(Context context) {
-        reloadDimens(context);
-        final FrameLayout.LayoutParams params = generateIconLayoutParams();
-        for (int i = 0; i < mNotificationIcons.getChildCount(); i++) {
-            View child = mNotificationIcons.getChildAt(i);
-            child.setLayoutParams(params);
-        }
-        if (mShelfIcons != null) {
-            for (int i = 0; i < mShelfIcons.getChildCount(); i++) {
-                View child = mShelfIcons.getChildAt(i);
-                child.setLayoutParams(params);
-            }
-        }
-        if (mAodIcons != null) {
-            for (int i = 0; i < mAodIcons.getChildCount(); i++) {
-                View child = mAodIcons.getChildAt(i);
-                child.setLayoutParams(params);
-            }
-        }
-    }
-
-    @NonNull
-    private FrameLayout.LayoutParams generateIconLayoutParams() {
-        return new FrameLayout.LayoutParams(
-                mIconSize + 2 * mIconHPadding, mStatusBarWindowController.getStatusBarHeight());
-    }
-
-    private void reloadDimens(Context context) {
-        Resources res = context.getResources();
-        mIconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size_sp);
-        mIconHPadding = res.getDimensionPixelSize(R.dimen.status_bar_icon_horizontal_margin);
-        mAodIconAppearTranslation = res.getDimensionPixelSize(
-                R.dimen.shelf_appear_translation);
-    }
-
-    /**
-     * Returns the view that represents the notification area.
-     */
-    public View getNotificationInnerAreaView() {
-        return mNotificationIconArea;
-    }
-
-    /**
-     * See {@link com.android.systemui.statusbar.policy.DarkIconDispatcher#setIconsDarkArea}.
-     * Sets the color that should be used to tint any icons in the notification area.
-     *
-     * @param tintAreas the areas in which to tint the icons, specified in screen coordinates
-     * @param darkIntensity
-     */
-    public void onDarkChanged(ArrayList<Rect> tintAreas, float darkIntensity, int iconTint) {
-        mTintAreas.clear();
-        mTintAreas.addAll(tintAreas);
-
-        if (DarkIconDispatcher.isInAreas(tintAreas, mNotificationIconArea)) {
-            mIconTint = iconTint;
-        }
-
-        applyNotificationIconsTint();
-    }
-
-    protected boolean shouldShowNotificationIcon(NotificationEntry entry,
-            boolean showAmbient, boolean showLowPriority, boolean hideDismissed,
-            boolean hideRepliedMessages, boolean hideCurrentMedia, boolean hidePulsing) {
-        if (!showAmbient && mSectionStyleProvider.isMinimized(entry)) {
-            return false;
-        }
-        if (hideCurrentMedia && entry.getKey().equals(mMediaManager.getMediaNotificationKey())) {
-            return false;
-        }
-        if (!showLowPriority && mSectionStyleProvider.isSilent(entry)) {
-            return false;
-        }
-        if (entry.isRowDismissed() && hideDismissed) {
-            return false;
-        }
-        if (hideRepliedMessages && entry.isLastMessageFromReply()) {
-            return false;
-        }
-        // showAmbient == show in shade but not shelf
-        if (!showAmbient && entry.shouldSuppressStatusBar()) {
-            return false;
-        }
-        if (hidePulsing && entry.showingPulsing()
-                && (!mWakeUpCoordinator.getNotificationsFullyHidden()
-                        || !entry.isPulseSuppressed())) {
-            return false;
-        }
-        if (mBubblesOptional.isPresent()
-                && mBubblesOptional.get().isBubbleExpanded(entry.getKey())) {
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Updates the notifications with the given list of notifications to display.
-     */
-    public void updateNotificationIcons(List<ListEntry> entries) {
-        mNotificationEntries = entries;
-        updateNotificationIcons();
-    }
-
-    private void updateNotificationIcons() {
-        Trace.beginSection("NotificationIconAreaController.updateNotificationIcons");
-        updateStatusBarIcons();
-        updateShelfIcons();
-        updateAodNotificationIcons();
-
-        applyNotificationIconsTint();
-        Trace.endSection();
-    }
-
-    private void updateShelfIcons() {
-        if (mShelfIcons == null) {
-            return;
-        }
-        updateIconsForLayout(entry -> entry.getIcons().getShelfIcon(), mShelfIcons,
-                true /* showAmbient */,
-                true /* showLowPriority */,
-                false /* hideDismissed */,
-                false /* hideRepliedMessages */,
-                false /* hideCurrentMedia */,
-                false /* hidePulsing */);
-    }
-
-    public void updateStatusBarIcons() {
-        updateIconsForLayout(entry -> entry.getIcons().getStatusBarIcon(), mNotificationIcons,
-                false /* showAmbient */,
-                mShowLowPriority,
-                true /* hideDismissed */,
-                true /* hideRepliedMessages */,
-                false /* hideCurrentMedia */,
-                false /* hidePulsing */);
-    }
-
-    public void updateAodNotificationIcons() {
-        if (mAodIcons == null) {
-            return;
-        }
-        updateIconsForLayout(entry -> entry.getIcons().getAodIcon(), mAodIcons,
-                false /* showAmbient */,
-                true /* showLowPriority */,
-                true /* hideDismissed */,
-                true /* hideRepliedMessages */,
-                true /* hideCurrentMedia */,
-                mBypassController.getBypassEnabled() /* hidePulsing */);
-    }
-
-    @VisibleForTesting
-    boolean shouldShouldLowPriorityIcons() {
-        return mShowLowPriority;
-    }
-
-    /**
-     * Updates the notification icons for a host layout. This will ensure that the notification
-     * host layout will have the same icons like the ones in here.
-     * @param function A function to look up an icon view based on an entry
-     * @param hostLayout which layout should be updated
-     * @param showAmbient should ambient notification icons be shown
-     * @param showLowPriority should icons from silent notifications be shown
-     * @param hideDismissed should dismissed icons be hidden
-     * @param hideRepliedMessages should messages that have been replied to be hidden
-     * @param hidePulsing should pulsing notifications be hidden
-     */
-    private void updateIconsForLayout(Function<NotificationEntry, StatusBarIconView> function,
-            NotificationIconContainer hostLayout, boolean showAmbient, boolean showLowPriority,
-            boolean hideDismissed, boolean hideRepliedMessages, boolean hideCurrentMedia,
-            boolean hidePulsing) {
-        ArrayList<StatusBarIconView> toShow = new ArrayList<>(mNotificationEntries.size());
-        // Filter out ambient notifications and notification children.
-        for (int i = 0; i < mNotificationEntries.size(); i++) {
-            NotificationEntry entry = mNotificationEntries.get(i).getRepresentativeEntry();
-            if (entry != null && entry.getRow() != null) {
-                if (shouldShowNotificationIcon(entry, showAmbient, showLowPriority, hideDismissed,
-                        hideRepliedMessages, hideCurrentMedia, hidePulsing)) {
-                    StatusBarIconView iconView = function.apply(entry);
-                    if (iconView != null) {
-                        toShow.add(iconView);
-                    }
-                }
-            }
-        }
-
-        // In case we are changing the suppression of a group, the replacement shouldn't flicker
-        // and it should just be replaced instead. We therefore look for notifications that were
-        // just replaced by the child or vice-versa to suppress this.
-
-        ArrayMap<String, ArrayList<StatusBarIcon>> replacingIcons = new ArrayMap<>();
-        ArrayList<View> toRemove = new ArrayList<>();
-        for (int i = 0; i < hostLayout.getChildCount(); i++) {
-            View child = hostLayout.getChildAt(i);
-            if (!(child instanceof StatusBarIconView)) {
-                continue;
-            }
-            if (!toShow.contains(child)) {
-                boolean iconWasReplaced = false;
-                StatusBarIconView removedIcon = (StatusBarIconView) child;
-                String removedGroupKey = removedIcon.getNotification().getGroupKey();
-                for (int j = 0; j < toShow.size(); j++) {
-                    StatusBarIconView candidate = toShow.get(j);
-                    if (candidate.getSourceIcon().sameAs((removedIcon.getSourceIcon()))
-                            && candidate.getNotification().getGroupKey().equals(removedGroupKey)) {
-                        if (!iconWasReplaced) {
-                            iconWasReplaced = true;
-                        } else {
-                            iconWasReplaced = false;
-                            break;
-                        }
-                    }
-                }
-                if (iconWasReplaced) {
-                    ArrayList<StatusBarIcon> statusBarIcons = replacingIcons.get(removedGroupKey);
-                    if (statusBarIcons == null) {
-                        statusBarIcons = new ArrayList<>();
-                        replacingIcons.put(removedGroupKey, statusBarIcons);
-                    }
-                    statusBarIcons.add(removedIcon.getStatusBarIcon());
-                }
-                toRemove.add(removedIcon);
-            }
-        }
-        // removing all duplicates
-        ArrayList<String> duplicates = new ArrayList<>();
-        for (String key : replacingIcons.keySet()) {
-            ArrayList<StatusBarIcon> statusBarIcons = replacingIcons.get(key);
-            if (statusBarIcons.size() != 1) {
-                duplicates.add(key);
-            }
-        }
-        replacingIcons.removeAll(duplicates);
-        hostLayout.setReplacingIconsLegacy(replacingIcons);
-
-        final int toRemoveCount = toRemove.size();
-        for (int i = 0; i < toRemoveCount; i++) {
-            hostLayout.removeView(toRemove.get(i));
-        }
-
-        final FrameLayout.LayoutParams params = generateIconLayoutParams();
-        for (int i = 0; i < toShow.size(); i++) {
-            StatusBarIconView v = toShow.get(i);
-            // The view might still be transiently added if it was just removed and added again
-            hostLayout.removeTransientView(v);
-            if (v.getParent() == null) {
-                if (hideDismissed) {
-                    v.setOnDismissListener(mUpdateStatusBarIcons);
-                }
-                hostLayout.addView(v, i, params);
-            }
-        }
-
-        hostLayout.setChangingViewPositions(true);
-        // Re-sort notification icons
-        final int childCount = hostLayout.getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            View actual = hostLayout.getChildAt(i);
-            StatusBarIconView expected = toShow.get(i);
-            if (actual == expected) {
-                continue;
-            }
-            hostLayout.removeView(expected);
-            hostLayout.addView(expected, i);
-        }
-        hostLayout.setChangingViewPositions(false);
-        hostLayout.setReplacingIconsLegacy(null);
-    }
-
-    /**
-     * Applies {@link #mIconTint} to the notification icons.
-     */
-    private void applyNotificationIconsTint() {
-        for (int i = 0; i < mNotificationIcons.getChildCount(); i++) {
-            final StatusBarIconView iv = (StatusBarIconView) mNotificationIcons.getChildAt(i);
-            if (iv.getWidth() != 0) {
-                updateTintForIcon(iv, mIconTint);
-            } else {
-                iv.executeOnLayout(() -> updateTintForIcon(iv, mIconTint));
-            }
-        }
-
-        updateAodIconColors();
-    }
-
-    private void updateTintForIcon(StatusBarIconView v, int tint) {
-        boolean isPreL = Boolean.TRUE.equals(v.getTag(R.id.icon_is_pre_L));
-        int color = StatusBarIconView.NO_COLOR;
-        boolean colorize = !isPreL || NotificationUtils.isGrayscale(v, mContrastColorUtil);
-        if (colorize) {
-            color = DarkIconDispatcher.getTint(mTintAreas, v, tint);
-        }
-        v.setStaticDrawableColor(color);
-        v.setDecorColor(tint);
-    }
-
-    public void showIconIsolated(StatusBarIconView icon, boolean animated) {
-        mNotificationIcons.showIconIsolatedLegacy(icon, animated);
-    }
-
-    public void setIsolatedIconLocation(@NotNull Rect iconDrawingRect, boolean requireStateUpdate) {
-        mNotificationIcons.setIsolatedIconLocation(iconDrawingRect, requireStateUpdate);
-    }
-
-    @Override
-    public void onDozingChanged(boolean isDozing) {
-        if (mAodIcons == null) {
-            return;
-        }
-        boolean animate = mDozeParameters.getAlwaysOn()
-                && !mDozeParameters.getDisplayNeedsBlanking();
-        mAodIcons.setDozing(isDozing, animate, 0);
-    }
-
-    public void setAnimationsEnabled(boolean enabled) {
-        mAnimationsEnabled = enabled;
-        updateAnimations();
-    }
-
-    @Override
-    public void onStateChanged(int newState) {
-        updateAodIconsVisibility(false /* animate */, false /* force */);
-        updateAnimations();
-    }
-
-    private void updateAnimations() {
-        boolean inShade = mStatusBarStateController.getState() == StatusBarState.SHADE;
-        if (mAodIcons != null) {
-            mAodIcons.setAnimationsEnabled(mAnimationsEnabled && !inShade);
-        }
-        mNotificationIcons.setAnimationsEnabled(mAnimationsEnabled && inShade);
-    }
-
-    public void onThemeChanged() {
-        reloadAodColor();
-        updateAodIconColors();
-    }
-
-    public int getHeight() {
-        return mAodIcons == null ? 0 : mAodIcons.getHeight();
-    }
-
-    public void appearAodIcons() {
-        if (mAodIcons == null) {
-            return;
-        }
-        if (mScreenOffAnimationController.shouldAnimateAodIcons()) {
-            if (!MigrateClocksToBlueprint.isEnabled()) {
-                mAodIcons.setTranslationY(-mAodIconAppearTranslation);
-            }
-            mAodIcons.setAlpha(0);
-            animateInAodIconTranslation();
-            mAodIcons.animate()
-                    .alpha(1)
-                    .setInterpolator(Interpolators.LINEAR)
-                    .setDuration(AOD_ICONS_APPEAR_DURATION)
-                    .start();
-        } else {
-            mAodIcons.setAlpha(1.0f);
-            if (!MigrateClocksToBlueprint.isEnabled()) {
-                mAodIcons.setTranslationY(0);
-            }
-        }
-    }
-
-    private void animateInAodIconTranslation() {
-        if (!MigrateClocksToBlueprint.isEnabled()) {
-            mAodIcons.animate()
-                    .setInterpolator(Interpolators.DECELERATE_QUINT)
-                    .translationY(0)
-                    .setDuration(AOD_ICONS_APPEAR_DURATION)
-                    .start();
-        }
-    }
-
-    private void reloadAodColor() {
-        mAodIconTint = Utils.getColorAttrDefaultColor(mContext,
-                R.attr.wallpaperTextColor, DEFAULT_AOD_ICON_COLOR);
-    }
-
-    private void updateAodIconColors() {
-        if (mAodIcons != null) {
-            for (int i = 0; i < mAodIcons.getChildCount(); i++) {
-                final StatusBarIconView iv = (StatusBarIconView) mAodIcons.getChildAt(i);
-                if (iv.getWidth() != 0) {
-                    updateTintForIcon(iv, mAodIconTint);
-                } else {
-                    iv.executeOnLayout(() -> updateTintForIcon(iv, mAodIconTint));
-                }
-            }
-        }
-    }
-
-    @Override
-    public void onFullyHiddenChanged(boolean fullyHidden) {
-        boolean animate = true;
-        if (!mBypassController.getBypassEnabled()) {
-            animate = mDozeParameters.getAlwaysOn() && !mDozeParameters.getDisplayNeedsBlanking();
-            if (!newAodTransition()) {
-                // We only want the appear animations to happen when the notifications get fully
-                // hidden, since otherwise the unhide animation overlaps
-                animate &= fullyHidden;
-            }
-        }
-        updateAodIconsVisibility(animate, false /* force */);
-        updateAodNotificationIcons();
-        updateAodIconColors();
-    }
-
-    @Override
-    public void onPulseExpansionAmountChanged(boolean expandingChanged) {
-        if (expandingChanged) {
-            updateAodIconsVisibility(true /* animate */, false /* force */);
-        }
-    }
-
-    private void updateAodIconsVisibility(boolean animate, boolean forceUpdate) {
-        if (mAodIcons == null) {
-            return;
-        }
-        boolean visible = mBypassController.getBypassEnabled()
-                || mWakeUpCoordinator.getNotificationsFullyHidden();
-
-        // Hide the AOD icons if we're not in the KEYGUARD state unless the screen off animation is
-        // playing, in which case we want them to be visible since we're animating in the AOD UI and
-        // will be switching to KEYGUARD shortly.
-        if (mStatusBarStateController.getState() != StatusBarState.KEYGUARD
-                && !mScreenOffAnimationController.shouldShowAodIconsWhenShade()) {
-            visible = false;
-        }
-        if (visible && mWakeUpCoordinator.isPulseExpanding()
-                && !mBypassController.getBypassEnabled()) {
-            visible = false;
-        }
-        if (mAodIconsVisible != visible || forceUpdate) {
-            mAodIconsVisible = visible;
-            mAodIcons.animate().cancel();
-            if (animate) {
-                if (newAodTransition()) {
-                    // Let's make sure the icon are translated to 0, since we cancelled it above
-                    animateInAodIconTranslation();
-                    if (mAodIconsVisible) {
-                        CrossFadeHelper.fadeIn(mAodIcons);
-                    } else {
-                        CrossFadeHelper.fadeOut(mAodIcons);
-                    }
-                } else {
-                    boolean wasFullyInvisible = mAodIcons.getVisibility() != View.VISIBLE;
-                    if (mAodIconsVisible) {
-                        if (wasFullyInvisible) {
-                            // No fading here, let's just appear the icons instead!
-                            mAodIcons.setVisibility(View.VISIBLE);
-                            mAodIcons.setAlpha(1.0f);
-                            appearAodIcons();
-                        } else {
-                            // Let's make sure the icon are translated to 0, since we cancelled it
-                            // above
-                            animateInAodIconTranslation();
-                            // We were fading out, let's fade in instead
-                            CrossFadeHelper.fadeIn(mAodIcons);
-                        }
-                    } else {
-                        // Let's make sure the icon are translated to 0, since we cancelled it above
-                        animateInAodIconTranslation();
-                        CrossFadeHelper.fadeOut(mAodIcons);
-                    }
-                }
-            } else {
-                mAodIcons.setAlpha(1.0f);
-                if (!MigrateClocksToBlueprint.isEnabled()) {
-                    mAodIcons.setTranslationY(0);
-                }
-                mAodIcons.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
-            }
-        }
-    }
-
-    @Override
-    public List<String> demoCommands() {
-        ArrayList<String> commands = new ArrayList<>();
-        commands.add(DemoMode.COMMAND_NOTIFICATIONS);
-        return commands;
-    }
-
-    @Override
-    public void dispatchDemoCommand(String command, Bundle args) {
-        if (mNotificationIconArea != null) {
-            String visible = args.getString("visible");
-            int vis = "false".equals(visible) ? View.INVISIBLE : View.VISIBLE;
-            mNotificationIconArea.setVisibility(vis);
-        }
-    }
-
-    @Override
-    public void onDemoModeFinished() {
-        if (mNotificationIconArea != null) {
-            mNotificationIconArea.setVisibility(View.VISIBLE);
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.kt
deleted file mode 100644
index 4385a2e..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.kt
+++ /dev/null
@@ -1,45 +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.statusbar.phone
-
-import android.content.Context
-import android.graphics.Rect
-import android.view.View
-import com.android.systemui.statusbar.StatusBarIconView
-import com.android.systemui.statusbar.notification.collection.ListEntry
-
-/**
- * A controller for the space in the status bar to the left of the system icons. This area is
- * normally reserved for notifications.
- */
-interface NotificationIconAreaController {
-    /** Called by the Keyguard*ViewController whose view contains the aod icons. */
-    fun setupAodIcons(aodIcons: NotificationIconContainer?)
-    fun setShelfIcons(icons: NotificationIconContainer)
-    fun onDensityOrFontScaleChanged(context: Context)
-
-    /** Returns the view that represents the notification area. */
-    fun getNotificationInnerAreaView(): View?
-
-    /** Updates the notifications with the given list of notifications to display. */
-    fun updateNotificationIcons(entries: List<@JvmSuppressWildcards ListEntry>)
-    fun updateAodNotificationIcons()
-    fun showIconIsolated(icon: StatusBarIconView?, animated: Boolean)
-    fun setIsolatedIconLocation(iconDrawingRect: Rect, requireStateUpdate: Boolean)
-    fun setAnimationsEnabled(enabled: Boolean)
-    fun onThemeChanged()
-    fun getHeight(): Int
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerModule.kt
deleted file mode 100644
index ba69370..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerModule.kt
+++ /dev/null
@@ -1,36 +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.statusbar.phone
-
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconAreaControllerViewBinderWrapperImpl
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
-import dagger.Module
-import dagger.Provides
-import javax.inject.Provider
-
-@Module
-object NotificationIconAreaControllerModule {
-    @Provides
-    fun provideNotificationIconAreaControllerImpl(
-        legacyProvider: Provider<LegacyNotificationIconAreaControllerImpl>,
-        newProvider: Provider<NotificationIconAreaControllerViewBinderWrapperImpl>,
-    ): NotificationIconAreaController =
-        if (NotificationIconContainerRefactor.isEnabled) {
-            newProvider.get()
-        } else {
-            legacyProvider.get()
-        }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index 8e3d678..ecd62bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -26,7 +26,6 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Icon;
 import android.util.AttributeSet;
-import android.util.MathUtils;
 import android.util.Property;
 import android.view.ContextThemeWrapper;
 import android.view.View;
@@ -42,7 +41,6 @@
 import com.android.settingslib.Utils;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.StatusBarIconView;
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
 import com.android.systemui.statusbar.notification.stack.AnimationFilter;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.notification.stack.ViewState;
@@ -243,7 +241,7 @@
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         final int childCount = getChildCount();
-        final int maxVisibleIcons = getMaxVisibleIcons(childCount);
+        final int maxVisibleIcons = mMaxIcons;
         final int width = MeasureSpec.getSize(widthMeasureSpec);
         final int childWidthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.UNSPECIFIED);
         int totalWidth = (int) (getActualPaddingStart() + getActualPaddingEnd());
@@ -284,22 +282,13 @@
 
     @Override
     public String toString() {
-        if (NotificationIconContainerRefactor.isEnabled()) {
-            return super.toString()
-                    + " {"
-                    + " overrideIconColor=" + mOverrideIconColor
-                    + ", maxIcons=" + mMaxIcons
-                    + ", isStaticLayout=" + mIsStaticLayout
-                    + ", themedTextColorPrimary=#" + Integer.toHexString(mThemedTextColorPrimary)
-                    + " }";
-        } else {
-            return "NotificationIconContainer("
-                    + "dozing=" + mDozing + " onLockScreen=" + mOnLockScreen
-                    + " overrideIconColor=" + mOverrideIconColor
-                    + " speedBumpIndex=" + mSpeedBumpIndex
-                    + " themedTextColorPrimary=#" + Integer.toHexString(mThemedTextColorPrimary)
-                    + ')';
-        }
+        return super.toString()
+                + " {"
+                + " overrideIconColor=" + mOverrideIconColor
+                + ", maxIcons=" + mMaxIcons
+                + ", isStaticLayout=" + mIsStaticLayout
+                + ", themedTextColorPrimary=#" + Integer.toHexString(mThemedTextColorPrimary)
+                + " }";
     }
 
     @VisibleForTesting
@@ -349,13 +338,8 @@
             }
         }
         if (child instanceof StatusBarIconView) {
-            if (NotificationIconContainerRefactor.isEnabled()) {
-                if (!mChangingViewPositions) {
-                    ((StatusBarIconView) child).updateIconDimens();
-                }
-            } else {
+            if (!mChangingViewPositions) {
                 ((StatusBarIconView) child).updateIconDimens();
-                ((StatusBarIconView) child).setDozing(mDozing, false, 0);
             }
         }
     }
@@ -367,23 +351,11 @@
         StatusBarIconView iconView = (StatusBarIconView) child;
         Icon sourceIcon = iconView.getSourceIcon();
         String groupKey = iconView.getNotification().getGroupKey();
-        if (NotificationIconContainerRefactor.isEnabled()) {
-            if (mReplacingIcons == null) {
-                return false;
-            }
-            StatusBarIcon replacedIcon = mReplacingIcons.get(groupKey);
-            return replacedIcon != null && sourceIcon.sameAs(replacedIcon.icon);
-        } else {
-            if (mReplacingIconsLegacy == null) {
-                return false;
-            }
-            ArrayList<StatusBarIcon> statusBarIcons = mReplacingIconsLegacy.get(groupKey);
-            if (statusBarIcons != null) {
-                StatusBarIcon replacedIcon = statusBarIcons.get(0);
-                return sourceIcon.sameAs(replacedIcon.icon);
-            }
+        if (mReplacingIcons == null) {
             return false;
         }
+        StatusBarIcon replacedIcon = mReplacingIcons.get(groupKey);
+        return replacedIcon != null && sourceIcon.sameAs(replacedIcon.icon);
     }
 
     @Override
@@ -468,24 +440,14 @@
         if (numIcons == 0) {
             return 0f;
         }
-        final float contentWidth;
-        if (NotificationIconContainerRefactor.isEnabled()) {
-            contentWidth = mIconSize * numIcons;
-        } else {
-            contentWidth = mIconSize * MathUtils.min(numIcons, mMaxIconsOnLockscreen + 1);
-        }
+        final float contentWidth = mIconSize * numIcons;
         return getActualPaddingStart() + contentWidth + getActualPaddingEnd();
     }
 
     @VisibleForTesting
     boolean shouldForceOverflow(int i, int speedBumpIndex, float iconAppearAmount,
             int maxVisibleIcons) {
-        if (NotificationIconContainerRefactor.isEnabled()) {
-            return i >= maxVisibleIcons && iconAppearAmount > 0.0f;
-        } else {
-            return speedBumpIndex != -1 && i >= speedBumpIndex
-                    && iconAppearAmount > 0.0f || i >= maxVisibleIcons;
-        }
+        return i >= maxVisibleIcons && iconAppearAmount > 0.0f;
     }
 
     @VisibleForTesting
@@ -510,7 +472,7 @@
         float translationX = getActualPaddingStart();
         int firstOverflowIndex = -1;
         int childCount = getChildCount();
-        int maxVisibleIcons = getMaxVisibleIcons(childCount);
+        int maxVisibleIcons = mMaxIcons;
         float layoutEnd = getLayoutEnd();
         mVisualOverflowStart = 0;
         mFirstVisibleIconState = null;
@@ -592,27 +554,15 @@
     }
 
     private float getDrawingScale(View view) {
-        final boolean useIncreasedScale = NotificationIconContainerRefactor.isEnabled()
-                ? mUseIncreasedIconScale
-                : mOnLockScreen;
-        return useIncreasedScale && view instanceof StatusBarIconView
+        return mUseIncreasedIconScale && view instanceof StatusBarIconView
                 ? ((StatusBarIconView) view).getIconScaleIncreased()
                 : 1f;
     }
 
     public void setUseIncreasedIconScale(boolean useIncreasedIconScale) {
-        if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return;
         mUseIncreasedIconScale = useIncreasedIconScale;
     }
 
-    private int getMaxVisibleIcons(int childCount) {
-        if (NotificationIconContainerRefactor.isEnabled()) {
-            return mMaxIcons;
-        } else {
-            return mOnLockScreen ? mMaxIconsOnAod : mIsStaticLayout ? mMaxStaticIcons : childCount;
-        }
-    }
-
     private float getLayoutEnd() {
         return getActualWidth() - getActualPaddingEnd();
     }
@@ -689,50 +639,11 @@
         mChangingViewPositions = changingViewPositions;
     }
 
-    public void setDozing(boolean dozing, boolean animate, long delay) {
-        NotificationIconContainerRefactor.assertInLegacyMode();
-        setDozing(dozing, animate, delay, /* endRunnable= */ null);
-    }
-
-    private void setDozing(boolean dozing, boolean animate, long delay,
-            @Nullable Runnable endRunnable) {
-        NotificationIconContainerRefactor.assertInLegacyMode();
-        mDozing = dozing;
-        mDisallowNextAnimation |= !animate;
-        final int childCount = getChildCount();
-        // Track all the child invocations of setDozing, invoking the top-level endRunnable once
-        // they have all completed.
-        final Runnable onChildCompleted = endRunnable == null ? null : new Runnable() {
-            private int mPendingCallbacks = childCount;
-
-            @Override
-            public void run() {
-                if (--mPendingCallbacks == 0) {
-                    endRunnable.run();
-                }
-            }
-        };
-        for (int i = 0; i < childCount; i++) {
-            View view = getChildAt(i);
-            if (view instanceof StatusBarIconView) {
-                ((StatusBarIconView) view).setDozing(dozing, animate, delay, onChildCompleted);
-            } else if (onChildCompleted != null) {
-                onChildCompleted.run();
-            }
-        }
-    }
-
     public IconState getIconState(StatusBarIconView icon) {
         return mIconStates.get(icon);
     }
 
-    public void setSpeedBumpIndex(int speedBumpIndex) {
-        NotificationIconContainerRefactor.assertInLegacyMode();
-        mSpeedBumpIndex = speedBumpIndex;
-    }
-
     public void setMaxIconsAmount(int maxIcons) {
-        if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return;
         mMaxIcons = maxIcons;
     }
 
@@ -754,36 +665,18 @@
         mAnimationsEnabled = enabled;
     }
 
-    public void setReplacingIconsLegacy(ArrayMap<String, ArrayList<StatusBarIcon>> replacingIcons) {
-        NotificationIconContainerRefactor.assertInLegacyMode();
-        mReplacingIconsLegacy = replacingIcons;
-    }
-
     public void setReplacingIcons(ArrayMap<String, StatusBarIcon> replacingIcons) {
-        if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return;
         mReplacingIcons = replacingIcons;
     }
 
-    @Deprecated
-    public void showIconIsolatedLegacy(StatusBarIconView icon, boolean animated) {
-        NotificationIconContainerRefactor.assertInLegacyMode();
-        if (animated) {
-            mIsolatedIconForAnimation = icon != null ? icon : mIsolatedIcon;
-        }
-        mIsolatedIcon = icon;
-        updateState();
-    }
-
     public void showIconIsolatedAnimated(StatusBarIconView icon,
             @Nullable Runnable onAnimationEnd) {
-        if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return;
         mIsolatedIconForAnimation = icon != null ? icon : mIsolatedIcon;
         mIsolatedIconAnimationEndRunnable = onAnimationEnd;
         showIconIsolated(icon);
     }
 
     public void showIconIsolated(StatusBarIconView icon) {
-        if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return;
         mIsolatedIcon = icon;
         updateState();
     }
@@ -795,23 +688,7 @@
         }
     }
 
-    /**
-     * Set whether the device is on the lockscreen and which lockscreen mode the device is
-     * configured to. Depending on these values, the layout of the AOD icons change.
-     */
-    public void setOnLockScreen(boolean onLockScreen) {
-        NotificationIconContainerRefactor.assertInLegacyMode();
-        mOnLockScreen = onLockScreen;
-    }
-
-    @Deprecated
-    public void setInNotificationIconShelf(boolean inShelf) {
-        NotificationIconContainerRefactor.assertInLegacyMode();
-        mOverrideIconColor = inShelf;
-    }
-
     public void setOverrideIconColor(boolean override) {
-        if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return;
         mOverrideIconColor = override;
     }
 
@@ -922,14 +799,9 @@
                     }
                 }
                 icon.setVisibleState(visibleState, animationsAllowed);
-                if (NotificationIconContainerRefactor.isEnabled()) {
-                    if (mOverrideIconColor) {
-                        icon.setIconColor(mThemedTextColorPrimary,
-                                /* animate= */ needsCannedAnimation && animationsAllowed);
-                    }
-                } else {
-                    icon.setIconColor(mOverrideIconColor ? mThemedTextColorPrimary : iconColor,
-                            needsCannedAnimation && animationsAllowed);
+                if (mOverrideIconColor) {
+                    icon.setIconColor(mThemedTextColorPrimary,
+                            /* animate= */ needsCannedAnimation && animationsAllowed);
                 }
                 if (animate) {
                     animateTo(icon, animationProperties);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 8115c36..f178708 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -36,6 +36,7 @@
 
 import com.android.internal.policy.SystemBarUtils;
 import com.android.systemui.Dependency;
+import com.android.systemui.Flags;
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
@@ -217,8 +218,12 @@
 
     @Override
     public boolean onInterceptTouchEvent(MotionEvent event) {
-        mTouchEventHandler.onInterceptTouchEvent(event);
-        return super.onInterceptTouchEvent(event);
+        if (Flags.statusBarSwipeOverChip()) {
+            return mTouchEventHandler.onInterceptTouchEvent(event);
+        } else {
+            mTouchEventHandler.onInterceptTouchEvent(event);
+            return super.onInterceptTouchEvent(event);
+        }
     }
 
     public void updateResources() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index 5206e46..a818c05 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -23,9 +23,10 @@
 import android.view.View
 import android.view.ViewGroup
 import android.view.ViewTreeObserver
+import com.android.systemui.Flags
 import com.android.systemui.Gefingerpoken
 import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
+import com.android.systemui.flags.Flags.ENABLE_UNFOLD_STATUS_BAR_ANIMATIONS
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.ui.view.WindowRootView
@@ -83,22 +84,25 @@
         statusContainer.setOnHoverListener(
             statusOverlayHoverListenerFactory.createDarkAwareListener(statusContainer)
         )
-        statusContainer.setOnTouchListener(object : View.OnTouchListener {
-            override fun onTouch(v: View, event: MotionEvent): Boolean {
-                // We want to handle only mouse events here to avoid stealing finger touches from
-                // status bar which expands shade when swiped down on. We're using onTouchListener
-                // instead of onClickListener as the later will lead to isClickable being set to
-                // true and hence ALL touches always being intercepted. See [View.OnTouchEvent]
-                if (event.source == InputDevice.SOURCE_MOUSE) {
-                    if (event.action == MotionEvent.ACTION_UP) {
-                        v.performClick()
-                        shadeController.animateExpandShade()
+        statusContainer.setOnTouchListener(
+            object : View.OnTouchListener {
+                override fun onTouch(v: View, event: MotionEvent): Boolean {
+                    // We want to handle only mouse events here to avoid stealing finger touches
+                    // from status bar which expands shade when swiped down on. See b/326097469.
+                    // We're using onTouchListener instead of onClickListener as the later will lead
+                    // to isClickable being set to true and hence ALL touches always being
+                    // intercepted. See [View.OnTouchEvent]
+                    if (event.source == InputDevice.SOURCE_MOUSE) {
+                        if (event.action == MotionEvent.ACTION_UP) {
+                            v.performClick()
+                            shadeController.animateExpandShade()
+                        }
+                        return true
                     }
-                    return true
+                    return false
                 }
-                return false
             }
-        })
+        )
 
         progressProvider?.setReadyToHandleTransition(true)
         configurationController.addCallback(configurationListener)
@@ -180,8 +184,12 @@
 
     inner class PhoneStatusBarViewTouchHandler : Gefingerpoken {
         override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
-            onTouch(event)
-            return false
+            return if (Flags.statusBarSwipeOverChip()) {
+                shadeViewController.handleExternalInterceptTouch(event)
+            } else {
+                onTouch(event)
+                false
+            }
         }
 
         override fun onTouchEvent(event: MotionEvent): Boolean {
@@ -280,7 +288,7 @@
     ) {
         fun create(view: PhoneStatusBarView): PhoneStatusBarViewController {
             val statusBarMoveFromCenterAnimationController =
-                if (featureFlags.isEnabled(Flags.ENABLE_UNFOLD_STATUS_BAR_ANIMATIONS)) {
+                if (featureFlags.isEnabled(ENABLE_UNFOLD_STATUS_BAR_ANIMATIONS)) {
                     unfoldComponent.getOrNull()?.getStatusBarMoveFromCenterAnimationController()
                 } else {
                     null
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 2d775b7..5486abb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -708,7 +708,7 @@
      * Shows the notification keyguard or the bouncer depending on
      * {@link #needsFullscreenBouncer()}.
      */
-    protected void showBouncerOrKeyguard(boolean hideBouncerWhenShowing) {
+    protected void showBouncerOrKeyguard(boolean hideBouncerWhenShowing, boolean isFalsingReset) {
         boolean isDozing = mDozing;
         if (Flags.simPinRaceConditionOnRestart()) {
             KeyguardState toState = mKeyguardTransitionInteractor.getTransitionState().getValue()
@@ -734,8 +734,11 @@
                         mPrimaryBouncerInteractor.show(/* isScrimmed= */ true);
                     }
                 }
-            } else {
-                Log.e(TAG, "Attempted to show the sim bouncer when it is already showing.");
+            } else if (!isFalsingReset) {
+                // Falsing resets can cause this to flicker, so don't reset in this case
+                Log.i(TAG, "Sim bouncer is already showing, issuing a refresh");
+                mPrimaryBouncerInteractor.show(/* isScrimmed= */ true);
+
             }
         } else {
             mCentralSurfaces.showKeyguard();
@@ -957,6 +960,10 @@
 
     @Override
     public void reset(boolean hideBouncerWhenShowing) {
+        reset(hideBouncerWhenShowing, /* isFalsingReset= */false);
+    }
+
+    public void reset(boolean hideBouncerWhenShowing, boolean isFalsingReset) {
         if (mKeyguardStateController.isShowing() && !bouncerIsAnimatingAway()) {
             final boolean isOccluded = mKeyguardStateController.isOccluded();
             // Hide quick settings.
@@ -965,10 +972,14 @@
             if (isOccluded && !mDozing) {
                 mCentralSurfaces.hideKeyguard();
                 if (hideBouncerWhenShowing || needsFullscreenBouncer()) {
-                    hideBouncer(false /* destroyView */);
+                    // We're removing "reset" in the refactor - bouncer will be hidden by the root
+                    // cause of the "reset" calls.
+                    if (!KeyguardWmStateRefactor.isEnabled()) {
+                        hideBouncer(false /* destroyView */);
+                    }
                 }
             } else {
-                showBouncerOrKeyguard(hideBouncerWhenShowing);
+                showBouncerOrKeyguard(hideBouncerWhenShowing, isFalsingReset);
             }
             if (hideBouncerWhenShowing) {
                 hideAlternateBouncer(true);
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 07c190d..5be4ba2 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
@@ -59,8 +59,6 @@
 import com.android.systemui.statusbar.events.SystemStatusAnimationCallback;
 import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder;
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
-import com.android.systemui.statusbar.phone.NotificationIconAreaController;
 import com.android.systemui.statusbar.phone.NotificationIconContainer;
 import com.android.systemui.statusbar.phone.PhoneStatusBarView;
 import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
@@ -140,7 +138,6 @@
     private final OngoingCallController mOngoingCallController;
     private final SystemStatusAnimationScheduler mAnimationScheduler;
     private final StatusBarLocationPublisher mLocationPublisher;
-    private final NotificationIconAreaController mNotificationIconAreaController;
     private final ShadeExpansionStateManager mShadeExpansionStateManager;
     private final StatusBarIconController mStatusBarIconController;
     private final CarrierConfigTracker mCarrierConfigTracker;
@@ -237,7 +234,6 @@
             OngoingCallController ongoingCallController,
             SystemStatusAnimationScheduler animationScheduler,
             StatusBarLocationPublisher locationPublisher,
-            NotificationIconAreaController notificationIconAreaController,
             ShadeExpansionStateManager shadeExpansionStateManager,
             StatusBarIconController statusBarIconController,
             DarkIconManager.Factory darkIconManagerFactory,
@@ -262,7 +258,6 @@
         mOngoingCallController = ongoingCallController;
         mAnimationScheduler = animationScheduler;
         mLocationPublisher = locationPublisher;
-        mNotificationIconAreaController = notificationIconAreaController;
         mShadeExpansionStateManager = shadeExpansionStateManager;
         mStatusBarIconController = statusBarIconController;
         mCollapsedStatusBarViewModel = collapsedStatusBarViewModel;
@@ -313,18 +308,14 @@
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         mStatusBarWindowStateController.addListener(mStatusBarWindowStateListener);
-        if (NotificationIconContainerRefactor.isEnabled()) {
-            mDemoModeController.addCallback(mDemoModeCallback);
-        }
+        mDemoModeController.addCallback(mDemoModeCallback);
     }
 
     @Override
     public void onDestroy() {
         super.onDestroy();
         mStatusBarWindowStateController.removeListener(mStatusBarWindowStateListener);
-        if (NotificationIconContainerRefactor.isEnabled()) {
-            mDemoModeController.removeCallback(mDemoModeCallback);
-        }
+        mDemoModeController.removeCallback(mDemoModeCallback);
     }
 
     @Override
@@ -471,11 +462,9 @@
             mStartableStates.put(startable, Startable.State.STOPPED);
         }
         mDumpManager.unregisterDumpable(getClass().getSimpleName());
-        if (NotificationIconContainerRefactor.isEnabled()) {
-            if (mNicBindingDisposable != null) {
-                mNicBindingDisposable.dispose();
-                mNicBindingDisposable = null;
-            }
+        if (mNicBindingDisposable != null) {
+            mNicBindingDisposable.dispose();
+            mNicBindingDisposable = null;
         }
     }
 
@@ -483,22 +472,12 @@
     public void initNotificationIconArea() {
         Trace.beginSection("CollapsedStatusBarFragment#initNotifIconArea");
         ViewGroup notificationIconArea = mStatusBar.requireViewById(R.id.notification_icon_area);
-        if (NotificationIconContainerRefactor.isEnabled()) {
-            LayoutInflater.from(getContext())
-                    .inflate(R.layout.notification_icon_area, notificationIconArea, true);
-            NotificationIconContainer notificationIcons =
-                    notificationIconArea.requireViewById(R.id.notificationIcons);
-            mNotificationIconAreaInner = notificationIcons;
-            mNicBindingDisposable = mNicViewBinder.bindWhileAttached(notificationIcons);
-        } else {
-            mNotificationIconAreaInner =
-                    mNotificationIconAreaController.getNotificationInnerAreaView();
-            if (mNotificationIconAreaInner.getParent() != null) {
-                ((ViewGroup) mNotificationIconAreaInner.getParent())
-                        .removeView(mNotificationIconAreaInner);
-            }
-            notificationIconArea.addView(mNotificationIconAreaInner);
-        }
+        LayoutInflater.from(getContext())
+                .inflate(R.layout.notification_icon_area, notificationIconArea, true);
+        NotificationIconContainer notificationIcons =
+                notificationIconArea.requireViewById(R.id.notificationIcons);
+        mNotificationIconAreaInner = notificationIcons;
+        mNicBindingDisposable = mNicViewBinder.bindWhileAttached(notificationIcons);
 
         updateNotificationIconAreaAndOngoingActivityChip(/* animate= */ false);
         Trace.endSection();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index 4368239..bd6a1c0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -227,6 +227,15 @@
                 callNotificationInfo
                     // This shouldn't happen, but protect against it in case
                     ?: return OngoingCallModel.NoCall
+            logger.log(
+                TAG,
+                LogLevel.DEBUG,
+                {
+                    bool1 = Flags.statusBarCallChipNotificationIcon()
+                    bool2 = currentInfo.notificationIconView != null
+                },
+                { "Creating OngoingCallModel.InCall. notifIconFlag=$bool1 hasIcon=$bool2" }
+            )
             val icon =
                 if (Flags.statusBarCallChipNotificationIcon()) {
                     currentInfo.notificationIconView
@@ -257,6 +266,7 @@
 
     private fun updateInfoFromNotifModel(notifModel: ActiveNotificationModel?) {
         if (notifModel == null) {
+            logger.log(TAG, LogLevel.DEBUG, {}, { "NotifInteractorCallModel: null" })
             removeChip()
         } else if (notifModel.callType != CallType.Ongoing) {
             logger.log(
@@ -267,6 +277,18 @@
             )
             removeChip()
         } else {
+            logger.log(
+                TAG,
+                LogLevel.DEBUG,
+                {
+                    str1 = notifModel.key
+                    long1 = notifModel.whenTime
+                    str1 = notifModel.callType.name
+                    bool1 = notifModel.statusBarChipIconView != null
+                },
+                { "NotifInteractorCallModel: key=$str1 when=$long1 callType=$str2 hasIcon=$bool1" }
+            )
+
             val newOngoingCallInfo =
                 CallNotificationInfo(
                     notifModel.key,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
index 2e54972..9cbfc44 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
@@ -198,7 +198,8 @@
 
     fun logServiceProvidersUpdatedBroadcast(intent: Intent) {
         val showSpn = intent.getBooleanExtra(TelephonyManager.EXTRA_SHOW_SPN, false)
-        val spn = intent.getStringExtra(TelephonyManager.EXTRA_DATA_SPN)
+        val spn = intent.getStringExtra(TelephonyManager.EXTRA_SPN)
+        val dataSpn = intent.getStringExtra(TelephonyManager.EXTRA_DATA_SPN)
         val showPlmn = intent.getBooleanExtra(TelephonyManager.EXTRA_SHOW_PLMN, false)
         val plmn = intent.getStringExtra(TelephonyManager.EXTRA_PLMN)
 
@@ -208,12 +209,13 @@
             {
                 bool1 = showSpn
                 str1 = spn
+                str2 = dataSpn
                 bool2 = showPlmn
-                str2 = plmn
+                str3 = plmn
             },
             {
                 "Intent: ACTION_SERVICE_PROVIDERS_UPDATED." +
-                    " showSpn=$bool1 spn=$str1 showPlmn=$bool2 plmn=$str2"
+                    " showSpn=$bool1 spn=$str1 dataSpn=$str2 showPlmn=$bool2 plmn=$str3"
             }
         )
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt
index 99ed2d9..85bbe7e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt
@@ -21,6 +21,8 @@
 import android.telephony.TelephonyManager.EXTRA_PLMN
 import android.telephony.TelephonyManager.EXTRA_SHOW_PLMN
 import android.telephony.TelephonyManager.EXTRA_SHOW_SPN
+import android.telephony.TelephonyManager.EXTRA_SPN
+import com.android.systemui.Flags.statusBarSwitchToSpnFromDataSpn
 import com.android.systemui.log.table.Diffable
 import com.android.systemui.log.table.TableRowLogger
 
@@ -96,7 +98,13 @@
 
 fun Intent.toNetworkNameModel(separator: String): NetworkNameModel? {
     val showSpn = getBooleanExtra(EXTRA_SHOW_SPN, false)
-    val spn = getStringExtra(EXTRA_DATA_SPN)
+    val spn =
+        if (statusBarSwitchToSpnFromDataSpn()) {
+            getStringExtra(EXTRA_SPN)
+        } else {
+            getStringExtra(EXTRA_DATA_SPN)
+        }
+
     val showPlmn = getBooleanExtra(EXTRA_SHOW_PLMN, false)
     val plmn = getStringExtra(EXTRA_PLMN)
 
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 b7531b0..44b692f 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
@@ -131,7 +131,7 @@
 
         val on = context.resources.getString(R.string.zen_mode_on)
         val off = context.resources.getString(R.string.zen_mode_off)
-        return mode.rule.triggerDescription ?: if (mode.isActive) on else off
+        return mode.getDynamicDescription(context) ?: if (mode.isActive) on else off
     }
 
     private fun makeZenModeDialog(): Dialog {
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/TouchpadKeyboardTutorialModule.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/TouchpadKeyboardTutorialModule.kt
deleted file mode 100644
index 8ba8db4..0000000
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/TouchpadKeyboardTutorialModule.kt
+++ /dev/null
@@ -1,33 +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.touchpad.tutorial
-
-import android.app.Activity
-import com.android.systemui.touchpad.tutorial.ui.view.TouchpadTutorialActivity
-import dagger.Binds
-import dagger.Module
-import dagger.multibindings.ClassKey
-import dagger.multibindings.IntoMap
-
-@Module
-interface TouchpadKeyboardTutorialModule {
-
-    @Binds
-    @IntoMap
-    @ClassKey(TouchpadTutorialActivity::class)
-    fun activity(impl: TouchpadTutorialActivity): Activity
-}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/TouchpadTutorialModule.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/TouchpadTutorialModule.kt
new file mode 100644
index 0000000..238e8a1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/TouchpadTutorialModule.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.touchpad.tutorial
+
+import android.app.Activity
+import androidx.compose.runtime.Composable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.inputdevice.tutorial.TouchpadTutorialScreensProvider
+import com.android.systemui.model.SysUiState
+import com.android.systemui.settings.DisplayTracker
+import com.android.systemui.touchpad.tutorial.domain.interactor.TouchpadGesturesInteractor
+import com.android.systemui.touchpad.tutorial.ui.composable.BackGestureTutorialScreen
+import com.android.systemui.touchpad.tutorial.ui.composable.HomeGestureTutorialScreen
+import com.android.systemui.touchpad.tutorial.ui.view.TouchpadTutorialActivity
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import kotlinx.coroutines.CoroutineScope
+
+@Module
+interface TouchpadTutorialModule {
+
+    @Binds
+    @IntoMap
+    @ClassKey(TouchpadTutorialActivity::class)
+    fun activity(impl: TouchpadTutorialActivity): Activity
+
+    companion object {
+        @Provides
+        fun touchpadScreensProvider(): TouchpadTutorialScreensProvider {
+            return ScreensProvider
+        }
+
+        @SysUISingleton
+        @Provides
+        fun touchpadGesturesInteractor(
+            sysUiState: SysUiState,
+            displayTracker: DisplayTracker,
+            @Background backgroundScope: CoroutineScope
+        ): TouchpadGesturesInteractor {
+            return TouchpadGesturesInteractor(sysUiState, displayTracker, backgroundScope)
+        }
+    }
+}
+
+private object ScreensProvider : TouchpadTutorialScreensProvider {
+    @Composable
+    override fun BackGesture(onDoneButtonClicked: () -> Unit, onBack: () -> Unit) {
+        BackGestureTutorialScreen(onDoneButtonClicked, onBack)
+    }
+
+    @Composable
+    override fun HomeGesture(onDoneButtonClicked: () -> Unit, onBack: () -> Unit) {
+        HomeGestureTutorialScreen(onDoneButtonClicked, onBack)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/domain/interactor/TouchpadGesturesInteractor.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/domain/interactor/TouchpadGesturesInteractor.kt
index b6c2ae7..df95232 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/domain/interactor/TouchpadGesturesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/domain/interactor/TouchpadGesturesInteractor.kt
@@ -16,22 +16,16 @@
 
 package com.android.systemui.touchpad.tutorial.domain.interactor
 
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.model.SysUiState
 import com.android.systemui.settings.DisplayTracker
 import com.android.systemui.shared.system.QuickStepContract
-import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
 
-@SysUISingleton
-class TouchpadGesturesInteractor
-@Inject
-constructor(
+class TouchpadGesturesInteractor(
     private val sysUiState: SysUiState,
     private val displayTracker: DisplayTracker,
-    @Background private val backgroundScope: CoroutineScope
+    private val backgroundScope: CoroutineScope
 ) {
     fun disableGestures() {
         setGesturesState(disabled = true)
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 1b00ae2..1c8041f 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
@@ -21,6 +21,8 @@
 import androidx.compose.runtime.remember
 import com.airbnb.lottie.compose.rememberLottieDynamicProperties
 import com.android.compose.theme.LocalAndroidColorScheme
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialScreenConfig
+import com.android.systemui.inputdevice.tutorial.ui.composable.rememberColorFilterProperty
 import com.android.systemui.res.R
 import com.android.systemui.touchpad.tutorial.ui.gesture.BackGestureMonitor
 import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState
@@ -38,8 +40,8 @@
                 TutorialScreenConfig.Strings(
                     titleResId = R.string.touchpad_back_gesture_action_title,
                     bodyResId = R.string.touchpad_back_gesture_guidance,
-                    titleSuccessResId = R.string.touchpad_tutorial_gesture_done,
-                    bodySuccessResId = R.string.touchpad_back_gesture_finished
+                    titleSuccessResId = R.string.touchpad_back_gesture_success_title,
+                    bodySuccessResId = R.string.touchpad_back_gesture_success_body
                 ),
             animations =
                 TutorialScreenConfig.Animations(
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt
index 416c562..57d7c84 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt
@@ -16,57 +16,21 @@
 
 package com.android.systemui.touchpad.tutorial.ui.composable
 
-import android.graphics.ColorFilter
-import android.graphics.PorterDuff
-import android.graphics.PorterDuffColorFilter
 import androidx.activity.compose.BackHandler
-import androidx.annotation.RawRes
-import androidx.annotation.StringRes
-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.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.BoxScope
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.width
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.drawBehind
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.toArgb
 import androidx.compose.ui.input.pointer.pointerInteropFilter
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.unit.dp
-import com.airbnb.lottie.LottieProperty
-import com.airbnb.lottie.compose.LottieAnimation
-import com.airbnb.lottie.compose.LottieCompositionSpec
-import com.airbnb.lottie.compose.LottieConstants
-import com.airbnb.lottie.compose.LottieDynamicProperties
-import com.airbnb.lottie.compose.LottieDynamicProperty
-import com.airbnb.lottie.compose.animateLottieCompositionAsState
-import com.airbnb.lottie.compose.rememberLottieComposition
-import com.airbnb.lottie.compose.rememberLottieDynamicProperty
+import com.android.systemui.inputdevice.tutorial.ui.composable.ActionTutorialContent
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialScreenConfig
 import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState
 import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.FINISHED
 import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.IN_PROGRESS
@@ -81,6 +45,14 @@
     ): TouchpadGestureMonitor
 }
 
+fun GestureState.toTutorialActionState(): TutorialActionState {
+    return when (this) {
+        NOT_STARTED -> TutorialActionState.NOT_STARTED
+        IN_PROGRESS -> TutorialActionState.IN_PROGRESS
+        FINISHED -> TutorialActionState.FINISHED
+    }
+}
+
 @Composable
 fun GestureTutorialScreen(
     screenConfig: TutorialScreenConfig,
@@ -104,7 +76,11 @@
             )
         }
     TouchpadGesturesHandlingBox(gestureHandler, gestureState) {
-        GestureTutorialContent(gestureState, onDoneButtonClicked, screenConfig)
+        ActionTutorialContent(
+            gestureState.toTutorialActionState(),
+            onDoneButtonClicked,
+            screenConfig
+        )
     }
 }
 
@@ -135,165 +111,3 @@
         content()
     }
 }
-
-@Composable
-private fun GestureTutorialContent(
-    gestureState: GestureState,
-    onDoneButtonClicked: () -> Unit,
-    config: TutorialScreenConfig
-) {
-    val animatedColor by
-        animateColorAsState(
-            targetValue =
-                if (gestureState == 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) }
-                .padding(start = 48.dp, top = 124.dp, end = 48.dp, bottom = 48.dp)
-    ) {
-        Row(modifier = Modifier.fillMaxWidth().weight(1f)) {
-            TutorialDescription(
-                titleTextId =
-                    if (gestureState == FINISHED) config.strings.titleSuccessResId
-                    else config.strings.titleResId,
-                titleColor = config.colors.title,
-                bodyTextId =
-                    if (gestureState == FINISHED) config.strings.bodySuccessResId
-                    else config.strings.bodyResId,
-                modifier = Modifier.weight(1f)
-            )
-            Spacer(modifier = Modifier.width(76.dp))
-            TutorialAnimation(
-                gestureState,
-                config,
-                modifier = Modifier.weight(1f).padding(top = 8.dp)
-            )
-        }
-        DoneButton(onDoneButtonClicked = onDoneButtonClicked)
-    }
-}
-
-@Composable
-fun TutorialDescription(
-    @StringRes titleTextId: Int,
-    titleColor: Color,
-    @StringRes bodyTextId: Int,
-    modifier: Modifier = Modifier
-) {
-    Column(verticalArrangement = Arrangement.Top, modifier = modifier) {
-        Text(
-            text = stringResource(id = titleTextId),
-            style = MaterialTheme.typography.displayLarge,
-            color = titleColor
-        )
-        Spacer(modifier = Modifier.height(16.dp))
-        Text(
-            text = stringResource(id = bodyTextId),
-            style = MaterialTheme.typography.bodyLarge,
-            color = Color.White
-        )
-    }
-}
-
-@Composable
-fun TutorialAnimation(
-    gestureState: GestureState,
-    config: TutorialScreenConfig,
-    modifier: Modifier = Modifier
-) {
-    Box(modifier = modifier.fillMaxWidth()) {
-        AnimatedContent(
-            targetState = gestureState,
-            transitionSpec = {
-                if (initialState == NOT_STARTED && targetState == IN_PROGRESS) {
-                    val transitionDurationMillis = 150
-                    fadeIn(
-                        animationSpec = tween(transitionDurationMillis, easing = LinearEasing)
-                    ) togetherWith
-                        fadeOut(animationSpec = snap(delayMillis = transitionDurationMillis))
-                } else {
-                    // empty transition works because all remaining transitions are from IN_PROGRESS
-                    // state which shares initial animation frame with both FINISHED and NOT_STARTED
-                    EnterTransition.None togetherWith ExitTransition.None
-                }
-            }
-        ) { gestureState ->
-            when (gestureState) {
-                NOT_STARTED ->
-                    EducationAnimation(
-                        config.animations.educationResId,
-                        config.colors.animationColors
-                    )
-                IN_PROGRESS ->
-                    FrozenSuccessAnimation(
-                        config.animations.successResId,
-                        config.colors.animationColors
-                    )
-                FINISHED ->
-                    SuccessAnimation(config.animations.successResId, config.colors.animationColors)
-            }
-        }
-    }
-}
-
-@Composable
-private fun FrozenSuccessAnimation(
-    @RawRes successAnimationId: Int,
-    animationProperties: LottieDynamicProperties
-) {
-    val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(successAnimationId))
-    LottieAnimation(
-        composition = composition,
-        progress = { 0f }, // animation should freeze on 1st frame
-        dynamicProperties = animationProperties,
-    )
-}
-
-@Composable
-private fun EducationAnimation(
-    @RawRes educationAnimationId: Int,
-    animationProperties: LottieDynamicProperties
-) {
-    val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(educationAnimationId))
-    val progress by
-        animateLottieCompositionAsState(composition, iterations = LottieConstants.IterateForever)
-    LottieAnimation(
-        composition = composition,
-        progress = { progress },
-        dynamicProperties = animationProperties,
-    )
-}
-
-@Composable
-private fun SuccessAnimation(
-    @RawRes successAnimationId: Int,
-    animationProperties: LottieDynamicProperties
-) {
-    val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(successAnimationId))
-    val progress by animateLottieCompositionAsState(composition, iterations = 1)
-    LottieAnimation(
-        composition = composition,
-        progress = { progress },
-        dynamicProperties = animationProperties,
-    )
-}
-
-@Composable
-fun rememberColorFilterProperty(
-    layerName: String,
-    color: Color
-): LottieDynamicProperty<ColorFilter> {
-    return rememberLottieDynamicProperty(
-        LottieProperty.COLOR_FILTER,
-        value = PorterDuffColorFilter(color.toArgb(), PorterDuff.Mode.SRC_ATOP),
-        // "**" below means match zero or more layers, so ** layerName ** means find layer with that
-        // name at any depth
-        keyPath = arrayOf("**", layerName, "**")
-    )
-}
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
new file mode 100644
index 0000000..0a6283a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.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
+import com.android.compose.theme.LocalAndroidColorScheme
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialScreenConfig
+import com.android.systemui.inputdevice.tutorial.ui.composable.rememberColorFilterProperty
+import com.android.systemui.res.R
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState
+import com.android.systemui.touchpad.tutorial.ui.gesture.HomeGestureMonitor
+import com.android.systemui.touchpad.tutorial.ui.gesture.TouchpadGestureMonitor
+
+@Composable
+fun HomeGestureTutorialScreen(
+    onDoneButtonClicked: () -> Unit,
+    onBack: () -> Unit,
+) {
+    val screenConfig =
+        TutorialScreenConfig(
+            colors = rememberScreenColors(),
+            strings =
+                TutorialScreenConfig.Strings(
+                    titleResId = R.string.touchpad_home_gesture_action_title,
+                    bodyResId = R.string.touchpad_home_gesture_guidance,
+                    titleSuccessResId = R.string.touchpad_home_gesture_success_title,
+                    bodySuccessResId = R.string.touchpad_home_gesture_success_body
+                ),
+            animations =
+                TutorialScreenConfig.Animations(
+                    educationResId = R.raw.trackpad_home_edu,
+                    successResId = R.raw.trackpad_home_success
+                )
+        )
+    val gestureMonitorProvider =
+        object : GestureMonitorProvider {
+            override fun createGestureMonitor(
+                gestureDistanceThresholdPx: Int,
+                gestureStateChangedCallback: (GestureState) -> Unit
+            ): TouchpadGestureMonitor {
+                return HomeGestureMonitor(gestureDistanceThresholdPx, gestureStateChangedCallback)
+            }
+        }
+    GestureTutorialScreen(screenConfig, gestureMonitorProvider, onDoneButtonClicked, onBack)
+}
+
+@Composable
+private fun rememberScreenColors(): TutorialScreenConfig.Colors {
+    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),
+            rememberColorFilterProperty(".onPrimaryFixed", onPrimaryFixed),
+            rememberColorFilterProperty(".onPrimaryFixedVariant", onPrimaryFixedVariant)
+        )
+    val screenColors =
+        remember(surfaceContainer, dynamicProperties) {
+            TutorialScreenConfig.Colors(
+                background = onPrimaryFixed,
+                successBackground = surfaceContainer,
+                title = primaryFixedDim,
+                animationColors = dynamicProperties,
+            )
+        }
+    return screenColors
+}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt
index 14355fa..65b452a 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt
@@ -34,6 +34,7 @@
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
+import com.android.systemui.inputdevice.tutorial.ui.composable.DoneButton
 import com.android.systemui.res.R
 
 @Composable
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt
index 088a8fd..256c5b5 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt
@@ -27,8 +27,11 @@
 import androidx.lifecycle.Lifecycle.State.STARTED
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.theme.PlatformTheme
+import com.android.systemui.inputdevice.tutorial.ui.composable.ActionKeyTutorialScreen
 import com.android.systemui.touchpad.tutorial.ui.composable.BackGestureTutorialScreen
+import com.android.systemui.touchpad.tutorial.ui.composable.HomeGestureTutorialScreen
 import com.android.systemui.touchpad.tutorial.ui.composable.TutorialSelectionScreen
+import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.ACTION_KEY
 import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.BACK_GESTURE
 import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.HOME_GESTURE
 import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.TUTORIAL_SELECTION
@@ -70,7 +73,7 @@
             TutorialSelectionScreen(
                 onBackTutorialClicked = { vm.goTo(BACK_GESTURE) },
                 onHomeTutorialClicked = { vm.goTo(HOME_GESTURE) },
-                onActionKeyTutorialClicked = {},
+                onActionKeyTutorialClicked = { vm.goTo(ACTION_KEY) },
                 onDoneButtonClicked = closeTutorial
             )
         BACK_GESTURE ->
@@ -78,6 +81,15 @@
                 onDoneButtonClicked = { vm.goTo(TUTORIAL_SELECTION) },
                 onBack = { vm.goTo(TUTORIAL_SELECTION) },
             )
-        HOME_GESTURE -> {}
+        HOME_GESTURE ->
+            HomeGestureTutorialScreen(
+                onDoneButtonClicked = { vm.goTo(TUTORIAL_SELECTION) },
+                onBack = { vm.goTo(TUTORIAL_SELECTION) },
+            )
+        ACTION_KEY -> // TODO(b/358105049) move action key tutorial to OOBE flow
+        ActionKeyTutorialScreen(
+                onDoneButtonClicked = { vm.goTo(TUTORIAL_SELECTION) },
+                onBack = { vm.goTo(TUTORIAL_SELECTION) },
+            )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModel.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModel.kt
index 11984af..d3aeaa7 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModel.kt
@@ -55,4 +55,5 @@
     TUTORIAL_SELECTION,
     BACK_GESTURE,
     HOME_GESTURE,
+    ACTION_KEY,
 }
diff --git a/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java b/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java
index 56b46624..32f2ca6 100644
--- a/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java
@@ -116,7 +116,7 @@
         return mCreateUserDialogController.createDialog(
                 this,
                 this::startActivity,
-                (mUserCreator.isMultipleAdminEnabled() && mUserCreator.isUserAdmin()
+                (mUserCreator.canCreateAdminUser() && mUserCreator.isUserAdmin()
                         && !isKeyguardShowing),
                 this::addUserNow,
                 this::finish
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserCreator.kt b/packages/SystemUI/src/com/android/systemui/user/UserCreator.kt
index 9304a46..a426da9 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserCreator.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/UserCreator.kt
@@ -19,6 +19,7 @@
 import android.content.Context
 import android.content.pm.UserInfo
 import android.graphics.drawable.Drawable
+import android.multiuser.Flags
 import android.os.UserManager
 import com.android.internal.util.UserIcons
 import com.android.settingslib.users.UserCreatingDialog
@@ -91,7 +92,17 @@
         return userManager.isAdminUser
     }
 
-    fun isMultipleAdminEnabled(): Boolean {
-        return UserManager.isMultipleAdminEnabled()
+    /**
+     * Checks if the creation of a new admin user is allowed.
+     *
+     * @return `true` if creating a new admin is allowed, `false` otherwise.
+     */
+    fun canCreateAdminUser(): Boolean {
+        return if (Flags.unicornModeRefactoringForHsumReadOnly()) {
+            UserManager.isMultipleAdminEnabled() &&
+                !userManager.hasUserRestriction(UserManager.DISALLOW_GRANT_ADMIN)
+        } else {
+            UserManager.isMultipleAdminEnabled()
+        }
     }
 }
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 e17274c..ade6c3d 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/FlowDumper.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/FlowDumper.kt
@@ -19,11 +19,14 @@
 import android.util.IndentingPrintWriter
 import com.android.systemui.Dumpable
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.lifecycle.SafeActivatable
+import com.android.systemui.lifecycle.SysUiViewModel
 import com.android.systemui.util.asIndenting
 import com.android.systemui.util.printCollection
 import java.io.PrintWriter
 import java.util.concurrent.ConcurrentHashMap
 import java.util.concurrent.atomic.AtomicBoolean
+import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharedFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -64,19 +67,20 @@
 }
 
 /**
- * An implementation of [FlowDumper]. This be extended directly, or can be used to implement
- * [FlowDumper] by delegation.
- *
- * @param dumpManager if provided, this will be used by the [FlowDumperImpl] to register and
- *   unregister itself when there is something to dump.
- * @param tag a static name by which this [FlowDumperImpl] is registered. If not provided, this
- *   class's name will be used. If you're implementing by delegation, you probably want to provide
- *   this tag to get a meaningful dumpable name.
+ * The minimal implementation of FlowDumper. The owner must either register this with the
+ * DumpManager, or else call [dumpFlows] from its own [Dumpable.dump] method.
  */
-open class FlowDumperImpl(private val dumpManager: DumpManager?, tag: String? = null) : FlowDumper {
+open class SimpleFlowDumper : FlowDumper {
+
     private val stateFlowMap = ConcurrentHashMap<String, StateFlow<*>>()
     private val sharedFlowMap = ConcurrentHashMap<String, SharedFlow<*>>()
     private val flowCollectionMap = ConcurrentHashMap<Pair<String, String>, Any>()
+
+    protected fun isNotEmpty(): Boolean =
+        stateFlowMap.isNotEmpty() || sharedFlowMap.isNotEmpty() || flowCollectionMap.isNotEmpty()
+
+    protected open fun onMapKeysChanged(added: Boolean) {}
+
     override fun dumpFlows(pw: IndentingPrintWriter) {
         pw.printCollection("StateFlow (value)", stateFlowMap.toSortedMap().entries) { (key, flow) ->
             append(key).append('=').println(flow.value)
@@ -92,43 +96,62 @@
         }
     }
 
-    private val Any.idString: String
-        get() = Integer.toHexString(System.identityHashCode(this))
-
     override fun <T> Flow<T>.dumpWhileCollecting(dumpName: String): Flow<T> = flow {
         val mapKey = dumpName to idString
         try {
             collect {
                 flowCollectionMap[mapKey] = it ?: "null"
-                updateRegistration(required = true)
+                onMapKeysChanged(added = true)
                 emit(it)
             }
         } finally {
             flowCollectionMap.remove(mapKey)
-            updateRegistration(required = false)
+            onMapKeysChanged(added = false)
         }
     }
 
     override fun <T, F : StateFlow<T>> F.dumpValue(dumpName: String): F {
         stateFlowMap[dumpName] = this
+        onMapKeysChanged(added = true)
         return this
     }
 
     override fun <T, F : SharedFlow<T>> F.dumpReplayCache(dumpName: String): F {
         sharedFlowMap[dumpName] = this
+        onMapKeysChanged(added = true)
         return this
     }
 
-    private val dumpManagerName = tag ?: "[$idString] ${javaClass.simpleName}"
+    protected val Any.idString: String
+        get() = Integer.toHexString(System.identityHashCode(this))
+}
+
+/**
+ * An implementation of [FlowDumper] that registers itself whenever there is something to dump. This
+ * class is meant to be extended.
+ *
+ * @param dumpManager this will be used by the [FlowDumperImpl] to register and unregister itself
+ *   when there is something to dump.
+ * @param tag a static name by which this [FlowDumperImpl] is registered. If not provided, this
+ *   class's name will be used.
+ */
+abstract class FlowDumperImpl(
+    private val dumpManager: DumpManager,
+    private val tag: String? = null,
+) : SimpleFlowDumper() {
+
+    override fun onMapKeysChanged(added: Boolean) {
+        updateRegistration(required = added)
+    }
+
+    private val dumpManagerName = "[$idString] ${tag ?: javaClass.simpleName}"
+
     private var registered = AtomicBoolean(false)
+
     private fun updateRegistration(required: Boolean) {
-        if (dumpManager == null) return
         if (required && registered.get()) return
         synchronized(registered) {
-            val shouldRegister =
-                stateFlowMap.isNotEmpty() ||
-                    sharedFlowMap.isNotEmpty() ||
-                    flowCollectionMap.isNotEmpty()
+            val shouldRegister = isNotEmpty()
             val wasRegistered = registered.getAndSet(shouldRegister)
             if (wasRegistered != shouldRegister) {
                 if (shouldRegister) {
@@ -140,3 +163,49 @@
         }
     }
 }
+
+/**
+ * A [FlowDumper] that also has an [activateFlowDumper] suspend function that allows the dumper to
+ * be registered with the [DumpManager] only when activated, just like
+ * [Activatable.activate()][com.android.systemui.lifecycle.Activatable.activate].
+ */
+interface ActivatableFlowDumper : FlowDumper {
+    suspend fun activateFlowDumper()
+}
+
+/**
+ * Implementation of [ActivatableFlowDumper] that only registers when activated.
+ *
+ * This is generally used to implement [ActivatableFlowDumper] by delegation, especially for
+ * [SysUiViewModel] implementations.
+ *
+ * @param dumpManager used to automatically register and unregister this instance when activated and
+ *   there is something to dump.
+ * @param tag the name with which this is dumper registered.
+ */
+class ActivatableFlowDumperImpl(
+    private val dumpManager: DumpManager,
+    tag: String,
+) : SimpleFlowDumper(), ActivatableFlowDumper {
+
+    private val registration =
+        object : SafeActivatable() {
+            override suspend fun onActivated() {
+                try {
+                    dumpManager.registerCriticalDumpable(
+                        dumpManagerName,
+                        this@ActivatableFlowDumperImpl
+                    )
+                    awaitCancellation()
+                } finally {
+                    dumpManager.unregisterDumpable(dumpManagerName)
+                }
+            }
+        }
+
+    private val dumpManagerName = "[$idString] $tag"
+
+    override suspend fun activateFlowDumper() {
+        registration.activate()
+    }
+}
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..055671c 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
@@ -28,6 +28,7 @@
 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.combine
@@ -62,7 +63,9 @@
 /**
  * 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 +74,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/util/settings/GlobalSettingsImpl.java b/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java
index 816f55d..7fcabe4 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java
@@ -42,28 +42,31 @@
         mBgDispatcher = bgDispatcher;
     }
 
+    @NonNull
     @Override
     public ContentResolver getContentResolver() {
         return mContentResolver;
     }
 
+    @NonNull
     @Override
-    public Uri getUriFor(String name) {
+    public Uri getUriFor(@NonNull String name) {
         return Settings.Global.getUriFor(name);
     }
 
+    @NonNull
     @Override
     public CoroutineDispatcher getBackgroundDispatcher() {
         return mBgDispatcher;
     }
 
     @Override
-    public String getString(String name) {
+    public String getString(@NonNull String name) {
         return Settings.Global.getString(mContentResolver, name);
     }
 
     @Override
-    public boolean putString(String name, String value) {
+    public boolean putString(@NonNull String name, String value) {
         return Settings.Global.putString(mContentResolver, name, value);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java b/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java
index f1da27f..c296481 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java
@@ -16,12 +16,11 @@
 
 package com.android.systemui.util.settings;
 
+import android.annotation.NonNull;
 import android.content.ContentResolver;
 import android.net.Uri;
 import android.provider.Settings;
 
-import androidx.annotation.NonNull;
-
 import com.android.systemui.util.kotlin.SettingsSingleThreadBackground;
 
 import kotlinx.coroutines.CoroutineDispatcher;
@@ -43,46 +42,50 @@
         mBgDispatcher = bgDispatcher;
     }
 
+    @NonNull
     @Override
     public ContentResolver getContentResolver() {
         return mContentResolver;
     }
 
+    @NonNull
     @Override
     public CurrentUserIdProvider getCurrentUserProvider() {
         return mCurrentUserProvider;
     }
 
+    @NonNull
     @Override
-    public Uri getUriFor(String name) {
+    public Uri getUriFor(@NonNull String name) {
         return Settings.Secure.getUriFor(name);
     }
 
+    @NonNull
     @Override
     public CoroutineDispatcher getBackgroundDispatcher() {
         return mBgDispatcher;
     }
 
     @Override
-    public String getStringForUser(String name, int userHandle) {
+    public String getStringForUser(@NonNull String name, int userHandle) {
         return Settings.Secure.getStringForUser(mContentResolver, name,
                 getRealUserHandle(userHandle));
     }
 
     @Override
-    public boolean putString(String name, String value, boolean overrideableByRestore) {
+    public boolean putString(@NonNull String name, String value, boolean overrideableByRestore) {
         return Settings.Secure.putString(mContentResolver, name, value, overrideableByRestore);
     }
 
     @Override
-    public boolean putStringForUser(String name, String value, int userHandle) {
+    public boolean putStringForUser(@NonNull String name, String value, int userHandle) {
         return Settings.Secure.putStringForUser(mContentResolver, name, value,
                 getRealUserHandle(userHandle));
     }
 
     @Override
-    public boolean putStringForUser(String name, String value, String tag, boolean makeDefault,
-            int userHandle, boolean overrideableByRestore) {
+    public boolean putStringForUser(@NonNull String name, String value, String tag,
+            boolean makeDefault, int userHandle, boolean overrideableByRestore) {
         return Settings.Secure.putStringForUser(
                 mContentResolver, name, value, tag, makeDefault, getRealUserHandle(userHandle),
                 overrideableByRestore);
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
index 0ee997e..82f41a7 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
@@ -346,7 +346,7 @@
      * @param value to associate with the name
      * @return true if the value was set, false on database errors
      */
-    fun putString(name: String, value: String): Boolean
+    fun putString(name: String, value: String?): Boolean
 
     /**
      * Store a name/value pair into the database.
@@ -377,7 +377,7 @@
      * @return true if the value was set, false on database errors.
      * @see .resetToDefaults
      */
-    fun putString(name: String, value: String, tag: String, makeDefault: Boolean): Boolean
+    fun putString(name: String, value: String?, tag: String?, makeDefault: Boolean): Boolean
 
     /**
      * Convenience function for retrieving a single secure settings value as an integer. Note that
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java b/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java
index 1e80357..e670b2c 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java
@@ -16,12 +16,11 @@
 
 package com.android.systemui.util.settings;
 
+import android.annotation.NonNull;
 import android.content.ContentResolver;
 import android.net.Uri;
 import android.provider.Settings;
 
-import androidx.annotation.NonNull;
-
 import com.android.systemui.util.kotlin.SettingsSingleThreadBackground;
 
 import kotlinx.coroutines.CoroutineDispatcher;
@@ -42,46 +41,50 @@
         mBgCoroutineDispatcher = bgDispatcher;
     }
 
+    @NonNull
     @Override
     public ContentResolver getContentResolver() {
         return mContentResolver;
     }
 
+    @NonNull
     @Override
     public CurrentUserIdProvider getCurrentUserProvider() {
         return mCurrentUserProvider;
     }
 
+    @NonNull
     @Override
-    public Uri getUriFor(String name) {
+    public Uri getUriFor(@NonNull String name) {
         return Settings.System.getUriFor(name);
     }
 
+    @NonNull
     @Override
     public CoroutineDispatcher getBackgroundDispatcher() {
         return mBgCoroutineDispatcher;
     }
 
     @Override
-    public String getStringForUser(String name, int userHandle) {
+    public String getStringForUser(@NonNull String name, int userHandle) {
         return Settings.System.getStringForUser(mContentResolver, name,
                 getRealUserHandle(userHandle));
     }
 
     @Override
-    public boolean putString(String name, String value, boolean overrideableByRestore) {
+    public boolean putString(@NonNull String name, String value, boolean overrideableByRestore) {
         return Settings.System.putString(mContentResolver, name, value, overrideableByRestore);
     }
 
     @Override
-    public boolean putStringForUser(String name, String value, int userHandle) {
+    public boolean putStringForUser(@NonNull String name, String value, int userHandle) {
         return Settings.System.putStringForUser(mContentResolver, name, value,
                 getRealUserHandle(userHandle));
     }
 
     @Override
-    public boolean putStringForUser(String name, String value, String tag, boolean makeDefault,
-            int userHandle, boolean overrideableByRestore) {
+    public boolean putStringForUser(@NonNull String name, String value, String tag,
+            boolean makeDefault, int userHandle, boolean overrideableByRestore) {
         throw new UnsupportedOperationException(
                 "This method only exists publicly for Settings.Secure and Settings.Global");
     }
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
index 9ae8f03..8e3b813 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
@@ -368,19 +368,19 @@
      * @param value to associate with the name
      * @return true if the value was set, false on database errors
      */
-    fun putString(name: String, value: String, overrideableByRestore: Boolean): Boolean
+    fun putString(name: String, value: String?, overrideableByRestore: Boolean): Boolean
 
-    override fun putString(name: String, value: String): Boolean {
+    override fun putString(name: String, value: String?): Boolean {
         return putStringForUser(name, value, userId)
     }
 
     /** Similar implementation to [putString] for the specified [userHandle]. */
-    fun putStringForUser(name: String, value: String, userHandle: Int): Boolean
+    fun putStringForUser(name: String, value: String?, userHandle: Int): Boolean
 
     /** Similar implementation to [putString] for the specified [userHandle]. */
     fun putStringForUser(
         name: String,
-        value: String,
+        value: String?,
         tag: String?,
         makeDefault: Boolean,
         @UserIdInt userHandle: Int,
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 5d8b6f1..d39daaf 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
@@ -79,13 +79,15 @@
             localBluetoothManager: LocalBluetoothManager?,
             @Application coroutineScope: CoroutineScope,
             @Background coroutineContext: CoroutineContext,
+            volumeLogger: VolumeLogger
         ): AudioSharingRepository =
             if (Flags.enableLeAudioSharing() && localBluetoothManager != null) {
                 AudioSharingRepositoryImpl(
                     contentResolver,
                     localBluetoothManager,
                     coroutineScope,
-                    coroutineContext
+                    coroutineContext,
+                    volumeLogger
                 )
             } else {
                 AudioSharingRepositoryEmptyImpl()
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/data/repository/VolumePanelGlobalStateRepository.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/data/repository/VolumePanelGlobalStateRepository.kt
index e46ce26..24fb001 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/data/repository/VolumePanelGlobalStateRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/data/repository/VolumePanelGlobalStateRepository.kt
@@ -19,6 +19,7 @@
 import com.android.systemui.Dumpable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.volume.panel.shared.VolumePanelLogger
 import com.android.systemui.volume.panel.shared.model.VolumePanelGlobalState
 import java.io.PrintWriter
 import javax.inject.Inject
@@ -27,10 +28,15 @@
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.update
 
-private const val TAG = "VolumePanelGlobalState"
+private const val TAG = "VolumePanelGlobalStateRepository"
 
 @SysUISingleton
-class VolumePanelGlobalStateRepository @Inject constructor(dumpManager: DumpManager) : Dumpable {
+class VolumePanelGlobalStateRepository
+@Inject
+constructor(
+    dumpManager: DumpManager,
+    private val logger: VolumePanelLogger,
+) : Dumpable {
 
     private val mutableGlobalState =
         MutableStateFlow(
@@ -48,6 +54,7 @@
         update: (currentState: VolumePanelGlobalState) -> VolumePanelGlobalState
     ) {
         mutableGlobalState.update(update)
+        logger.onVolumePanelGlobalStateChanged(mutableGlobalState.value)
     }
 
     override fun dump(pw: PrintWriter, args: Array<out String>) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractor.kt
index 5301b00..9de862a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractor.kt
@@ -19,6 +19,7 @@
 import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
 import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
 import com.android.systemui.volume.panel.domain.model.ComponentModel
+import com.android.systemui.volume.panel.shared.VolumePanelLogger
 import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
 import javax.inject.Inject
 import javax.inject.Provider
@@ -26,8 +27,12 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
 
 interface ComponentsInteractor {
 
@@ -45,6 +50,7 @@
     enabledComponents: Collection<VolumePanelComponentKey>,
     defaultCriteria: Provider<ComponentAvailabilityCriteria>,
     @VolumePanelScope coroutineScope: CoroutineScope,
+    private val logger: VolumePanelLogger,
     private val criteriaByKey:
         Map<
             VolumePanelComponentKey,
@@ -57,12 +63,18 @@
         combine(
                 enabledComponents.map { componentKey ->
                     val componentCriteria = (criteriaByKey[componentKey] ?: defaultCriteria).get()
-                    componentCriteria.isAvailable().map { isAvailable ->
-                        ComponentModel(componentKey, isAvailable = isAvailable)
-                    }
+                    componentCriteria
+                        .isAvailable()
+                        .distinctUntilChanged()
+                        .conflate()
+                        .onEach { logger.onComponentAvailabilityChanged(componentKey, it) }
+                        .map { isAvailable ->
+                            ComponentModel(componentKey, isAvailable = isAvailable)
+                        }
                 }
             ) {
                 it.asList()
             }
-            .shareIn(coroutineScope, SharingStarted.Eagerly, replay = 1)
+            .stateIn(coroutineScope, SharingStarted.Eagerly, null)
+            .filterNotNull()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/shared/VolumePanelLogger.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/VolumePanelLogger.kt
index cc513b5..276326c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/shared/VolumePanelLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/VolumePanelLogger.kt
@@ -20,15 +20,41 @@
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.dagger.VolumeLog
-import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
+import com.android.systemui.volume.panel.shared.model.VolumePanelGlobalState
+import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelState
 import javax.inject.Inject
 
 private const val TAG = "SysUI_VolumePanel"
 
 /** Logs events related to the Volume Panel. */
-@VolumePanelScope
 class VolumePanelLogger @Inject constructor(@VolumeLog private val logBuffer: LogBuffer) {
 
+    fun onVolumePanelStateChanged(state: VolumePanelState) {
+        logBuffer.log(TAG, LogLevel.DEBUG, { str1 = state.toString() }, { "State changed: $str1" })
+    }
+
+    fun onComponentAvailabilityChanged(key: VolumePanelComponentKey, isAvailable: Boolean) {
+        logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = key
+                bool1 = isAvailable
+            },
+            { "$str1 isAvailable=$bool1" }
+        )
+    }
+
+    fun onVolumePanelGlobalStateChanged(globalState: VolumePanelGlobalState) {
+        logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            { bool1 = globalState.isVisible },
+            { "Global state changed: isVisible=$bool1" }
+        )
+    }
+
     fun onSetVolumeRequested(audioStream: AudioStream, volume: Int) {
         logBuffer.log(
             TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayout.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayout.kt
index 1c51236..a06d3e3 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayout.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.volume.panel.ui.layout
 
 import com.android.systemui.volume.panel.ui.viewmodel.ComponentState
+import com.android.systemui.volume.panel.ui.viewmodel.toLogString
 
 /** Represents components grouping into the layout. */
 data class ComponentsLayout(
@@ -29,3 +30,12 @@
     /** This is a separated entity that is always visible on the bottom of the Volume Panel. */
     val bottomBarComponent: ComponentState,
 )
+
+fun ComponentsLayout.toLogString(): String {
+    return "(" +
+        " headerComponents=${headerComponents.joinToString { it.toLogString() }}" +
+        " contentComponents=${contentComponents.joinToString { it.toLogString() }}" +
+        " footerComponents=${footerComponents.joinToString { it.toLogString() }}" +
+        " bottomBarComponent=${bottomBarComponent.toLogString()}" +
+        " )"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/ComponentState.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/ComponentState.kt
index 5f4dbfb..41c80fa 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/ComponentState.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/ComponentState.kt
@@ -32,3 +32,5 @@
     val component: VolumePanelUiComponent,
     val isVisible: Boolean,
 )
+
+fun ComponentState.toLogString(): String = "$key:visible=$isVisible"
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt
index f495a02f..2f60c4b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt
@@ -19,23 +19,30 @@
 import android.content.Context
 import android.content.IntentFilter
 import android.content.res.Resources
+import com.android.systemui.Dumpable
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dump.DumpManager
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.onConfigChanged
+import com.android.systemui.util.kotlin.launchAndDispose
 import com.android.systemui.volume.VolumePanelDialogReceiver
 import com.android.systemui.volume.panel.dagger.VolumePanelComponent
 import com.android.systemui.volume.panel.dagger.factory.VolumePanelComponentFactory
 import com.android.systemui.volume.panel.domain.VolumePanelStartable
 import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractor
 import com.android.systemui.volume.panel.domain.interactor.VolumePanelGlobalStateInteractor
+import com.android.systemui.volume.panel.shared.VolumePanelLogger
 import com.android.systemui.volume.panel.ui.composable.ComponentsFactory
 import com.android.systemui.volume.panel.ui.layout.ComponentsLayout
 import com.android.systemui.volume.panel.ui.layout.ComponentsLayoutManager
+import com.android.systemui.volume.panel.ui.layout.toLogString
+import java.io.PrintWriter
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
@@ -43,19 +50,23 @@
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.flow.shareIn
 import kotlinx.coroutines.flow.stateIn
 
+private const val TAG = "VolumePanelViewModel"
+
 // Can't inject a constructor here because VolumePanelComponent provides this view model for its
 // components.
+@OptIn(ExperimentalCoroutinesApi::class)
 class VolumePanelViewModel(
     resources: Resources,
     coroutineScope: CoroutineScope,
     daggerComponentFactory: VolumePanelComponentFactory,
     configurationController: ConfigurationController,
     broadcastDispatcher: BroadcastDispatcher,
+    private val dumpManager: DumpManager,
+    private val logger: VolumePanelLogger,
     private val volumePanelGlobalStateInteractor: VolumePanelGlobalStateInteractor,
-) {
+) : Dumpable {
 
     private val volumePanelComponent: VolumePanelComponent =
         daggerComponentFactory.create(this, coroutineScope)
@@ -77,9 +88,10 @@
             .onStart { emit(resources.configuration) }
             .map { configuration ->
                 VolumePanelState(
-                    orientation = configuration.orientation,
-                    isLargeScreen = resources.getBoolean(R.bool.volume_panel_is_large_screen),
-                )
+                        orientation = configuration.orientation,
+                        isLargeScreen = resources.getBoolean(R.bool.volume_panel_is_large_screen),
+                    )
+                    .also { logger.onVolumePanelStateChanged(it) }
             }
             .stateIn(
                 scope,
@@ -89,7 +101,7 @@
                     isLargeScreen = resources.getBoolean(R.bool.volume_panel_is_large_screen)
                 ),
             )
-    val componentsLayout: Flow<ComponentsLayout> =
+    val componentsLayout: StateFlow<ComponentsLayout?> =
         combine(
                 componentsInteractor.components,
                 volumePanelState,
@@ -104,13 +116,18 @@
                     }
                 componentsLayoutManager.layout(scope, componentStates)
             }
-            .shareIn(
+            .stateIn(
                 scope,
                 SharingStarted.Eagerly,
-                replay = 1,
+                null,
             )
 
     init {
+        scope.launchAndDispose {
+            dumpManager.registerNormalDumpable(TAG, this)
+            DisposableHandle { dumpManager.unregisterDumpable(TAG) }
+        }
+
         volumePanelComponent.volumePanelStartables().onEach(VolumePanelStartable::start)
         broadcastDispatcher
             .broadcastFlow(IntentFilter(VolumePanelDialogReceiver.DISMISS_ACTION))
@@ -122,6 +139,13 @@
         volumePanelGlobalStateInteractor.setVisible(false)
     }
 
+    override fun dump(pw: PrintWriter, args: Array<out String>) {
+        with(pw) {
+            println("volumePanelState=${volumePanelState.value}")
+            println("componentsLayout=${componentsLayout.value?.toLogString()}")
+        }
+    }
+
     class Factory
     @Inject
     constructor(
@@ -129,6 +153,8 @@
         private val daggerComponentFactory: VolumePanelComponentFactory,
         private val configurationController: ConfigurationController,
         private val broadcastDispatcher: BroadcastDispatcher,
+        private val dumpManager: DumpManager,
+        private val logger: VolumePanelLogger,
         private val volumePanelGlobalStateInteractor: VolumePanelGlobalStateInteractor,
     ) {
 
@@ -139,6 +165,8 @@
                 daggerComponentFactory,
                 configurationController,
                 broadcastDispatcher,
+                dumpManager,
+                logger,
                 volumePanelGlobalStateInteractor,
             )
         }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/shared/VolumeLogger.kt b/packages/SystemUI/src/com/android/systemui/volume/shared/VolumeLogger.kt
index 869a82a..d6b159e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/shared/VolumeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/shared/VolumeLogger.kt
@@ -16,7 +16,8 @@
 
 package com.android.systemui.volume.shared
 
-import com.android.settingslib.volume.data.repository.AudioRepositoryImpl
+import com.android.settingslib.volume.shared.AudioLogger
+import com.android.settingslib.volume.shared.AudioSharingLogger
 import com.android.settingslib.volume.shared.model.AudioStream
 import com.android.settingslib.volume.shared.model.AudioStreamModel
 import com.android.systemui.dagger.SysUISingleton
@@ -30,7 +31,7 @@
 /** Logs general System UI volume events. */
 @SysUISingleton
 class VolumeLogger @Inject constructor(@VolumeLog private val logBuffer: LogBuffer) :
-    AudioRepositoryImpl.Logger {
+    AudioLogger, AudioSharingLogger {
 
     override fun onSetVolumeRequested(audioStream: AudioStream, volume: Int) {
         logBuffer.log(
@@ -55,4 +56,35 @@
             { "Volume update received: stream=$str1 volume=$int1" }
         )
     }
+
+    override fun onAudioSharingStateChanged(state: Boolean) {
+        logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            { bool1 = state },
+            { "Audio sharing state update: state=$bool1" }
+        )
+    }
+
+    override fun onSecondaryGroupIdChanged(groupId: Int) {
+        logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            { int1 = groupId },
+            { "Secondary group id in audio sharing update: groupId=$int1" }
+        )
+    }
+
+    override fun onVolumeMapChanged(map: Map<Int, Int>) {
+        logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            { str1 = map.toString() },
+            { "Volume map update: map=$str1" }
+        )
+    }
+
+    override fun onSetDeviceVolumeRequested(volume: Int) {
+        logBuffer.log(TAG, LogLevel.DEBUG, { int1 = volume }, { "Set device volume: volume=$int1" })
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 9ca0591..50efc21 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -34,6 +34,8 @@
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.inputmethodservice.InputMethodService;
+import android.inputmethodservice.InputMethodService.BackDispositionMode;
+import android.inputmethodservice.InputMethodService.ImeWindowVisibility;
 import android.util.Log;
 import android.view.Display;
 import android.view.KeyEvent;
@@ -378,8 +380,8 @@
             }
 
             @Override
-            public void setImeWindowStatus(int displayId, int vis, int backDisposition,
-                    boolean showImeSwitcher) {
+            public void setImeWindowStatus(int displayId, @ImeWindowVisibility int vis,
+                    @BackDispositionMode int backDisposition, boolean showImeSwitcher) {
                 if (displayId == mDisplayTracker.getDefaultDisplayId()
                         && (vis & InputMethodService.IME_VISIBLE) != 0) {
                     oneHanded.stopOneHanded(
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
index 319b615..2bb9e68 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
@@ -55,7 +55,6 @@
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerAlwaysOnDisplayViewBinder;
-import com.android.systemui.statusbar.phone.NotificationIconAreaController;
 import com.android.systemui.statusbar.phone.NotificationIconContainer;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.settings.SecureSettings;
@@ -78,8 +77,6 @@
     @Mock
     KeyguardSliceViewController mKeyguardSliceViewController;
     @Mock
-    NotificationIconAreaController mNotificationIconAreaController;
-    @Mock
     LockscreenSmartspaceController mSmartspaceController;
 
     @Mock
@@ -176,7 +173,6 @@
                 mStatusBarStateController,
                 mClockRegistry,
                 mKeyguardSliceViewController,
-                mNotificationIconAreaController,
                 mSmartspaceController,
                 mock(NotificationIconContainerAlwaysOnDisplayViewBinder.class),
                 mKeyguardUnlockAnimationController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
index 16b9ab5..ff47fd1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
@@ -48,6 +48,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.model.SysUiState;
@@ -102,6 +103,8 @@
     private AccessibilityLogger mA11yLogger;
     @Mock
     private IWindowManager mIWindowManager;
+    @Mock
+    private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
 
     private IMagnificationConnection mIMagnificationConnection;
     private MagnificationImpl mMagnification;
@@ -123,7 +126,8 @@
                 mTestableLooper.getLooper(), mContext.getMainExecutor(), mCommandQueue,
                 mModeSwitchesController, mSysUiState, mOverviewProxyService, mSecureSettings,
                 mDisplayTracker, getContext().getSystemService(DisplayManager.class),
-                mA11yLogger, mIWindowManager, mAccessibilityManager);
+                mA11yLogger, mIWindowManager, mAccessibilityManager,
+                mViewCaptureAwareWindowManager);
         mMagnification.mWindowMagnificationControllerSupplier =
                 new FakeWindowMagnificationControllerSupplier(
                         mContext.getSystemService(DisplayManager.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
index 5be1180..1ceac78 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
@@ -73,10 +73,14 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.app.viewcapture.ViewCapture;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.res.R;
 
+import kotlin.Lazy;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -104,6 +108,8 @@
     private SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
     @Mock
     private MagnificationModeSwitch.ClickListener mClickListener;
+    @Mock
+    private Lazy<ViewCapture> mLazyViewCapture;
     private TestableWindowManager mWindowManager;
     private ViewPropertyAnimator mViewPropertyAnimator;
     private MagnificationModeSwitch mMagnificationModeSwitch;
@@ -133,8 +139,10 @@
             return null;
         }).when(mSfVsyncFrameProvider).postFrameCallback(
                 any(Choreographer.FrameCallback.class));
+        ViewCaptureAwareWindowManager vwm = new ViewCaptureAwareWindowManager(mWindowManager,
+                mLazyViewCapture, false);
         mMagnificationModeSwitch = new MagnificationModeSwitch(mContext, mSpyImageView,
-                mSfVsyncFrameProvider, mClickListener);
+                mSfVsyncFrameProvider, mClickListener, vwm);
         assertNotNull(mTouchListener);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationSettingsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationSettingsControllerTest.java
index d0f8e78..3cd3fef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationSettingsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationSettingsControllerTest.java
@@ -27,6 +27,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.accessibility.WindowMagnificationSettings.MagnificationSize;
@@ -56,6 +57,8 @@
     private SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
     @Mock
     private SecureSettings mSecureSettings;
+    @Mock
+    private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
 
     @Before
     public void setUp() {
@@ -63,7 +66,7 @@
         mMagnificationSettingsController = new MagnificationSettingsController(
                 mContext, mSfVsyncFrameProvider,
                 mMagnificationSettingControllerCallback, mSecureSettings,
-                mWindowMagnificationSettings);
+                mWindowMagnificationSettings, mViewCaptureAwareWindowManager);
     }
 
     @After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
index 038b81b..057ddcd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
@@ -50,6 +50,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.recents.OverviewProxyService;
@@ -96,6 +97,8 @@
     private AccessibilityLogger mA11yLogger;
     @Mock
     private IWindowManager mIWindowManager;
+    @Mock
+    private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
 
     @Before
     public void setUp() throws Exception {
@@ -129,7 +132,8 @@
                 mCommandQueue, mModeSwitchesController,
                 mSysUiState, mOverviewProxyService, mSecureSettings, mDisplayTracker,
                 getContext().getSystemService(DisplayManager.class), mA11yLogger, mIWindowManager,
-                getContext().getSystemService(AccessibilityManager.class));
+                getContext().getSystemService(AccessibilityManager.class),
+                mViewCaptureAwareWindowManager);
         mMagnification.mWindowMagnificationControllerSupplier = new FakeControllerSupplier(
                 mContext.getSystemService(DisplayManager.class), mWindowMagnificationController);
         mMagnification.mMagnificationSettingsSupplier = new FakeSettingsSupplier(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java
index 6e94297..e1e515e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java
@@ -28,6 +28,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.systemui.SysuiTestCase;
 
 import org.junit.After;
@@ -50,6 +51,8 @@
     private View mSpyView;
     @Mock
     private MagnificationModeSwitch.ClickListener mListener;
+    @Mock
+    private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
 
 
     @Before
@@ -58,7 +61,8 @@
         mSupplier = new FakeSwitchSupplier(mContext.getSystemService(DisplayManager.class));
         mModeSwitchesController = new ModeSwitchesController(mSupplier);
         mModeSwitchesController.setClickListenerDelegate(mListener);
-        mModeSwitch = Mockito.spy(new MagnificationModeSwitch(mContext, mModeSwitchesController));
+        mModeSwitch = Mockito.spy(new MagnificationModeSwitch(mContext, mModeSwitchesController,
+                mViewCaptureAwareWindowManager));
         mSpyView = Mockito.spy(new View(mContext));
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
index 003f7e4..9507077 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
@@ -61,6 +61,8 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.app.viewcapture.ViewCapture;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView;
@@ -68,6 +70,8 @@
 import com.android.systemui.res.R;
 import com.android.systemui.util.settings.SecureSettings;
 
+import kotlin.Lazy;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -95,6 +99,8 @@
     private SecureSettings mSecureSettings;
     @Mock
     private WindowMagnificationSettingsCallback mWindowMagnificationSettingsCallback;
+    @Mock
+    private Lazy<ViewCapture> mLazyViewCapture;
     private TestableWindowManager mWindowManager;
     private WindowMagnificationSettings mWindowMagnificationSettings;
     private MotionEventHelper mMotionEventHelper = new MotionEventHelper();
@@ -119,9 +125,11 @@
         when(mSecureSettings.getFloatForUser(anyString(), anyFloat(), anyInt())).then(
                 returnsSecondArg());
 
+        ViewCaptureAwareWindowManager vwm = new ViewCaptureAwareWindowManager(mWindowManager,
+                mLazyViewCapture, /* isViewCaptureEnabled= */ false);
         mWindowMagnificationSettings = new WindowMagnificationSettings(mContext,
                 mWindowMagnificationSettingsCallback, mSfVsyncFrameProvider,
-                mSecureSettings);
+                mSecureSettings, vwm);
 
         mSettingView = mWindowMagnificationSettings.getSettingView();
         mZoomSeekbar = mSettingView.findViewById(R.id.magnifier_zoom_slider);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsControllerTest.java
new file mode 100644
index 0000000..2ac5d10
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsControllerTest.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.hearingaid;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import static java.util.Collections.emptyList;
+
+import android.bluetooth.BluetoothCsipSetCoordinator;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHapClient;
+import android.bluetooth.BluetoothHapPresetInfo;
+import android.testing.TestableLooper;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.HapClientProfile;
+import com.android.settingslib.bluetooth.LocalBluetoothProfile;
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/** Tests for {@link HearingDevicesPresetsController}. */
+@RunWith(AndroidJUnit4.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@SmallTest
+public class HearingDevicesPresetsControllerTest extends SysuiTestCase {
+
+    private static final int TEST_PRESET_INDEX = 1;
+    private static final String TEST_PRESET_NAME = "test_preset";
+    private static final int TEST_HAP_GROUP_ID = 1;
+    private static final int TEST_REASON = 1024;
+
+    @Rule
+    public MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private LocalBluetoothProfileManager mProfileManager;
+    @Mock
+    private HapClientProfile mHapClientProfile;
+    @Mock
+    private CachedBluetoothDevice mCachedBluetoothDevice;
+    @Mock
+    private CachedBluetoothDevice mSubCachedBluetoothDevice;
+    @Mock
+    private BluetoothDevice mBluetoothDevice;
+    @Mock
+    private BluetoothDevice mSubBluetoothDevice;
+
+    @Mock
+    private HearingDevicesPresetsController.PresetCallback mCallback;
+
+    private HearingDevicesPresetsController mController;
+
+    @Before
+    public void setUp() {
+        when(mProfileManager.getHapClientProfile()).thenReturn(mHapClientProfile);
+        when(mHapClientProfile.isProfileReady()).thenReturn(true);
+        when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+        when(mCachedBluetoothDevice.getSubDevice()).thenReturn(mSubCachedBluetoothDevice);
+        when(mSubCachedBluetoothDevice.getDevice()).thenReturn(mSubBluetoothDevice);
+
+        mController = new HearingDevicesPresetsController(mProfileManager, mCallback);
+    }
+
+    @Test
+    public void onServiceConnected_callExpectedCallback() {
+        mController.onServiceConnected();
+
+        verify(mHapClientProfile).registerCallback(any(Executor.class),
+                any(BluetoothHapClient.Callback.class));
+        verify(mCallback).onPresetInfoUpdated(anyList(), anyInt());
+    }
+
+    @Test
+    public void getAllPresetInfo_setInvalidHearingDevice_getEmpty() {
+        when(mCachedBluetoothDevice.getProfiles()).thenReturn(emptyList());
+        mController.setHearingDeviceIfSupportHap(mCachedBluetoothDevice);
+        BluetoothHapPresetInfo hapPresetInfo = getHapPresetInfo(true);
+        when(mHapClientProfile.getAllPresetInfo(mBluetoothDevice)).thenReturn(
+                List.of(hapPresetInfo));
+
+        assertThat(mController.getAllPresetInfo()).isEmpty();
+    }
+
+    @Test
+    public void getAllPresetInfo_containsNotAvailablePresetInfo_getEmpty() {
+        setValidHearingDeviceSupportHap();
+        BluetoothHapPresetInfo hapPresetInfo = getHapPresetInfo(false);
+        when(mHapClientProfile.getAllPresetInfo(mBluetoothDevice)).thenReturn(
+                List.of(hapPresetInfo));
+
+        assertThat(mController.getAllPresetInfo()).isEmpty();
+    }
+
+    @Test
+    public void getAllPresetInfo_containsOnePresetInfo_getOnePresetInfo() {
+        setValidHearingDeviceSupportHap();
+        BluetoothHapPresetInfo hapPresetInfo = getHapPresetInfo(true);
+        when(mHapClientProfile.getAllPresetInfo(mBluetoothDevice)).thenReturn(
+                List.of(hapPresetInfo));
+
+        assertThat(mController.getAllPresetInfo()).contains(hapPresetInfo);
+    }
+
+    @Test
+    public void getActivePresetIndex_getExpectedIndex() {
+        setValidHearingDeviceSupportHap();
+        when(mHapClientProfile.getActivePresetIndex(mBluetoothDevice)).thenReturn(
+                TEST_PRESET_INDEX);
+
+        assertThat(mController.getActivePresetIndex()).isEqualTo(TEST_PRESET_INDEX);
+    }
+
+    @Test
+    public void onPresetSelected_presetIndex_callOnPresetInfoUpdatedWithExpectedPresetIndex() {
+        setValidHearingDeviceSupportHap();
+        BluetoothHapPresetInfo hapPresetInfo = getHapPresetInfo(true);
+        when(mHapClientProfile.getAllPresetInfo(mBluetoothDevice)).thenReturn(
+                List.of(hapPresetInfo));
+        when(mHapClientProfile.getActivePresetIndex(mBluetoothDevice)).thenReturn(
+                TEST_PRESET_INDEX);
+
+        mController.onPresetSelected(mBluetoothDevice, TEST_PRESET_INDEX, TEST_REASON);
+
+        verify(mCallback).onPresetInfoUpdated(eq(List.of(hapPresetInfo)), eq(TEST_PRESET_INDEX));
+    }
+
+    @Test
+    public void onPresetInfoChanged_presetIndex_callOnPresetInfoUpdatedWithExpectedPresetIndex() {
+        setValidHearingDeviceSupportHap();
+        BluetoothHapPresetInfo hapPresetInfo = getHapPresetInfo(true);
+        when(mHapClientProfile.getAllPresetInfo(mBluetoothDevice)).thenReturn(
+                List.of(hapPresetInfo));
+        when(mHapClientProfile.getActivePresetIndex(mBluetoothDevice)).thenReturn(
+                TEST_PRESET_INDEX);
+
+        mController.onPresetInfoChanged(mBluetoothDevice, List.of(hapPresetInfo), TEST_REASON);
+
+        verify(mCallback).onPresetInfoUpdated(List.of(hapPresetInfo), TEST_PRESET_INDEX);
+    }
+
+    @Test
+    public void onPresetSelectionFailed_callOnPresetCommandFailed() {
+        setValidHearingDeviceSupportHap();
+
+        mController.onPresetSelectionFailed(mBluetoothDevice, TEST_REASON);
+
+        verify(mCallback).onPresetCommandFailed(TEST_REASON);
+    }
+
+    @Test
+    public void onSetPresetNameFailed_callOnPresetCommandFailed() {
+        setValidHearingDeviceSupportHap();
+
+        mController.onSetPresetNameFailed(mBluetoothDevice, TEST_REASON);
+
+        verify(mCallback).onPresetCommandFailed(TEST_REASON);
+    }
+
+    @Test
+    public void onPresetSelectionForGroupFailed_callSelectPresetIndividual() {
+        setValidHearingDeviceSupportHap();
+        mController.selectPreset(TEST_PRESET_INDEX);
+        Mockito.reset(mHapClientProfile);
+        when(mHapClientProfile.getHapGroup(mBluetoothDevice)).thenReturn(TEST_HAP_GROUP_ID);
+
+        mController.onPresetSelectionForGroupFailed(TEST_HAP_GROUP_ID, TEST_REASON);
+
+
+        verify(mHapClientProfile).selectPreset(mBluetoothDevice, TEST_PRESET_INDEX);
+        verify(mHapClientProfile).selectPreset(mSubBluetoothDevice, TEST_PRESET_INDEX);
+    }
+
+    @Test
+    public void onSetPresetNameForGroupFailed_callOnPresetCommandFailed() {
+        setValidHearingDeviceSupportHap();
+
+        mController.onSetPresetNameForGroupFailed(TEST_HAP_GROUP_ID, TEST_REASON);
+
+        verify(mCallback).onPresetCommandFailed(TEST_REASON);
+    }
+
+    @Test
+    public void registerHapCallback_callHapRegisterCallback() {
+        mController.registerHapCallback();
+
+        verify(mHapClientProfile).registerCallback(any(Executor.class),
+                any(BluetoothHapClient.Callback.class));
+    }
+
+    @Test
+    public void unregisterHapCallback_callHapUnregisterCallback() {
+        mController.unregisterHapCallback();
+
+        verify(mHapClientProfile).unregisterCallback(any(BluetoothHapClient.Callback.class));
+    }
+
+    @Test
+    public void selectPreset_supportSynchronized_validGroupId_callSelectPresetForGroup() {
+        setValidHearingDeviceSupportHap();
+        when(mHapClientProfile.supportsSynchronizedPresets(mBluetoothDevice)).thenReturn(true);
+        when(mHapClientProfile.getHapGroup(mBluetoothDevice)).thenReturn(TEST_HAP_GROUP_ID);
+
+        mController.selectPreset(TEST_PRESET_INDEX);
+
+        verify(mHapClientProfile).selectPresetForGroup(TEST_HAP_GROUP_ID, TEST_PRESET_INDEX);
+    }
+
+    @Test
+    public void selectPreset_supportSynchronized_invalidGroupId_callSelectPresetIndividual() {
+        setValidHearingDeviceSupportHap();
+        when(mHapClientProfile.supportsSynchronizedPresets(mBluetoothDevice)).thenReturn(true);
+        when(mHapClientProfile.getHapGroup(mBluetoothDevice)).thenReturn(
+                BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
+
+        mController.selectPreset(TEST_PRESET_INDEX);
+
+        verify(mHapClientProfile).selectPreset(mBluetoothDevice, TEST_PRESET_INDEX);
+        verify(mHapClientProfile).selectPreset(mSubBluetoothDevice, TEST_PRESET_INDEX);
+    }
+
+    @Test
+    public void selectPreset_notSupportSynchronized_validGroupId_callSelectPresetIndividual() {
+        setValidHearingDeviceSupportHap();
+        when(mHapClientProfile.supportsSynchronizedPresets(mBluetoothDevice)).thenReturn(false);
+        when(mHapClientProfile.getHapGroup(mBluetoothDevice)).thenReturn(TEST_HAP_GROUP_ID);
+
+        mController.selectPreset(TEST_PRESET_INDEX);
+
+        verify(mHapClientProfile).selectPreset(mBluetoothDevice, TEST_PRESET_INDEX);
+        verify(mHapClientProfile).selectPreset(mSubBluetoothDevice, TEST_PRESET_INDEX);
+    }
+
+    private BluetoothHapPresetInfo getHapPresetInfo(boolean available) {
+        BluetoothHapPresetInfo info = mock(BluetoothHapPresetInfo.class);
+        when(info.getName()).thenReturn(TEST_PRESET_NAME);
+        when(info.getIndex()).thenReturn(TEST_PRESET_INDEX);
+        when(info.isAvailable()).thenReturn(available);
+        return info;
+    }
+
+    private void setValidHearingDeviceSupportHap() {
+        LocalBluetoothProfile hapClientProfile = mock(HapClientProfile.class);
+        List<LocalBluetoothProfile> profiles = List.of(hapClientProfile);
+        when(mCachedBluetoothDevice.getProfiles()).thenReturn(profiles);
+
+        mController.setHearingDeviceIfSupportHap(mCachedBluetoothDevice);
+    }
+}
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..a18d272 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
@@ -711,6 +711,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 dc69cda..f94a6f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -29,7 +29,6 @@
 import android.hardware.face.FaceSensorPropertiesInternal
 import android.hardware.fingerprint.FingerprintManager
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
-import android.os.Handler
 import android.os.IBinder
 import android.os.UserManager
 import android.testing.TestableLooper
@@ -45,7 +44,6 @@
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.widget.LockPatternUtils
 import com.android.launcher3.icons.IconProvider
-import com.android.systemui.Flags.FLAG_CONSTRAINT_BP
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.data.repository.FakeBiometricStatusRepository
 import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
@@ -105,7 +103,6 @@
     @Mock lateinit var fingerprintManager: FingerprintManager
     @Mock lateinit var lockPatternUtils: LockPatternUtils
     @Mock lateinit var wakefulnessLifecycle: WakefulnessLifecycle
-    @Mock lateinit var panelInteractionDetector: AuthDialogPanelInteractionDetector
     @Mock lateinit var windowToken: IBinder
     @Mock lateinit var interactionJankMonitor: InteractionJankMonitor
     @Mock lateinit var vibrator: VibratorHelper
@@ -265,14 +262,6 @@
     }
 
     @Test
-    fun testActionCancel_panelInteractionDetectorDisable() {
-        val container = initializeFingerprintContainer()
-        container.mBiometricCallback.onUserCanceled()
-        waitForIdleSync()
-        verify(panelInteractionDetector).disable()
-    }
-
-    @Test
     fun testActionAuthenticated_sendsDismissedAuthenticated() {
         val container = initializeFingerprintContainer()
         container.mBiometricCallback.onAuthenticated()
@@ -416,19 +405,7 @@
     }
 
     @Test
-    fun testShowBiometricUI() {
-        mSetFlagsRule.disableFlags(FLAG_CONSTRAINT_BP)
-        val container = initializeFingerprintContainer()
-
-        waitForIdleSync()
-
-        assertThat(container.hasCredentialView()).isFalse()
-        assertThat(container.hasBiometricPrompt()).isTrue()
-    }
-
-    @Test
     fun testShowBiometricUI_ContentViewWithMoreOptionsButton() {
-        mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
         mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
         var isButtonClicked = false
         val contentView =
@@ -466,7 +443,6 @@
 
     @Test
     fun testShowCredentialUI_withVerticalListContentView() {
-        mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
         mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
         val container =
             initializeFingerprintContainer(
@@ -488,7 +464,6 @@
 
     @Test
     fun testShowCredentialUI_withContentViewWithMoreOptionsButton() {
-        mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
         mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
         val contentView =
             PromptContentViewWithMoreOptionsButton.Builder()
@@ -674,7 +649,6 @@
             fingerprintProps,
             faceProps,
             wakefulnessLifecycle,
-            panelInteractionDetector,
             userManager,
             lockPatternUtils,
             interactionJankMonitor,
@@ -690,7 +664,6 @@
                 activityTaskManager
             ),
             { credentialViewModel },
-            Handler(TestableLooper.get(this).looper),
             fakeExecutor,
             vibrator
         ) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
deleted file mode 100644
index 0231486..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
+++ /dev/null
@@ -1,156 +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.biometrics
-
-import android.platform.test.flag.junit.FlagsParameterization
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.andSceneContainer
-import com.android.systemui.kosmos.applicationCoroutineScope
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.shade.domain.interactor.shadeInteractor
-import com.android.systemui.shade.shadeTestUtil
-import com.android.systemui.testKosmos
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-import org.mockito.kotlin.verifyZeroInteractions
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4
-import platform.test.runner.parameterized.Parameters
-
-@SmallTest
-@RunWith(ParameterizedAndroidJunit4::class)
-class AuthDialogPanelInteractionDetectorTest(flags: FlagsParameterization?) : SysuiTestCase() {
-
-    companion object {
-        @JvmStatic
-        @Parameters(name = "{0}")
-        fun getParams(): List<FlagsParameterization> {
-            return FlagsParameterization.allCombinationsOf().andSceneContainer()
-        }
-    }
-
-    init {
-        mSetFlagsRule.setFlagsParameterization(flags!!)
-    }
-
-    private val kosmos = testKosmos()
-    private val testScope = kosmos.testScope
-
-    private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
-
-    @Mock private lateinit var action: Runnable
-
-    lateinit var detector: AuthDialogPanelInteractionDetector
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        detector =
-            AuthDialogPanelInteractionDetector(
-                kosmos.applicationCoroutineScope,
-                { kosmos.shadeInteractor },
-            )
-    }
-
-    @Test
-    fun enableDetector_expand_shouldRunAction() =
-        testScope.runTest {
-            // GIVEN shade is closed and detector is enabled
-            shadeTestUtil.setShadeExpansion(0f)
-            detector.enable(action)
-            runCurrent()
-
-            // WHEN shade expands
-            shadeTestUtil.setTracking(true)
-            shadeTestUtil.setShadeExpansion(.5f)
-            runCurrent()
-
-            // THEN action was run
-            verify(action).run()
-        }
-
-    @Test
-    fun enableDetector_isUserInteractingTrue_shouldNotPostRunnable() =
-        testScope.runTest {
-            // GIVEN isInteracting starts true
-            shadeTestUtil.setTracking(true)
-            runCurrent()
-            detector.enable(action)
-
-            // THEN action was not run
-            verifyZeroInteractions(action)
-        }
-
-    @Test
-    fun enableDetector_shadeExpandImmediate_shouldNotPostRunnable() =
-        testScope.runTest {
-            // GIVEN shade is closed and detector is enabled
-            shadeTestUtil.setShadeExpansion(0f)
-            detector.enable(action)
-            runCurrent()
-
-            // WHEN shade expands fully instantly
-            shadeTestUtil.setShadeExpansion(1f)
-            runCurrent()
-
-            // THEN action not run
-            verifyZeroInteractions(action)
-            detector.disable()
-        }
-
-    @Test
-    fun disableDetector_shouldNotPostRunnable() =
-        testScope.runTest {
-            // GIVEN shade is closed and detector is enabled
-            shadeTestUtil.setShadeExpansion(0f)
-            detector.enable(action)
-            runCurrent()
-
-            // WHEN detector is disabled and shade opens
-            detector.disable()
-            runCurrent()
-            shadeTestUtil.setTracking(true)
-            shadeTestUtil.setShadeExpansion(.5f)
-            runCurrent()
-
-            // THEN action not run
-            verifyZeroInteractions(action)
-        }
-
-    @Test
-    fun enableDetector_beginCollapse_shouldNotPostRunnable() =
-        testScope.runTest {
-            // GIVEN shade is open and detector is enabled
-            shadeTestUtil.setShadeExpansion(1f)
-            detector.enable(action)
-            runCurrent()
-
-            // WHEN shade begins to collapse
-            shadeTestUtil.programmaticCollapseShade()
-            runCurrent()
-
-            // THEN action not run
-            verifyZeroInteractions(action)
-            detector.disable()
-        }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
index 720f207..dc499cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
@@ -155,7 +155,7 @@
                         Authenticators.BIOMETRIC_STRONG
                     }
                 isDeviceCredentialAllowed = allowCredentialFallback
-                componentNameForConfirmDeviceCredentialActivity =
+                realCallerForConfirmDeviceCredentialActivity =
                     if (setComponentNameForConfirmDeviceCredentialActivity)
                         componentNameOverriddenForConfirmDeviceCredentialActivity
                     else null
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 512d946..6047e7d 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
@@ -43,7 +43,6 @@
 import android.view.Surface
 import androidx.test.filters.SmallTest
 import com.android.app.activityTaskManager
-import com.android.systemui.Flags.FLAG_CONSTRAINT_BP
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.biometrics.Utils.toBitmap
@@ -503,11 +502,13 @@
                     collectLastValue(kosmos.promptViewModel.iconViewModel.contentDescriptionId)
                 val shouldAnimateIconView by
                     collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconView)
+                val message by collectLastValue(kosmos.promptViewModel.message)
                 verifyIconSize()
 
                 kosmos.promptViewModel.showAuthenticated(
                     modality = testCase.authenticatedModality,
-                    dismissAfterDelay = DELAY
+                    dismissAfterDelay = DELAY,
+                    "TEST"
                 )
 
                 if (testCase.isFingerprintOnly) {
@@ -531,6 +532,7 @@
                     assertThat(iconContentDescriptionId)
                         .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticated)
                     assertThat(shouldAnimateIconView).isEqualTo(true)
+                    assertThat(message).isEqualTo(PromptMessage.Empty)
                 }
             }
         }
@@ -1382,7 +1384,7 @@
     }
 
     @Test
-    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
+    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
     fun descriptionOverriddenByVerticalListContentView() =
         runGenericTest(description = "test description", contentView = promptContentView) {
             val contentView by collectLastValue(kosmos.promptViewModel.contentView)
@@ -1393,7 +1395,7 @@
         }
 
     @Test
-    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
+    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
     fun descriptionOverriddenByContentViewWithMoreOptionsButton() =
         runGenericTest(
             description = "test description",
@@ -1407,7 +1409,7 @@
         }
 
     @Test
-    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
+    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
     fun descriptionWithoutContentView() =
         runGenericTest(description = "test description") {
             val contentView by collectLastValue(kosmos.promptViewModel.contentView)
@@ -1418,7 +1420,7 @@
         }
 
     @Test
-    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
+    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
     fun logo_nullIfPkgNameNotFound() =
         runGenericTest(packageName = OP_PACKAGE_NAME_CAN_NOT_BE_FOUND) {
             val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
@@ -1427,7 +1429,7 @@
         }
 
     @Test
-    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
+    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
     fun logo_defaultFromActivityInfo() =
         runGenericTest(packageName = OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO) {
             val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
@@ -1442,7 +1444,7 @@
         }
 
     @Test
-    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
+    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
     fun logo_defaultIsNull() =
         runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) {
             val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
@@ -1451,7 +1453,7 @@
         }
 
     @Test
-    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
+    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
     fun logo_default() = runGenericTest {
         val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
         assertThat(logoInfo).isNotNull()
@@ -1459,7 +1461,7 @@
     }
 
     @Test
-    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
+    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
     fun logo_resSetByApp() =
         runGenericTest(logoRes = logoResFromApp) {
             val expectedBitmap = context.getDrawable(logoResFromApp).toBitmap()
@@ -1469,7 +1471,7 @@
         }
 
     @Test
-    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
+    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
     fun logo_bitmapSetByApp() =
         runGenericTest(logoBitmap = logoBitmapFromApp) {
             val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
@@ -1477,7 +1479,7 @@
         }
 
     @Test
-    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
+    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
     fun logoDescription_emptyIfPkgNameNotFound() =
         runGenericTest(packageName = OP_PACKAGE_NAME_CAN_NOT_BE_FOUND) {
             val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
@@ -1485,7 +1487,7 @@
         }
 
     @Test
-    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
+    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
     fun logoDescription_defaultFromActivityInfo() =
         runGenericTest(packageName = OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO) {
             val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
@@ -1497,7 +1499,7 @@
         }
 
     @Test
-    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
+    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
     fun logoDescription_defaultIsEmpty() =
         runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) {
             val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
@@ -1505,14 +1507,14 @@
         }
 
     @Test
-    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
+    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
     fun logoDescription_default() = runGenericTest {
         val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
         assertThat(logoInfo!!.second).isEqualTo(defaultLogoDescriptionFromAppInfo)
     }
 
     @Test
-    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
+    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
     fun logoDescription_setByApp() =
         runGenericTest(logoDescription = logoDescriptionFromApp) {
             val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
@@ -1520,7 +1522,6 @@
         }
 
     @Test
-    @EnableFlags(FLAG_CONSTRAINT_BP)
     fun position_bottom_rotation0() = runGenericTest {
         kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_0)
         val position by collectLastValue(kosmos.promptViewModel.position)
@@ -1528,7 +1529,6 @@
     } // TODO(b/335278136): Add test for no sensor landscape
 
     @Test
-    @EnableFlags(FLAG_CONSTRAINT_BP)
     fun position_bottom_forceLarge() = runGenericTest {
         kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270)
         kosmos.promptViewModel.onSwitchToCredential()
@@ -1537,7 +1537,6 @@
     }
 
     @Test
-    @EnableFlags(FLAG_CONSTRAINT_BP)
     fun position_bottom_largeScreen() = runGenericTest {
         kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270)
         kosmos.displayStateRepository.setIsLargeScreen(true)
@@ -1546,7 +1545,6 @@
     }
 
     @Test
-    @EnableFlags(FLAG_CONSTRAINT_BP)
     fun position_right_rotation90() = runGenericTest {
         kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90)
         val position by collectLastValue(kosmos.promptViewModel.position)
@@ -1554,7 +1552,6 @@
     }
 
     @Test
-    @EnableFlags(FLAG_CONSTRAINT_BP)
     fun position_left_rotation270() = runGenericTest {
         kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270)
         val position by collectLastValue(kosmos.promptViewModel.position)
@@ -1562,7 +1559,6 @@
     }
 
     @Test
-    @EnableFlags(FLAG_CONSTRAINT_BP)
     fun position_top_rotation180() = runGenericTest {
         kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_180)
         val position by collectLastValue(kosmos.promptViewModel.position)
@@ -1574,7 +1570,6 @@
     }
 
     @Test
-    @EnableFlags(FLAG_CONSTRAINT_BP)
     fun guideline_bottom() = runGenericTest {
         kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_0)
         val guidelineBounds by collectLastValue(kosmos.promptViewModel.guidelineBounds)
@@ -1582,7 +1577,6 @@
     } // TODO(b/335278136): Add test for no sensor landscape
 
     @Test
-    @EnableFlags(FLAG_CONSTRAINT_BP)
     fun guideline_right() = runGenericTest {
         kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90)
 
@@ -1599,7 +1593,6 @@
     }
 
     @Test
-    @EnableFlags(FLAG_CONSTRAINT_BP)
     fun guideline_right_onlyShortTitle() =
         runGenericTest(subtitle = "") {
             kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90)
@@ -1614,7 +1607,6 @@
         }
 
     @Test
-    @EnableFlags(FLAG_CONSTRAINT_BP)
     fun guideline_left() = runGenericTest {
         kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270)
 
@@ -1631,7 +1623,6 @@
     }
 
     @Test
-    @EnableFlags(FLAG_CONSTRAINT_BP)
     fun guideline_left_onlyShortTitle() =
         runGenericTest(subtitle = "") {
             kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270)
@@ -1646,7 +1637,6 @@
         }
 
     @Test
-    @EnableFlags(FLAG_CONSTRAINT_BP)
     fun guideline_top() = runGenericTest {
         kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_180)
         val guidelineBounds by collectLastValue(kosmos.promptViewModel.guidelineBounds)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
index 3b2cf61..0db7b62 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
@@ -32,7 +32,6 @@
 import com.airbnb.lottie.model.KeyPath
 import com.android.keyguard.keyguardUpdateMonitor
 import com.android.settingslib.Utils
-import com.android.systemui.Flags.FLAG_CONSTRAINT_BP
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider
 import com.android.systemui.biometrics.data.repository.biometricStatusRepository
@@ -199,7 +198,6 @@
     @Test
     fun updatesOverlayViewParams_onDisplayRotationChange_xAlignedSensor() {
         kosmos.testScope.runTest {
-            mSetFlagsRule.disableFlags(FLAG_CONSTRAINT_BP)
             setupTestConfiguration(
                 DeviceConfig.X_ALIGNED,
                 rotation = DisplayRotation.ROTATION_0,
@@ -230,11 +228,11 @@
                 .isEqualTo(
                     displayWidth - sensorLocation.sensorLocationX - sensorLocation.sensorRadius * 2
                 )
-            assertThat(overlayViewParams!!.y).isEqualTo(displayHeight - boundsHeight)
+            assertThat(overlayViewParams!!.y).isEqualTo(displayHeight)
 
             kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270)
             assertThat(overlayViewParams).isNotNull()
-            assertThat(overlayViewParams!!.x).isEqualTo(displayWidth - boundsWidth)
+            assertThat(overlayViewParams!!.x).isEqualTo(displayWidth)
             assertThat(overlayViewParams!!.y).isEqualTo(sensorLocation.sensorLocationX)
         }
     }
@@ -242,7 +240,6 @@
     @Test
     fun updatesOverlayViewParams_onDisplayRotationChange_yAlignedSensor() {
         kosmos.testScope.runTest {
-            mSetFlagsRule.disableFlags(FLAG_CONSTRAINT_BP)
             setupTestConfiguration(
                 DeviceConfig.Y_ALIGNED,
                 rotation = DisplayRotation.ROTATION_0,
@@ -256,7 +253,7 @@
             runCurrent()
 
             assertThat(overlayViewParams).isNotNull()
-            assertThat(overlayViewParams!!.x).isEqualTo(displayWidth - boundsWidth)
+            assertThat(overlayViewParams!!.x).isEqualTo(displayWidth)
             assertThat(overlayViewParams!!.y).isEqualTo(sensorLocation.sensorLocationY)
 
             kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90)
@@ -278,7 +275,7 @@
                 .isEqualTo(
                     displayWidth - sensorLocation.sensorLocationY - sensorLocation.sensorRadius * 2
                 )
-            assertThat(overlayViewParams!!.y).isEqualTo(displayHeight - boundsHeight)
+            assertThat(overlayViewParams!!.y).isEqualTo(displayHeight)
         }
     }
 
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 8246506..74bc928 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
@@ -208,8 +208,8 @@
         with(kosmos) {
             testScope.runTest {
                 whenever(cachedBluetoothDevice.address).thenReturn(DEVICE_ADDRESS)
-                whenever(cachedBluetoothDevice.connectableProfiles)
-                        .thenReturn(listOf(leAudioProfile))
+                whenever(cachedBluetoothDevice.uiAccessibleProfiles)
+                    .thenReturn(listOf(leAudioProfile))
 
                 whenever(BluetoothUtils.isAudioSharingEnabled()).thenReturn(true)
                 whenever(localBluetoothManager.profileManager).thenReturn(profileManager)
@@ -243,8 +243,8 @@
             testScope.runTest {
                 whenever(cachedBluetoothDevice.address).thenReturn(DEVICE_ADDRESS)
                 whenever(cachedBluetoothDevice.device).thenReturn(bluetoothDevice)
-                whenever(cachedBluetoothDevice.connectableProfiles)
-                        .thenReturn(listOf(leAudioProfile))
+                whenever(cachedBluetoothDevice.uiAccessibleProfiles)
+                    .thenReturn(listOf(leAudioProfile))
 
                 whenever(BluetoothUtils.isAudioSharingEnabled()).thenReturn(true)
                 whenever(localBluetoothManager.profileManager).thenReturn(profileManager)
@@ -254,12 +254,12 @@
 
                 whenever(BluetoothUtils.isBroadcasting(ArgumentMatchers.any())).thenReturn(true)
                 whenever(
-                    BluetoothUtils.hasConnectedBroadcastSource(
-                        ArgumentMatchers.any(),
-                        ArgumentMatchers.any()
+                        BluetoothUtils.hasConnectedBroadcastSource(
+                            ArgumentMatchers.any(),
+                            ArgumentMatchers.any()
+                        )
                     )
-                )
-                        .thenReturn(false)
+                    .thenReturn(false)
 
                 actionInteractorImpl.onClick(connectedMediaDeviceItem, dialog)
                 verify(activityStarter)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
index 79e312f..77337d3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
@@ -21,8 +21,12 @@
 import android.hardware.fingerprint.FingerprintManager
 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.bouncer.data.repository.keyguardBouncerRepository
+import com.android.systemui.communal.data.repository.communalSceneRepository
+import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
+import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
@@ -41,6 +45,7 @@
 import com.android.systemui.util.mockito.any
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
@@ -65,6 +70,7 @@
     private val bouncerRepository = kosmos.keyguardBouncerRepository
     private val powerRepository = kosmos.fakePowerRepository
     private val biometricSettingsRepository = kosmos.biometricSettingsRepository
+    private val communalSceneRepository = kosmos.communalSceneRepository
     private val mockedContext = kosmos.mockedContext
     private val mockedActivityStarter = kosmos.activityStarter
 
@@ -143,6 +149,20 @@
         }
 
     @Test
+    fun lockout_onOccludingApp_onCommunal_neverGoToHomeScreen() =
+        testScope.runTest {
+            givenOnOccludingApp(isOnOccludingApp = true, isOnCommunal = true)
+            fingerprintAuthRepository.setAuthenticationStatus(
+                ErrorFingerprintAuthenticationStatus(
+                    FingerprintManager.FINGERPRINT_ERROR_LOCKOUT,
+                    "lockoutTest"
+                )
+            )
+            runCurrent()
+            verifyNeverGoToHomeScreen()
+        }
+
+    @Test
     fun message_fpFailOnOccludingApp_thenNotOnOccludingApp() =
         testScope.runTest {
             val message by collectLastValue(underTest.message)
@@ -261,7 +281,10 @@
             assertThat(message).isNull()
         }
 
-    private suspend fun givenOnOccludingApp(isOnOccludingApp: Boolean) {
+    private suspend fun givenOnOccludingApp(
+        isOnOccludingApp: Boolean,
+        isOnCommunal: Boolean = false
+    ) {
         powerRepository.setInteractive(true)
         keyguardRepository.setIsDozing(false)
         keyguardRepository.setKeyguardOccluded(isOnOccludingApp)
@@ -270,6 +293,14 @@
         bouncerRepository.setPrimaryShow(!isOnOccludingApp)
         bouncerRepository.setAlternateVisible(!isOnOccludingApp)
 
+        kosmos.fakeCommunalSceneRepository.setTransitionState(
+            flowOf(
+                ObservableTransitionState.Idle(
+                    if (isOnCommunal) CommunalScenes.Communal else CommunalScenes.Blank
+                )
+            )
+        )
+
         if (isOnOccludingApp) {
             kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
                 from = KeyguardState.LOCKSCREEN,
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 20cb1e1..0ac04b6 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
@@ -28,6 +28,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.FlowValue
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
 import com.android.systemui.util.mockito.kotlinArgumentCaptor
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
@@ -456,8 +457,20 @@
             assertThat(value?.ids()).containsExactly(DEFAULT_DISPLAY)
         }
 
+    @Test
+    fun displayFlow_emitsCorrectDisplaysAtFirst() =
+        testScope.runTest {
+            setDisplays(0, 1, 2)
+
+            val values: List<Set<Display>> by collectValues(displayRepository.displays)
+
+            assertThat(values.toIdSets()).containsExactly(setOf(0, 1, 2))
+        }
+
     private fun Iterable<Display>.ids(): List<Int> = map { it.displayId }
 
+    private fun Iterable<Set<Display>>.toIdSets(): List<Set<Int>> = map { it.ids().toSet() }
+
     // Wrapper to capture the displayListener.
     private fun TestScope.latestDisplayFlowValue(): FlowValue<Set<Display>?> {
         val flowValue = collectLastValue(displayRepository.displays)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStatePreventingAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStatePreventingAdapterTest.java
index 419f7ed..299c384 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStatePreventingAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStatePreventingAdapterTest.java
@@ -29,31 +29,22 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.util.concurrent.Executor;
-
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class DozeScreenStatePreventingAdapterTest extends SysuiTestCase {
 
-    private Executor mExecutor;
     private DozeMachine.Service mInner;
     private DozeScreenStatePreventingAdapter mWrapper;
 
     @Before
     public void setup() throws Exception {
-        mExecutor = new FakeExecutor(new FakeSystemClock());
         mInner = mock(DozeMachine.Service.class);
-        mWrapper = new DozeScreenStatePreventingAdapter(
-                mInner,
-                mExecutor
-        );
+        mWrapper = new DozeScreenStatePreventingAdapter(mInner);
     }
 
     @Test
@@ -98,7 +89,7 @@
         when(params.getDisplayStateSupported()).thenReturn(false);
 
         assertEquals(DozeScreenStatePreventingAdapter.class,
-                DozeScreenStatePreventingAdapter.wrapIfNeeded(mInner, params, mExecutor)
+                DozeScreenStatePreventingAdapter.wrapIfNeeded(mInner, params)
                         .getClass());
     }
 
@@ -107,7 +98,6 @@
         DozeParameters params = mock(DozeParameters.class);
         when(params.getDisplayStateSupported()).thenReturn(true);
 
-        assertSame(mInner, DozeScreenStatePreventingAdapter.wrapIfNeeded(mInner, params,
-                mExecutor));
+        assertSame(mInner, DozeScreenStatePreventingAdapter.wrapIfNeeded(mInner, params));
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapterTest.java
index 5a89710..0d6a9ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapterTest.java
@@ -29,28 +29,22 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.util.concurrent.Executor;
-
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class DozeSuspendScreenStatePreventingAdapterTest extends SysuiTestCase {
 
-    private Executor mExecutor;
     private DozeMachine.Service mInner;
     private DozeSuspendScreenStatePreventingAdapter mWrapper;
 
     @Before
     public void setup() throws Exception {
-        mExecutor = new FakeExecutor(new FakeSystemClock());
         mInner = mock(DozeMachine.Service.class);
-        mWrapper = new DozeSuspendScreenStatePreventingAdapter(mInner, mExecutor);
+        mWrapper = new DozeSuspendScreenStatePreventingAdapter(mInner);
     }
 
     @Test
@@ -101,7 +95,7 @@
         when(params.getDozeSuspendDisplayStateSupported()).thenReturn(false);
 
         assertEquals(DozeSuspendScreenStatePreventingAdapter.class,
-                DozeSuspendScreenStatePreventingAdapter.wrapIfNeeded(mInner, params, mExecutor)
+                DozeSuspendScreenStatePreventingAdapter.wrapIfNeeded(mInner, params)
                         .getClass());
     }
 
@@ -110,7 +104,6 @@
         DozeParameters params = mock(DozeParameters.class);
         when(params.getDozeSuspendDisplayStateSupported()).thenReturn(true);
 
-        assertSame(mInner, DozeSuspendScreenStatePreventingAdapter.wrapIfNeeded(mInner, params,
-                mExecutor));
+        assertSame(mInner, DozeSuspendScreenStatePreventingAdapter.wrapIfNeeded(mInner, params));
     }
 }
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 e3a38a8..37f1a3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -443,7 +443,7 @@
         mViewMediator.onSystemReady();
         TestableLooper.get(this).processAllMessages();
 
-        mViewMediator.setShowingLocked(false);
+        mViewMediator.setShowingLocked(false, "");
         TestableLooper.get(this).processAllMessages();
 
         mViewMediator.onStartedGoingToSleep(OFF_BECAUSE_OF_USER);
@@ -463,7 +463,7 @@
         mViewMediator.onSystemReady();
         TestableLooper.get(this).processAllMessages();
 
-        mViewMediator.setShowingLocked(false);
+        mViewMediator.setShowingLocked(false, "");
         TestableLooper.get(this).processAllMessages();
 
         mViewMediator.onStartedGoingToSleep(OFF_BECAUSE_OF_USER);
@@ -570,7 +570,7 @@
         // When showing and provisioned
         mViewMediator.onSystemReady();
         when(mUpdateMonitor.isDeviceProvisioned()).thenReturn(true);
-        mViewMediator.setShowingLocked(true);
+        mViewMediator.setShowingLocked(true, "");
 
         // and a SIM becomes locked and requires a PIN
         mViewMediator.mUpdateCallback.onSimStateChanged(
@@ -579,7 +579,7 @@
                 TelephonyManager.SIM_STATE_PIN_REQUIRED);
 
         // and the keyguard goes away
-        mViewMediator.setShowingLocked(false);
+        mViewMediator.setShowingLocked(false, "");
         when(mKeyguardStateController.isShowing()).thenReturn(false);
         mViewMediator.mUpdateCallback.onKeyguardVisibilityChanged(false);
 
@@ -595,7 +595,7 @@
         // When showing and provisioned
         mViewMediator.onSystemReady();
         when(mUpdateMonitor.isDeviceProvisioned()).thenReturn(true);
-        mViewMediator.setShowingLocked(false);
+        mViewMediator.setShowingLocked(false, "");
 
         // and a SIM becomes locked and requires a PIN
         mViewMediator.mUpdateCallback.onSimStateChanged(
@@ -604,7 +604,7 @@
                 TelephonyManager.SIM_STATE_PIN_REQUIRED);
 
         // and the keyguard goes away
-        mViewMediator.setShowingLocked(false);
+        mViewMediator.setShowingLocked(false, "");
         when(mKeyguardStateController.isShowing()).thenReturn(false);
         mViewMediator.mUpdateCallback.onKeyguardVisibilityChanged(false);
 
@@ -843,7 +843,7 @@
         mViewMediator.onSystemReady();
         TestableLooper.get(this).processAllMessages();
 
-        mViewMediator.setShowingLocked(false);
+        mViewMediator.setShowingLocked(false, "");
         TestableLooper.get(this).processAllMessages();
 
         mViewMediator.onStartedGoingToSleep(OFF_BECAUSE_OF_USER);
@@ -863,7 +863,7 @@
         mViewMediator.onSystemReady();
         TestableLooper.get(this).processAllMessages();
 
-        mViewMediator.setShowingLocked(false);
+        mViewMediator.setShowingLocked(false, "");
         TestableLooper.get(this).processAllMessages();
 
         mViewMediator.onStartedGoingToSleep(OFF_BECAUSE_OF_USER);
@@ -978,7 +978,7 @@
     @Test
     @TestableLooper.RunWithLooper(setAsMainLooper = true)
     public void testDoKeyguardWhileInteractive_resets() {
-        mViewMediator.setShowingLocked(true);
+        mViewMediator.setShowingLocked(true, "");
         when(mKeyguardStateController.isShowing()).thenReturn(true);
         TestableLooper.get(this).processAllMessages();
 
@@ -992,7 +992,7 @@
     @Test
     @TestableLooper.RunWithLooper(setAsMainLooper = true)
     public void testDoKeyguardWhileNotInteractive_showsInsteadOfResetting() {
-        mViewMediator.setShowingLocked(true);
+        mViewMediator.setShowingLocked(true, "");
         when(mKeyguardStateController.isShowing()).thenReturn(true);
         TestableLooper.get(this).processAllMessages();
 
@@ -1051,7 +1051,7 @@
         mViewMediator.onSystemReady();
         processAllMessagesAndBgExecutorMessages();
 
-        mViewMediator.setShowingLocked(true);
+        mViewMediator.setShowingLocked(true, "");
 
         RemoteAnimationTarget[] apps = new RemoteAnimationTarget[]{
                 mock(RemoteAnimationTarget.class)
@@ -1123,7 +1123,7 @@
     @Test
     @TestableLooper.RunWithLooper(setAsMainLooper = true)
     public void testNotStartingKeyguardWhenFlagIsDisabled() {
-        mViewMediator.setShowingLocked(false);
+        mViewMediator.setShowingLocked(false, "");
         when(mKeyguardStateController.isShowing()).thenReturn(false);
 
         mViewMediator.onDreamingStarted();
@@ -1133,7 +1133,7 @@
     @Test
     @TestableLooper.RunWithLooper(setAsMainLooper = true)
     public void testStartingKeyguardWhenFlagIsEnabled() {
-        mViewMediator.setShowingLocked(true);
+        mViewMediator.setShowingLocked(true, "");
         when(mKeyguardStateController.isShowing()).thenReturn(true);
 
         mViewMediator.onDreamingStarted();
@@ -1174,7 +1174,7 @@
         TestableLooper.get(this).processAllMessages();
 
         // WHEN keyguard visibility becomes FALSE
-        mViewMediator.setShowingLocked(false);
+        mViewMediator.setShowingLocked(false, "");
         keyguardUpdateMonitorCallback.onKeyguardVisibilityChanged(false);
         TestableLooper.get(this).processAllMessages();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
index 2af4d87..bfe89de 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
@@ -17,9 +17,6 @@
 package com.android.systemui.keyguard.data.repository
 
 import android.animation.ValueAnimator
-import android.util.Log
-import android.util.Log.TerribleFailure
-import android.util.Log.TerribleFailureHandler
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.FlakyTest
 import androidx.test.filters.SmallTest
@@ -53,7 +50,6 @@
 import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
-import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -67,23 +63,14 @@
     private val testScope = kosmos.testScope
 
     private lateinit var underTest: KeyguardTransitionRepository
-    private lateinit var oldWtfHandler: TerribleFailureHandler
-    private lateinit var wtfHandler: WtfHandler
     private lateinit var runner: KeyguardTransitionRunner
 
     @Before
     fun setUp() {
         underTest = KeyguardTransitionRepositoryImpl(Dispatchers.Main)
-        wtfHandler = WtfHandler()
-        oldWtfHandler = Log.setWtfHandler(wtfHandler)
         runner = KeyguardTransitionRunner(underTest)
     }
 
-    @After
-    fun tearDown() {
-        oldWtfHandler?.let { Log.setWtfHandler(it) }
-    }
-
     @Test
     fun startTransitionRunsAnimatorToCompletion() =
         testScope.runTest {
@@ -333,15 +320,17 @@
         }
 
     @Test
-    fun attemptTomanuallyUpdateTransitionWithInvalidUUIDthrowsException() =
+    fun attemptTomanuallyUpdateTransitionWithInvalidUUIDEmitsNothing() =
         testScope.runTest {
+            val steps by collectValues(underTest.transitions.dropWhile { step -> step.from == OFF })
             underTest.updateTransition(UUID.randomUUID(), 0f, TransitionState.RUNNING)
-            assertThat(wtfHandler.failed).isTrue()
+            assertThat(steps.size).isEqualTo(0)
         }
 
     @Test
-    fun attemptToManuallyUpdateTransitionAfterFINISHEDstateThrowsException() =
+    fun attemptToManuallyUpdateTransitionAfterFINISHEDstateEmitsNothing() =
         testScope.runTest {
+            val steps by collectValues(underTest.transitions.dropWhile { step -> step.from == OFF })
             val uuid =
                 underTest.startTransition(
                     TransitionInfo(
@@ -356,12 +345,19 @@
                 underTest.updateTransition(it, 1f, TransitionState.FINISHED)
                 underTest.updateTransition(it, 0.5f, TransitionState.RUNNING)
             }
-            assertThat(wtfHandler.failed).isTrue()
+            assertThat(steps.size).isEqualTo(2)
+            assertThat(steps[0])
+                .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED, OWNER_NAME))
+            assertThat(steps[1])
+                .isEqualTo(
+                    TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED, OWNER_NAME)
+                )
         }
 
     @Test
-    fun attemptToManuallyUpdateTransitionAfterCANCELEDstateThrowsException() =
+    fun attemptToManuallyUpdateTransitionAfterCANCELEDstateEmitsNothing() =
         testScope.runTest {
+            val steps by collectValues(underTest.transitions.dropWhile { step -> step.from == OFF })
             val uuid =
                 underTest.startTransition(
                     TransitionInfo(
@@ -376,7 +372,13 @@
                 underTest.updateTransition(it, 0.2f, TransitionState.CANCELED)
                 underTest.updateTransition(it, 0.5f, TransitionState.RUNNING)
             }
-            assertThat(wtfHandler.failed).isTrue()
+            assertThat(steps.size).isEqualTo(2)
+            assertThat(steps[0])
+                .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED, OWNER_NAME))
+            assertThat(steps[1])
+                .isEqualTo(
+                    TransitionStep(AOD, LOCKSCREEN, 0.2f, TransitionState.CANCELED, OWNER_NAME)
+                )
         }
 
     @Test
@@ -530,8 +532,6 @@
             }
         assertThat(steps[steps.size - 1])
             .isEqualTo(TransitionStep(from, to, lastValue, status, OWNER_NAME))
-
-        assertThat(wtfHandler.failed).isFalse()
     }
 
     private fun getAnimator(): ValueAnimator {
@@ -541,14 +541,6 @@
         }
     }
 
-    private class WtfHandler : TerribleFailureHandler {
-        var failed = false
-
-        override fun onTerribleFailure(tag: String, what: TerribleFailure, system: Boolean) {
-            failed = true
-        }
-    }
-
     companion object {
         private const val OWNER_NAME = "KeyguardTransitionRunner"
     }
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 e44bc7b..d8e96bc 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
@@ -51,14 +51,14 @@
     @Test
     fun showPrimaryBouncer() =
         testScope.runTest {
-            underTest.showPrimaryBouncer()
+            underTest.onTapped()
             verify(statusBarKeyguardViewManager).showPrimaryBouncer(any())
         }
 
     @Test
     fun hideAlternateBouncer() =
         testScope.runTest {
-            underTest.hideAlternateBouncer()
+            underTest.onRemovedFromWindow()
             verify(statusBarKeyguardViewManager).hideAlternateBouncer(any())
         }
 
@@ -102,34 +102,6 @@
         }
 
     @Test
-    fun forcePluginOpen() =
-        testScope.runTest {
-            val forcePluginOpen by collectLastValue(underTest.forcePluginOpen)
-
-            transitionRepository.sendTransitionSteps(
-                listOf(
-                    stepToAlternateBouncer(0f, TransitionState.STARTED),
-                    stepToAlternateBouncer(.4f),
-                    stepToAlternateBouncer(.6f),
-                    stepToAlternateBouncer(1f),
-                ),
-                testScope,
-            )
-            assertThat(forcePluginOpen).isTrue()
-
-            transitionRepository.sendTransitionSteps(
-                listOf(
-                    stepFromAlternateBouncer(0f, TransitionState.STARTED),
-                    stepFromAlternateBouncer(.3f),
-                    stepFromAlternateBouncer(.6f),
-                    stepFromAlternateBouncer(1f),
-                ),
-                testScope,
-            )
-            assertThat(forcePluginOpen).isFalse()
-        }
-
-    @Test
     fun registerForDismissGestures() =
         testScope.runTest {
             val registerForDismissGestures by collectLastValue(underTest.registerForDismissGestures)
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 77977f3..24bea2c 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
@@ -195,9 +195,7 @@
         mSetFlagsRule.enableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
 
         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)
         keyguardInteractor = withDeps.keyguardInteractor
@@ -289,6 +287,7 @@
 
         underTest =
             KeyguardQuickAffordancesCombinedViewModel(
+                applicationScope = testScope.backgroundScope,
                 quickAffordanceInteractor =
                     KeyguardQuickAffordanceInteractor(
                         keyguardInteractor = keyguardInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index d5cd86e..c8cc6b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -50,14 +50,18 @@
 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
 import com.android.settingslib.media.LocalMediaManager;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.SysuiTestCaseExtKt;
 import com.android.systemui.animation.DialogTransitionAnimator;
 import com.android.systemui.broadcast.BroadcastSender;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.kosmos.Kosmos;
 import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
+import com.android.systemui.volume.panel.domain.interactor.VolumePanelGlobalStateInteractor;
+import com.android.systemui.volume.panel.domain.interactor.VolumePanelGlobalStateInteractorKosmosKt;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -73,6 +77,8 @@
 
     private static final String TEST_PACKAGE = "test_package";
 
+    private final Kosmos mKosmos = SysuiTestCaseExtKt.testKosmos(this);
+
     // Mock
     private MediaOutputBaseAdapter mMediaOutputBaseAdapter = mock(MediaOutputBaseAdapter.class);
     private MediaController mMediaController = mock(MediaController.class);
@@ -122,6 +128,9 @@
         when(mMediaController.getPackageName()).thenReturn(TEST_PACKAGE);
         mMediaControllers.add(mMediaController);
         when(mMediaSessionManager.getActiveSessions(any())).thenReturn(mMediaControllers);
+        VolumePanelGlobalStateInteractor volumePanelGlobalStateInteractor =
+                VolumePanelGlobalStateInteractorKosmosKt.getVolumePanelGlobalStateInteractor(
+                        mKosmos);
 
         mMediaOutputController =
                 new MediaOutputController(
@@ -139,6 +148,7 @@
                         mPowerExemptionManager,
                         mKeyguardManager,
                         mFlags,
+                        volumePanelGlobalStateInteractor,
                         mUserTracker);
 
         // Using a fake package will cause routing operations to fail, so we intercept
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
index 87d2245..189a561 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
@@ -51,14 +51,18 @@
 import com.android.settingslib.media.LocalMediaManager;
 import com.android.settingslib.media.MediaDevice;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.SysuiTestCaseExtKt;
 import com.android.systemui.animation.DialogTransitionAnimator;
 import com.android.systemui.broadcast.BroadcastSender;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.kosmos.Kosmos;
 import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
+import com.android.systemui.volume.panel.domain.interactor.VolumePanelGlobalStateInteractor;
+import com.android.systemui.volume.panel.domain.interactor.VolumePanelGlobalStateInteractorKosmosKt;
 
 import com.google.common.base.Strings;
 
@@ -81,6 +85,8 @@
     private static final String BROADCAST_CODE_TEST = "112233";
     private static final String BROADCAST_CODE_UPDATE_TEST = "11223344";
 
+    private final Kosmos mKosmos = SysuiTestCaseExtKt.testKosmos(this);
+
     // Mock
     private final MediaSessionManager mMediaSessionManager = mock(MediaSessionManager.class);
     private final LocalBluetoothManager mLocalBluetoothManager = mock(LocalBluetoothManager.class);
@@ -123,6 +129,9 @@
         when(mLocalBluetoothLeBroadcast.getProgramInfo()).thenReturn(BROADCAST_NAME_TEST);
         when(mLocalBluetoothLeBroadcast.getBroadcastCode()).thenReturn(
                 BROADCAST_CODE_TEST.getBytes(StandardCharsets.UTF_8));
+        VolumePanelGlobalStateInteractor volumePanelGlobalStateInteractor =
+                VolumePanelGlobalStateInteractorKosmosKt.getVolumePanelGlobalStateInteractor(
+                        mKosmos);
 
         mMediaOutputController =
                 new MediaOutputController(
@@ -140,6 +149,7 @@
                         mPowerExemptionManager,
                         mKeyguardManager,
                         mFlags,
+                        volumePanelGlobalStateInteractor,
                         mUserTracker);
         mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
         mMediaOutputBroadcastDialog = new MediaOutputBroadcastDialog(mContext, false,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index 856bc0b..714fad9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -72,15 +72,19 @@
 import com.android.settingslib.media.LocalMediaManager;
 import com.android.settingslib.media.MediaDevice;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.SysuiTestCaseExtKt;
 import com.android.systemui.animation.ActivityTransitionAnimator;
 import com.android.systemui.animation.DialogTransitionAnimator;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.kosmos.Kosmos;
 import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
+import com.android.systemui.volume.panel.domain.interactor.VolumePanelGlobalStateInteractor;
+import com.android.systemui.volume.panel.domain.interactor.VolumePanelGlobalStateInteractorKosmosKt;
 
 import com.google.common.collect.ImmutableList;
 
@@ -158,11 +162,16 @@
     @Mock
     private UserTracker mUserTracker;
 
+    private final Kosmos mKosmos = SysuiTestCaseExtKt.testKosmos(this);
+
     private FeatureFlags mFlags = mock(FeatureFlags.class);
     private View mDialogLaunchView = mock(View.class);
     private MediaOutputController.Callback mCallback = mock(MediaOutputController.Callback.class);
 
     final Notification mNotification = mock(Notification.class);
+    private final VolumePanelGlobalStateInteractor mVolumePanelGlobalStateInteractor =
+            VolumePanelGlobalStateInteractorKosmosKt.getVolumePanelGlobalStateInteractor(
+                    mKosmos);
 
     private Context mSpyContext;
     private String mPackageName = null;
@@ -194,6 +203,7 @@
         when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(
                 mCachedBluetoothDeviceManager);
 
+
         mMediaOutputController =
                 new MediaOutputController(
                         mSpyContext,
@@ -210,6 +220,7 @@
                         mPowerExemptionManager,
                         mKeyguardManager,
                         mFlags,
+                        mVolumePanelGlobalStateInteractor,
                         mUserTracker);
         mLocalMediaManager = spy(mMediaOutputController.mLocalMediaManager);
         when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(false);
@@ -304,6 +315,7 @@
                         mPowerExemptionManager,
                         mKeyguardManager,
                         mFlags,
+                        mVolumePanelGlobalStateInteractor,
                         mUserTracker);
 
         mMediaOutputController.start(mCb);
@@ -346,6 +358,7 @@
                         mPowerExemptionManager,
                         mKeyguardManager,
                         mFlags,
+                        mVolumePanelGlobalStateInteractor,
                         mUserTracker);
 
         mMediaOutputController.start(mCb);
@@ -602,6 +615,7 @@
                         mPowerExemptionManager,
                         mKeyguardManager,
                         mFlags,
+                        mVolumePanelGlobalStateInteractor,
                         mUserTracker);
         testMediaOutputController.start(mCb);
         reset(mCb);
@@ -636,6 +650,7 @@
                         mPowerExemptionManager,
                         mKeyguardManager,
                         mFlags,
+                        mVolumePanelGlobalStateInteractor,
                         mUserTracker);
         testMediaOutputController.start(mCb);
         reset(mCb);
@@ -683,6 +698,7 @@
                         mPowerExemptionManager,
                         mKeyguardManager,
                         mFlags,
+                        mVolumePanelGlobalStateInteractor,
                         mUserTracker);
 
         LocalMediaManager mockLocalMediaManager = mock(LocalMediaManager.class);
@@ -710,6 +726,7 @@
                         mPowerExemptionManager,
                         mKeyguardManager,
                         mFlags,
+                        mVolumePanelGlobalStateInteractor,
                         mUserTracker);
 
         LocalMediaManager mockLocalMediaManager = mock(LocalMediaManager.class);
@@ -990,6 +1007,7 @@
                         mPowerExemptionManager,
                         mKeyguardManager,
                         mFlags,
+                        mVolumePanelGlobalStateInteractor,
                         mUserTracker);
 
         assertThat(mMediaOutputController.getNotificationIcon()).isNull();
@@ -1193,6 +1211,7 @@
                         mPowerExemptionManager,
                         mKeyguardManager,
                         mFlags,
+                        mVolumePanelGlobalStateInteractor,
                         mUserTracker);
 
         testMediaOutputController.setTemporaryAllowListExceptionIfNeeded(mMediaDevice2);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
index f20b04a..90c2930 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
@@ -54,14 +54,18 @@
 import com.android.settingslib.media.LocalMediaManager;
 import com.android.settingslib.media.MediaDevice;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.SysuiTestCaseExtKt;
 import com.android.systemui.animation.DialogTransitionAnimator;
 import com.android.systemui.broadcast.BroadcastSender;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.kosmos.Kosmos;
 import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
+import com.android.systemui.volume.panel.domain.interactor.VolumePanelGlobalStateInteractor;
+import com.android.systemui.volume.panel.domain.interactor.VolumePanelGlobalStateInteractorKosmosKt;
 
 import org.junit.After;
 import org.junit.Before;
@@ -84,6 +88,8 @@
     @Rule
     public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
 
+    private final Kosmos mKosmos = SysuiTestCaseExtKt.testKosmos(this);
+
     // Mock
     private final MediaSessionManager mMediaSessionManager = mock(MediaSessionManager.class);
     private MediaController mMediaController = mock(MediaController.class);
@@ -136,6 +142,9 @@
         when(mMediaSessionManager.getActiveSessionsForUser(any(),
                 Mockito.eq(userHandle))).thenReturn(
                 mMediaControllers);
+        VolumePanelGlobalStateInteractor volumePanelGlobalStateInteractor =
+                VolumePanelGlobalStateInteractorKosmosKt.getVolumePanelGlobalStateInteractor(
+                        mKosmos);
 
         mMediaOutputController =
                 new MediaOutputController(
@@ -153,6 +162,7 @@
                         mPowerExemptionManager,
                         mKeyguardManager,
                         mFlags,
+                        volumePanelGlobalStateInteractor,
                         mUserTracker);
         mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
         mMediaOutputDialog = makeTestDialog(mMediaOutputController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt
index a73df07..9797c8c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt
@@ -104,6 +104,7 @@
             WindowConfiguration.WINDOWING_MODE_FULLSCREEN,
             /* appearance= */ 0,
             /* isTranslucent= */ false,
-            /* hasImeSurface= */ false
+            /* hasImeSurface= */ false,
+            /* uiMode */ 0
         )
 }
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 b337ccf..8fbd3c8 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
@@ -24,7 +24,6 @@
 import android.view.ViewGroup
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.systemui.Flags.FLAG_PSS_APP_SELECTOR_ABRUPT_EXIT_FIX
 import com.android.systemui.Flags.FLAG_PSS_APP_SELECTOR_RECENTS_SPLIT_SCREEN
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorResultHandler
@@ -141,27 +140,13 @@
     }
 
     @Test
-    fun onRecentAppClicked_fullScreenTaskInForeground_flagOff_usesScaleUpAnimation() {
-        mSetFlagsRule.disableFlags(FLAG_PSS_APP_SELECTOR_ABRUPT_EXIT_FIX)
-
-        controller.onRecentAppClicked(fullScreenTask, taskView)
-
-        assertThat(getStartedTaskActivityOptions(fullScreenTask.taskId).animationType)
-            .isEqualTo(ActivityOptions.ANIM_SCALE_UP)
-    }
-
-    @Test
-    fun onRecentAppClicked_fullScreenTaskInForeground_flagOn_usesDefaultAnimation() {
-        mSetFlagsRule.enableFlags(FLAG_PSS_APP_SELECTOR_ABRUPT_EXIT_FIX)
+    fun onRecentAppClicked_fullScreenTaskInForeground_usesDefaultAnimation() {
         assertForegroundTaskUsesDefaultCloseAnimation(fullScreenTask)
     }
 
     @Test
     fun onRecentAppClicked_splitScreenTaskInForeground_flagOn_usesDefaultAnimation() {
-        mSetFlagsRule.enableFlags(
-            FLAG_PSS_APP_SELECTOR_ABRUPT_EXIT_FIX,
-            FLAG_PSS_APP_SELECTOR_RECENTS_SPLIT_SCREEN
-        )
+        mSetFlagsRule.enableFlags(FLAG_PSS_APP_SELECTOR_RECENTS_SPLIT_SCREEN)
         assertForegroundTaskUsesDefaultCloseAnimation(splitScreenTask)
     }
 
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 a8cbbd4..04d140c 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
@@ -20,7 +20,6 @@
 import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
 import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN;
 import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT;
-import static android.inputmethodservice.InputMethodService.IME_INVISIBLE;
 import static android.inputmethodservice.InputMethodService.IME_VISIBLE;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
@@ -79,6 +78,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.SysuiTestCase;
@@ -215,6 +215,8 @@
     @Mock
     private WindowManager mWindowManager;
     @Mock
+    private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
+    @Mock
     private TelecomManager mTelecomManager;
     @Mock
     private InputMethodManager mInputMethodManager;
@@ -512,7 +514,7 @@
 
         externalNavBar.setImeWindowStatus(EXTERNAL_DISPLAY_ID, IME_VISIBLE,
                 BACK_DISPOSITION_DEFAULT, true);
-        defaultNavBar.setImeWindowStatus(DEFAULT_DISPLAY, IME_INVISIBLE,
+        defaultNavBar.setImeWindowStatus(DEFAULT_DISPLAY, 0 /* vis */,
                 BACK_DISPOSITION_DEFAULT, false);
         // Verify IME window state will be updated in external NavBar & default NavBar state reset.
         assertEquals(NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_SHOWN
@@ -620,6 +622,7 @@
                 null,
                 context,
                 mWindowManager,
+                mViewCaptureAwareWindowManager,
                 () -> mAssistManager,
                 mock(AccessibilityManager.class),
                 deviceProvisionedController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/UserSettingObserverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/UserSettingObserverTest.kt
index 90e0dd8..0c2b59f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/UserSettingObserverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/UserSettingObserverTest.kt
@@ -17,25 +17,34 @@
 package com.android.systemui.qs
 
 import android.os.Handler
+import android.platform.test.flag.junit.FlagsParameterization
 import android.testing.TestableLooper
-import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.fakeSettings
 import com.google.common.truth.Truth.assertThat
 import junit.framework.Assert.fail
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 private typealias Callback = (Int, Boolean) -> Unit
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(AndroidJUnit4::class)
+@RunWith(ParameterizedAndroidJunit4::class)
 @TestableLooper.RunWithLooper
-class UserSettingObserverTest : SysuiTestCase() {
+class UserSettingObserverTest(flags: FlagsParameterization) : SysuiTestCase() {
 
     companion object {
         private const val TEST_SETTING = "setting"
@@ -43,8 +52,23 @@
         private const val OTHER_USER = 1
         private const val DEFAULT_VALUE = 1
         private val FAIL_CALLBACK: Callback = { _, _ -> fail("Callback should not be called") }
+
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf(
+                Flags.FLAG_QS_REGISTER_SETTING_OBSERVER_ON_BG_THREAD
+            )
+        }
     }
 
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags)
+    }
+
+    private val kosmos = Kosmos()
+    private val testScope = kosmos.testScope
+
     private lateinit var testableLooper: TestableLooper
     private lateinit var setting: UserSettingObserver
     private lateinit var secureSettings: SecureSettings
@@ -54,7 +78,7 @@
     @Before
     fun setUp() {
         testableLooper = TestableLooper.get(this)
-        secureSettings = FakeSettings()
+        secureSettings = kosmos.fakeSettings
 
         setting =
             object :
@@ -76,92 +100,107 @@
 
     @After
     fun tearDown() {
-        setting.isListening = false
+        setListening(false)
     }
 
     @Test
-    fun testNotListeningByDefault() {
-        callback = FAIL_CALLBACK
+    fun testNotListeningByDefault() =
+        testScope.runTest {
+            callback = FAIL_CALLBACK
 
-        assertThat(setting.isListening).isFalse()
-        secureSettings.putIntForUser(TEST_SETTING, 2, USER)
-        testableLooper.processAllMessages()
-    }
+            assertThat(setting.isListening).isFalse()
+            secureSettings.putIntForUser(TEST_SETTING, 2, USER)
+            testableLooper.processAllMessages()
+        }
 
     @Test
-    fun testChangedWhenListeningCallsCallback() {
-        var changed = false
-        callback = { _, _ -> changed = true }
+    fun testChangedWhenListeningCallsCallback() =
+        testScope.runTest {
+            var changed = false
+            callback = { _, _ -> changed = true }
 
-        setting.isListening = true
-        secureSettings.putIntForUser(TEST_SETTING, 2, USER)
-        testableLooper.processAllMessages()
+            setListening(true)
+            secureSettings.putIntForUser(TEST_SETTING, 2, USER)
+            testableLooper.processAllMessages()
 
-        assertThat(changed).isTrue()
-    }
+            assertThat(changed).isTrue()
+        }
 
     @Test
-    fun testListensToCorrectSetting() {
-        callback = FAIL_CALLBACK
+    fun testListensToCorrectSetting() =
+        testScope.runTest {
+            callback = FAIL_CALLBACK
 
-        setting.isListening = true
-        secureSettings.putIntForUser("other", 2, USER)
-        testableLooper.processAllMessages()
-    }
+            setListening(true)
+            secureSettings.putIntForUser("other", 2, USER)
+            testableLooper.processAllMessages()
+        }
 
     @Test
-    fun testGetCorrectValue() {
-        secureSettings.putIntForUser(TEST_SETTING, 2, USER)
-        assertThat(setting.value).isEqualTo(2)
+    fun testGetCorrectValue() =
+        testScope.runTest {
+            secureSettings.putIntForUser(TEST_SETTING, 2, USER)
+            assertThat(setting.value).isEqualTo(2)
 
-        secureSettings.putIntForUser(TEST_SETTING, 4, USER)
-        assertThat(setting.value).isEqualTo(4)
-    }
+            secureSettings.putIntForUser(TEST_SETTING, 4, USER)
+            assertThat(setting.value).isEqualTo(4)
+        }
 
     @Test
-    fun testSetValue() {
-        setting.value = 5
-        assertThat(secureSettings.getIntForUser(TEST_SETTING, USER)).isEqualTo(5)
-    }
+    fun testSetValue() =
+        testScope.runTest {
+            setting.value = 5
+            assertThat(secureSettings.getIntForUser(TEST_SETTING, USER)).isEqualTo(5)
+        }
 
     @Test
-    fun testChangeUser() {
-        setting.isListening = true
-        setting.setUserId(OTHER_USER)
+    fun testChangeUser() =
+        testScope.runTest {
+            setListening(true)
+            setting.setUserId(OTHER_USER)
 
-        setting.isListening = true
-        assertThat(setting.currentUser).isEqualTo(OTHER_USER)
-    }
+            setListening(true)
+            assertThat(setting.currentUser).isEqualTo(OTHER_USER)
+        }
 
     @Test
-    fun testDoesntListenInOtherUsers() {
-        callback = FAIL_CALLBACK
-        setting.isListening = true
+    fun testDoesntListenInOtherUsers() =
+        testScope.runTest {
+            callback = FAIL_CALLBACK
+            setListening(true)
 
-        secureSettings.putIntForUser(TEST_SETTING, 3, OTHER_USER)
-        testableLooper.processAllMessages()
-    }
+            secureSettings.putIntForUser(TEST_SETTING, 3, OTHER_USER)
+            testableLooper.processAllMessages()
+        }
 
     @Test
-    fun testListensToCorrectUserAfterChange() {
-        var changed = false
-        callback = { _, _ -> changed = true }
+    fun testListensToCorrectUserAfterChange() =
+        testScope.runTest {
+            var changed = false
+            callback = { _, _ -> changed = true }
 
-        setting.isListening = true
-        setting.setUserId(OTHER_USER)
-        secureSettings.putIntForUser(TEST_SETTING, 2, OTHER_USER)
-        testableLooper.processAllMessages()
+            setListening(true)
+            setting.setUserId(OTHER_USER)
+            testScope.runCurrent()
+            secureSettings.putIntForUser(TEST_SETTING, 2, OTHER_USER)
+            testableLooper.processAllMessages()
 
-        assertThat(changed).isTrue()
-    }
+            assertThat(changed).isTrue()
+        }
 
     @Test
-    fun testDefaultValue() {
-        // Check default value before listening
-        assertThat(setting.value).isEqualTo(DEFAULT_VALUE)
+    fun testDefaultValue() =
+        testScope.runTest {
+            // Check default value before listening
+            assertThat(setting.value).isEqualTo(DEFAULT_VALUE)
 
-        // Check default value if setting is not set
-        setting.isListening = true
-        assertThat(setting.value).isEqualTo(DEFAULT_VALUE)
+            // Check default value if setting is not set
+            setListening(true)
+            assertThat(setting.value).isEqualTo(DEFAULT_VALUE)
+        }
+
+    fun setListening(listening: Boolean) {
+        setting.isListening = listening
+        testScope.runCurrent()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
index f866f74..3f550ca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
@@ -16,8 +16,13 @@
 
 package com.android.systemui.screenrecord;
 
+import static com.android.systemui.screenrecord.RecordingService.GROUP_KEY_ERROR_SAVING;
+import static com.android.systemui.screenrecord.RecordingService.GROUP_KEY_SAVED;
+
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
@@ -25,6 +30,7 @@
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
@@ -73,8 +79,6 @@
     @Mock
     private ScreenMediaRecorder mScreenMediaRecorder;
     @Mock
-    private Notification mNotification;
-    @Mock
     private Executor mExecutor;
     @Mock
     private Handler mHandler;
@@ -124,9 +128,6 @@
 
         // Mock notifications
         doNothing().when(mRecordingService).createRecordingNotification();
-        doReturn(mNotification).when(mRecordingService).createProcessingNotification();
-        doReturn(mNotification).when(mRecordingService).createSaveNotification(any());
-        doNothing().when(mRecordingService).createErrorNotification();
         doNothing().when(mRecordingService).showErrorToast(anyInt());
         doNothing().when(mRecordingService).stopForeground(anyInt());
 
@@ -227,6 +228,33 @@
     }
 
     @Test
+    public void testOnSystemRequestedStop_whenRecordingInProgress_showsNotifications() {
+        doReturn(true).when(mController).isRecording();
+
+        mRecordingService.onStopped();
+
+        // Processing notification
+        ArgumentCaptor<Notification> notifCaptor = ArgumentCaptor.forClass(Notification.class);
+        verify(mNotificationManager).notifyAsUser(any(), anyInt(), notifCaptor.capture(), any());
+        assertEquals(GROUP_KEY_SAVED, notifCaptor.getValue().getGroup());
+
+        reset(mNotificationManager);
+        verify(mExecutor).execute(mRunnableCaptor.capture());
+        mRunnableCaptor.getValue().run();
+
+        verify(mNotificationManager, times(2))
+                .notifyAsUser(any(), anyInt(), notifCaptor.capture(), any());
+        // Saved notification
+        Notification saveNotification = notifCaptor.getAllValues().get(0);
+        assertFalse(saveNotification.isGroupSummary());
+        assertEquals(GROUP_KEY_SAVED, saveNotification.getGroup());
+        // Group summary notification
+        Notification groupSummaryNotification = notifCaptor.getAllValues().get(1);
+        assertTrue(groupSummaryNotification.isGroupSummary());
+        assertEquals(GROUP_KEY_SAVED, groupSummaryNotification.getGroup());
+    }
+
+    @Test
     public void testOnSystemRequestedStop_recorderEndThrowsRuntimeException_showsErrorNotification()
             throws IOException {
         doReturn(true).when(mController).isRecording();
@@ -234,7 +262,11 @@
 
         mRecordingService.onStopped();
 
-        verify(mRecordingService).createErrorNotification();
+        verify(mRecordingService).createErrorSavingNotification(any());
+        ArgumentCaptor<Notification> notifCaptor = ArgumentCaptor.forClass(Notification.class);
+        verify(mNotificationManager).notifyAsUser(any(), anyInt(), notifCaptor.capture(), any());
+        assertTrue(notifCaptor.getValue().isGroupSummary());
+        assertEquals(GROUP_KEY_ERROR_SAVING, notifCaptor.getValue().getGroup());
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
index 11b0bdf..7dae5cc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
@@ -21,13 +21,13 @@
 import android.testing.TestableLooper
 import android.view.View
 import android.widget.Spinner
+import android.widget.TextView
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.Dependency
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
 import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity
 import com.android.systemui.mediaprojection.permission.ENTIRE_SCREEN
@@ -60,7 +60,6 @@
     @Mock private lateinit var starter: ActivityStarter
     @Mock private lateinit var controller: RecordingController
     @Mock private lateinit var userContextProvider: UserContextProvider
-    @Mock private lateinit var flags: FeatureFlags
     @Mock private lateinit var onStartRecordingClicked: Runnable
     @Mock private lateinit var mediaProjectionMetricsLogger: MediaProjectionMetricsLogger
 
@@ -128,6 +127,32 @@
     }
 
     @Test
+    fun startButtonText_entireScreenSelected() {
+        showDialog()
+
+        onSpinnerItemSelected(ENTIRE_SCREEN)
+
+        assertThat(getStartButton().text)
+            .isEqualTo(
+                context.getString(R.string.screenrecord_permission_dialog_continue_entire_screen)
+            )
+    }
+
+    @Test
+    fun startButtonText_singleAppSelected() {
+        showDialog()
+
+        onSpinnerItemSelected(SINGLE_APP)
+
+        assertThat(getStartButton().text)
+            .isEqualTo(
+                context.getString(
+                    R.string.media_projection_entry_generic_permission_dialog_continue_single_app
+                )
+            )
+    }
+
+    @Test
     fun startClicked_singleAppSelected_passesHostUidToAppSelector() {
         showDialog()
         onSpinnerItemSelected(SINGLE_APP)
@@ -152,7 +177,8 @@
         showDialog()
 
         val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options)
-        val singleApp = context.getString(R.string.screen_share_permission_dialog_option_single_app)
+        val singleApp =
+            context.getString(R.string.screenrecord_permission_dialog_option_text_single_app)
         assertEquals(spinner.adapter.getItem(0), singleApp)
     }
 
@@ -208,8 +234,10 @@
         dialog.requireViewById<View>(android.R.id.button2).performClick()
     }
 
+    private fun getStartButton() = dialog.requireViewById<TextView>(android.R.id.button1)
+
     private fun clickOnStart() {
-        dialog.requireViewById<View>(android.R.id.button1).performClick()
+        getStartButton().performClick()
     }
 
     private fun onSpinnerItemSelected(position: Int) {
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 5de31d8..1fa78b4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -57,9 +57,11 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
+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.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.lockscreen.lockscreenSmartspaceController
 import com.android.systemui.statusbar.notification.stack.notificationStackScrollLayoutController
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
@@ -134,7 +136,9 @@
                     ambientTouchComponentFactory,
                     communalContent,
                     kosmos.sceneDataSourceDelegator,
-                    kosmos.notificationStackScrollLayoutController
+                    kosmos.notificationStackScrollLayoutController,
+                    kosmos.keyguardMediaController,
+                    kosmos.lockscreenSmartspaceController
                 )
         }
         testableLooper = TestableLooper.get(this)
@@ -178,7 +182,9 @@
                         ambientTouchComponentFactory,
                         communalContent,
                         kosmos.sceneDataSourceDelegator,
-                        kosmos.notificationStackScrollLayoutController
+                        kosmos.notificationStackScrollLayoutController,
+                        kosmos.keyguardMediaController,
+                        kosmos.lockscreenSmartspaceController
                     )
 
                 // First call succeeds.
@@ -205,6 +211,8 @@
                     communalContent,
                     kosmos.sceneDataSourceDelegator,
                     kosmos.notificationStackScrollLayoutController,
+                    kosmos.keyguardMediaController,
+                    kosmos.lockscreenSmartspaceController
                 )
 
             assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.INITIALIZED)
@@ -226,6 +234,8 @@
                     communalContent,
                     kosmos.sceneDataSourceDelegator,
                     kosmos.notificationStackScrollLayoutController,
+                    kosmos.keyguardMediaController,
+                    kosmos.lockscreenSmartspaceController
                 )
 
             // Only initView without attaching a view as we don't want the flows to start collecting
@@ -599,6 +609,30 @@
         }
 
     @Test
+    fun fullScreenSwipeGesture_doNotProcessTouchesInUmo() =
+        with(kosmos) {
+            testScope.runTest {
+                // Communal is closed.
+                goToScene(CommunalScenes.Blank)
+                whenever(keyguardMediaController.isWithinMediaViewBounds(any(), any()))
+                    .thenReturn(true)
+                assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse()
+            }
+        }
+
+    @Test
+    fun fullScreenSwipeGesture_doNotProcessTouchesInSmartspace() =
+        with(kosmos) {
+            testScope.runTest {
+                // Communal is closed.
+                goToScene(CommunalScenes.Blank)
+                whenever(lockscreenSmartspaceController.isWithinSmartspaceBounds(any(), any()))
+                    .thenReturn(true)
+                assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse()
+            }
+        }
+
+    @Test
     fun onTouchEvent_hubOpen_touchesDispatched() =
         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 2803035..8125ef5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -192,6 +192,7 @@
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController;
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView;
 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
+import com.android.systemui.statusbar.policy.SplitShadeStateController;
 import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository;
 import com.android.systemui.statusbar.window.StatusBarWindowStateController;
 import com.android.systemui.unfold.SysUIUnfoldComponent;
@@ -428,6 +429,9 @@
                 mock(DeviceEntryUdfpsInteractor.class);
         when(deviceEntryUdfpsInteractor.isUdfpsSupported()).thenReturn(MutableStateFlow(false));
 
+        final SplitShadeStateController splitShadeStateController =
+                new ResourcesSplitShadeStateController();
+
         mShadeInteractor = new ShadeInteractorImpl(
                 mTestScope.getBackgroundScope(),
                 mKosmos.getDeviceProvisioningInteractor(),
@@ -445,7 +449,8 @@
                         new SharedNotificationContainerInteractor(
                                 new FakeConfigurationRepository(),
                                 mContext,
-                                new ResourcesSplitShadeStateController(),
+                                () -> splitShadeStateController,
+                                () -> mShadeInteractor,
                                 mKeyguardInteractor,
                                 deviceEntryUdfpsInteractor,
                                 () -> mLargeScreenHeaderHelper
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
index 2c2fcbe..13bc82f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.shade
 
 import android.platform.test.annotations.DisableFlags
-import android.platform.test.annotations.EnableFlags
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.View
@@ -28,7 +27,6 @@
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.test.filters.SmallTest
-import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX
 import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.fragments.FragmentHostManager
@@ -166,31 +164,7 @@
     }
 
     @Test
-    @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
-    fun testLargeScreen_updateResources_refactorFlagOff_splitShadeHeightIsSetBasedOnResource() {
-        val headerResourceHeight = 20
-        val headerHelperHeight = 30
-        whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
-            .thenReturn(headerHelperHeight)
-        overrideResource(R.bool.config_use_large_screen_shade_header, true)
-        overrideResource(R.dimen.qs_header_height, 10)
-        overrideResource(R.dimen.large_screen_shade_header_height, headerResourceHeight)
-
-        // ensure the estimated height (would be 3 here) wouldn't impact this test case
-        overrideResource(R.dimen.large_screen_shade_header_min_height, 1)
-        overrideResource(R.dimen.new_qs_header_non_clickable_element_height, 1)
-
-        underTest.updateResources()
-
-        val captor = ArgumentCaptor.forClass(ConstraintSet::class.java)
-        verify(view).applyConstraints(capture(captor))
-        assertThat(captor.value.getHeight(R.id.split_shade_status_bar))
-            .isEqualTo(headerResourceHeight)
-    }
-
-    @Test
-    @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
-    fun testLargeScreen_updateResources_refactorFlagOn_splitShadeHeightIsSetBasedOnHelper() {
+    fun testLargeScreen_updateResources_splitShadeHeightIsSetBasedOnHelper() {
         val headerResourceHeight = 20
         val headerHelperHeight = 30
         whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
@@ -447,31 +421,8 @@
     }
 
     @Test
-    @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
-    fun testLargeScreenLayout_refactorFlagOff_qsAndNotifsTopMarginIsOfHeaderHeightResource() {
-        setLargeScreen()
-        val largeScreenHeaderResourceHeight = 100
-        val largeScreenHeaderHelperHeight = 200
-        whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
-            .thenReturn(largeScreenHeaderHelperHeight)
-        overrideResource(R.dimen.large_screen_shade_header_height, largeScreenHeaderResourceHeight)
-
-        // ensure the estimated height (would be 30 here) wouldn't impact this test case
-        overrideResource(R.dimen.large_screen_shade_header_min_height, 10)
-        overrideResource(R.dimen.new_qs_header_non_clickable_element_height, 10)
-
-        underTest.updateResources()
-
-        assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin)
-            .isEqualTo(largeScreenHeaderResourceHeight)
-        assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).topMargin)
-            .isEqualTo(largeScreenHeaderResourceHeight)
-    }
-
-    @Test
     @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
-    fun testLargeScreenLayout_refactorFlagOn_qsAndNotifsTopMarginIsOfHeaderHeightHelper() {
+    fun testLargeScreenLayout_qsAndNotifsTopMarginIsOfHeaderHeightHelper() {
         setLargeScreen()
         val largeScreenHeaderResourceHeight = 100
         val largeScreenHeaderHelperHeight = 200
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
index f21def3..4850b0f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.shade
 
-import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
@@ -28,7 +27,6 @@
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.test.filters.SmallTest
-import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX
 import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.fragments.FragmentHostManager
@@ -164,29 +162,7 @@
     }
 
     @Test
-    @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
-    fun testLargeScreen_updateResources_refactorFlagOff_splitShadeHeightIsSet_basedOnResource() {
-        val helperHeight = 30
-        val resourceHeight = 20
-        whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(helperHeight)
-        overrideResource(R.bool.config_use_large_screen_shade_header, true)
-        overrideResource(R.dimen.qs_header_height, 10)
-        overrideResource(R.dimen.large_screen_shade_header_height, resourceHeight)
-
-        // ensure the estimated height (would be 3 here) wouldn't impact this test case
-        overrideResource(R.dimen.large_screen_shade_header_min_height, 1)
-        overrideResource(R.dimen.new_qs_header_non_clickable_element_height, 1)
-
-        underTest.updateResources()
-
-        val captor = ArgumentCaptor.forClass(ConstraintSet::class.java)
-        verify(view).applyConstraints(capture(captor))
-        assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(resourceHeight)
-    }
-
-    @Test
-    @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
-    fun testLargeScreen_updateResources_refactorFlagOn_splitShadeHeightIsSet_basedOnHelper() {
+    fun testLargeScreen_updateResources_splitShadeHeightIsSet_basedOnHelper() {
         val helperHeight = 30
         val resourceHeight = 20
         whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(helperHeight)
@@ -427,28 +403,7 @@
     }
 
     @Test
-    @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
-    fun testLargeScreenLayout_refactorFlagOff_qsAndNotifsTopMarginIsOfHeaderResourceHeight() {
-        setLargeScreen()
-        val largeScreenHeaderHelperHeight = 200
-        val largeScreenHeaderResourceHeight = 100
-        whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
-            .thenReturn(largeScreenHeaderHelperHeight)
-        overrideResource(R.dimen.large_screen_shade_header_height, largeScreenHeaderResourceHeight)
-
-        // ensure the estimated height (would be 30 here) wouldn't impact this test case
-        overrideResource(R.dimen.large_screen_shade_header_min_height, 10)
-        overrideResource(R.dimen.new_qs_header_non_clickable_element_height, 10)
-
-        underTest.updateResources()
-
-        assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin)
-            .isEqualTo(largeScreenHeaderResourceHeight)
-    }
-
-    @Test
-    @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
-    fun testLargeScreenLayout_refactorFlagOn_qsAndNotifsTopMarginIsOfHeaderHelperHeight() {
+    fun testLargeScreenLayout_qsAndNotifsTopMarginIsOfHeaderHelperHeight() {
         setLargeScreen()
         val largeScreenHeaderHelperHeight = 200
         val largeScreenHeaderResourceHeight = 100
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 e57382d..505f799 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
@@ -225,7 +225,8 @@
                         new SharedNotificationContainerInteractor(
                                 configurationRepository,
                                 mContext,
-                                splitShadeStateController,
+                                () -> splitShadeStateController,
+                                () -> mShadeInteractor,
                                 keyguardInteractor,
                                 deviceEntryUdfpsInteractor,
                                 () -> mLargeScreenHeaderHelper),
@@ -266,6 +267,7 @@
 
         when(mPanelView.getParent()).thenReturn(mPanelViewParent);
         when(mQs.getHeader()).thenReturn(mQsHeader);
+        when(mQSFragment.getHeader()).thenReturn(mQsHeader);
 
         doAnswer(invocation -> {
             mLockscreenShadeTransitionCallback = invocation.getArgument(0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java
index e7db469..2e9d6e8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java
@@ -24,34 +24,41 @@
 import static android.view.MotionEvent.BUTTON_SECONDARY;
 import static android.view.MotionEvent.BUTTON_STYLUS_PRIMARY;
 
-import static com.android.systemui.Flags.FLAG_QS_UI_REFACTOR;
 import static com.android.systemui.Flags.FLAG_QS_UI_REFACTOR_COMPOSE_FRAGMENT;
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.inOrder;
+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.graphics.Rect;
+import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.FlagsParameterization;
 import android.testing.TestableLooper;
 import android.view.MotionEvent;
+import android.view.ViewGroup;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.plugins.qs.QS;
+import com.android.systemui.qs.flags.QSComposeFragment;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
 
 import java.util.List;
 
@@ -65,7 +72,7 @@
 
     @Parameters(name = "{0}")
     public static List<FlagsParameterization> getParams() {
-        return progressionOf(FLAG_QS_UI_REFACTOR, FLAG_QS_UI_REFACTOR_COMPOSE_FRAGMENT);
+        return progressionOf(FLAG_QS_UI_REFACTOR_COMPOSE_FRAGMENT);
     }
 
     public QuickSettingsControllerImplTest(FlagsParameterization flags) {
@@ -244,6 +251,61 @@
     }
 
     @Test
+    @DisableFlags(QSComposeFragment.FLAG_NAME)
+    public void onQsFragmentAttached_qsComposeFragmentDisabled_setHeaderInNSSL() {
+        mFragmentListener.onFragmentViewCreated(QS.TAG, mQSFragment);
+
+        verify(mNotificationStackScrollLayoutController)
+                .setQsHeader((ViewGroup) mQSFragment.getHeader());
+        verify(mNotificationStackScrollLayoutController, never()).setQsHeaderBoundsProvider(any());
+    }
+
+    @Test
+    @EnableFlags(QSComposeFragment.FLAG_NAME)
+    public void onQsFragmentAttached_qsComposeFragmentEnabled_setQsHeaderBoundsProviderInNSSL() {
+        mFragmentListener.onFragmentViewCreated(QS.TAG, mQSFragment);
+
+        verify(mNotificationStackScrollLayoutController, never())
+                .setQsHeader((ViewGroup) mQSFragment.getHeader());
+        ArgumentCaptor<QSHeaderBoundsProvider> argumentCaptor =
+                ArgumentCaptor.forClass(QSHeaderBoundsProvider.class);
+
+        verify(mNotificationStackScrollLayoutController)
+                .setQsHeaderBoundsProvider(argumentCaptor.capture());
+
+        argumentCaptor.getValue().getLeftProvider().invoke();
+        argumentCaptor.getValue().getHeightProvider().invoke();
+        argumentCaptor.getValue().getBoundsOnScreenProvider().invoke(new Rect());
+        InOrder inOrderVerifier = inOrder(mQSFragment);
+
+        inOrderVerifier.verify(mQSFragment).getHeaderLeft();
+        inOrderVerifier.verify(mQSFragment).getHeaderHeight();
+        inOrderVerifier.verify(mQSFragment).getHeaderBoundsOnScreen(new Rect());
+    }
+
+    @Test
+    @DisableFlags(QSComposeFragment.FLAG_NAME)
+    public void onQSFragmentDetached_qsComposeFragmentFlagDisabled_setViewToNullInNSSL() {
+        mFragmentListener.onFragmentViewCreated(QS.TAG, mQSFragment);
+
+        mFragmentListener.onFragmentViewDestroyed(QS.TAG, mQSFragment);
+
+        verify(mNotificationStackScrollLayoutController).setQsHeader(null);
+        verify(mNotificationStackScrollLayoutController, never()).setQsHeaderBoundsProvider(null);
+    }
+
+    @Test
+    @EnableFlags(QSComposeFragment.FLAG_NAME)
+    public void onQSFragmentDetached_qsComposeFragmentFlagEnabled_setBoundsProviderToNullInNSSL() {
+        mFragmentListener.onFragmentViewCreated(QS.TAG, mQSFragment);
+
+        mFragmentListener.onFragmentViewDestroyed(QS.TAG, mQSFragment);
+
+        verify(mNotificationStackScrollLayoutController, never()).setQsHeader(null);
+        verify(mNotificationStackScrollLayoutController).setQsHeaderBoundsProvider(null);
+    }
+
+    @Test
     public void onQsFragmentAttached_notFullWidth_setsFullWidthFalseOnQS() {
         setIsFullWidth(false);
         mFragmentListener.onFragmentViewCreated(QS.TAG, mQSFragment);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index 150f53d..022825a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -22,8 +22,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags.TRANSIT_CLOCK
 import com.android.systemui.plugins.clocks.ClockController
 import com.android.systemui.plugins.clocks.ClockId
 import com.android.systemui.plugins.clocks.ClockMessageBuffers
@@ -76,7 +74,6 @@
     private lateinit var pluginListener: PluginListener<ClockProviderPlugin>
     private lateinit var registry: ClockRegistry
     private lateinit var pickerConfig: ClockPickerConfig
-    private val featureFlags = FakeFeatureFlags()
 
     companion object {
         private fun failFactory(clockId: ClockId): ClockController {
@@ -532,44 +529,4 @@
         val actual = ClockSettings.serialize(ClockSettings("ID", null))
         assertEquals(expected, actual)
     }
-
-    @Test
-    fun testTransitClockEnabled_hasTransitClock() {
-        testTransitClockFlag(true)
-    }
-
-    @Test
-    fun testTransitClockDisabled_noTransitClock() {
-        testTransitClockFlag(false)
-    }
-
-    private fun testTransitClockFlag(flag: Boolean) {
-        featureFlags.set(TRANSIT_CLOCK, flag)
-        registry.isTransitClockEnabled = featureFlags.isEnabled(TRANSIT_CLOCK)
-        val plugin = FakeClockPlugin()
-                .addClock("clock_1")
-                .addClock("DIGITAL_CLOCK_METRO")
-        val lifecycle = FakeLifecycle("metro", plugin)
-        pluginListener.onPluginLoaded(plugin, mockContext, lifecycle)
-
-        val list = registry.getClocks()
-        if (flag) {
-            assertEquals(
-                    setOf(
-                            ClockMetadata(DEFAULT_CLOCK_ID),
-                            ClockMetadata("clock_1"),
-                            ClockMetadata("DIGITAL_CLOCK_METRO")
-                    ),
-                    list.toSet()
-            )
-        } else {
-            assertEquals(
-                    setOf(
-                            ClockMetadata(DEFAULT_CLOCK_ID),
-                            ClockMetadata("clock_1")
-                    ),
-                    list.toSet()
-            )
-        }
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index 86d21e8..d10ea1f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -15,8 +15,9 @@
 package com.android.systemui.statusbar;
 
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
+import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_ADJUST_NOTHING;
 import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT;
-import static android.inputmethodservice.InputMethodService.IME_INVISIBLE;
+import static android.inputmethodservice.InputMethodService.IME_ACTIVE;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowInsetsController.BEHAVIOR_DEFAULT;
 
@@ -195,9 +196,11 @@
 
     @Test
     public void testShowImeButton() {
-        mCommandQueue.setImeWindowStatus(DEFAULT_DISPLAY, 1, 2, true);
+        mCommandQueue.setImeWindowStatus(DEFAULT_DISPLAY, IME_ACTIVE,
+                BACK_DISPOSITION_ADJUST_NOTHING, true);
         waitForIdleSync();
-        verify(mCallbacks).setImeWindowStatus(eq(DEFAULT_DISPLAY), eq(1), eq(2), eq(true));
+        verify(mCallbacks).setImeWindowStatus(eq(DEFAULT_DISPLAY), eq(IME_ACTIVE),
+                eq(BACK_DISPOSITION_ADJUST_NOTHING), eq(true));
     }
 
     @Test
@@ -205,11 +208,13 @@
         // First show in default display to update the "last updated ime display"
         testShowImeButton();
 
-        mCommandQueue.setImeWindowStatus(SECONDARY_DISPLAY, 1, 2, true);
+        mCommandQueue.setImeWindowStatus(SECONDARY_DISPLAY, IME_ACTIVE,
+                BACK_DISPOSITION_ADJUST_NOTHING, true);
         waitForIdleSync();
-        verify(mCallbacks).setImeWindowStatus(eq(DEFAULT_DISPLAY), eq(IME_INVISIBLE),
+        verify(mCallbacks).setImeWindowStatus(eq(DEFAULT_DISPLAY), eq(0) /* vis */,
                 eq(BACK_DISPOSITION_DEFAULT), eq(false));
-        verify(mCallbacks).setImeWindowStatus(eq(SECONDARY_DISPLAY), eq(1), eq(2), eq(true));
+        verify(mCallbacks).setImeWindowStatus(eq(SECONDARY_DISPLAY), eq(IME_ACTIVE),
+                eq(BACK_DISPOSITION_ADJUST_NOTHING), eq(true));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 80011dc..a75d7b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -25,6 +25,7 @@
 import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_AVAILABLE;
 import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED;
 import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED;
+import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_ADAPTIVE_AUTH;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_ALIGNMENT;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BATTERY;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BIOMETRIC_MESSAGE;
@@ -1535,6 +1536,48 @@
                 trustGrantedMsg);
     }
 
+    @Test
+    public void updateAdaptiveAuthMessage_whenNotLockedByAdaptiveAuth_doesNotShowMsg() {
+        // When the device is not locked by adaptive auth
+        when(mKeyguardUpdateMonitor.isDeviceLockedByAdaptiveAuth(getCurrentUser()))
+                .thenReturn(false);
+        createController();
+        mController.setVisible(true);
+
+        // Verify that the adaptive auth message does not show
+        verifyNoMessage(INDICATION_TYPE_ADAPTIVE_AUTH);
+    }
+
+    @Test
+    public void updateAdaptiveAuthMessage_whenLockedByAdaptiveAuth_cannotSkipBouncer_showsMsg() {
+        // When the device is locked by adaptive auth, and the user cannot skip bouncer
+        when(mKeyguardUpdateMonitor.isDeviceLockedByAdaptiveAuth(getCurrentUser()))
+                .thenReturn(true);
+        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser())).thenReturn(false);
+        createController();
+        mController.setVisible(true);
+
+        // Verify that the adaptive auth message shows
+        String message = mContext.getString(R.string.keyguard_indication_after_adaptive_auth_lock);
+        verifyIndicationMessage(INDICATION_TYPE_ADAPTIVE_AUTH, message);
+    }
+
+    @Test
+    public void updateAdaptiveAuthMessage_whenLockedByAdaptiveAuth_canSkipBouncer_doesNotShowMsg() {
+        createController();
+        mController.setVisible(true);
+
+        // When the device is locked by adaptive auth, but the device unlocked state changes and the
+        // user can skip bouncer
+        when(mKeyguardUpdateMonitor.isDeviceLockedByAdaptiveAuth(getCurrentUser()))
+                .thenReturn(true);
+        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser())).thenReturn(true);
+        mKeyguardStateControllerCallback.onUnlockedChanged();
+
+        // Verify that the adaptive auth message does not show
+        verifyNoMessage(INDICATION_TYPE_ADAPTIVE_AUTH);
+    }
+
     private void screenIsTurningOn() {
         when(mScreenLifecycle.getScreenState()).thenReturn(SCREEN_TURNING_ON);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
index ef1c927..27a1bb5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
@@ -24,7 +24,6 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
 
 import android.app.Notification;
 import android.app.NotificationManager;
@@ -41,7 +40,6 @@
 import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
 import com.android.systemui.statusbar.data.repository.NotificationListenerSettingsRepository;
 import com.android.systemui.statusbar.domain.interactor.SilentNotificationStatusIconsVisibilityInteractor;
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 
@@ -149,30 +147,4 @@
         verify(mNotificationHandler).onNotificationRankingUpdate(eq(ranking3));
         verifyNoMoreInteractions(mNotificationHandler);
     }
-
-    @Test
-    public void testOnConnectReadStatusBarSetting() {
-        mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME);
-        NotificationListener.NotificationSettingsListener settingsListener =
-                mock(NotificationListener.NotificationSettingsListener.class);
-        mListener.addNotificationSettingsListener(settingsListener);
-
-        when(mNotificationManager.shouldHideSilentStatusBarIcons()).thenReturn(true);
-
-        mListener.onListenerConnected();
-
-        verify(settingsListener).onStatusBarIconsBehaviorChanged(true);
-    }
-
-    @Test
-    public void testOnStatusBarIconsBehaviorChanged() {
-        mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME);
-        NotificationListener.NotificationSettingsListener settingsListener =
-                mock(NotificationListener.NotificationSettingsListener.class);
-        mListener.addNotificationSettingsListener(settingsListener);
-
-        mListener.onSilentStatusBarIconsVisibilityChanged(true);
-
-        verify(settingsListener).onStatusBarIconsBehaviorChanged(true);
-    }
 }
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 e68fa0b..804eb5c 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
@@ -231,6 +231,34 @@
             assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs).isEqualTo(5678)
         }
 
+    /** Regression test for b/349620526. */
+    @Test
+    fun chip_recordingState_thenGetsTaskInfo_startTimeDoesNotChange() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+
+            // Start recording, but without any task info
+            systemClock.setElapsedRealtime(1234)
+            screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording
+            mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.NotProjecting
+
+            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+            assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs).isEqualTo(1234)
+
+            // WHEN we receive the recording task info a few milliseconds later
+            systemClock.setElapsedRealtime(1240)
+            mediaProjectionRepo.mediaProjectionState.value =
+                MediaProjectionState.Projecting.SingleTask(
+                    "host.package",
+                    hostDeviceName = null,
+                    FakeActivityTaskManager.createTask(taskId = 1)
+                )
+
+            // THEN the start time is still the old start time
+            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+            assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs).isEqualTo(1234)
+        }
+
     @Test
     fun chip_notProjecting_clickListenerShowsDialog() =
         testScope.runTest {
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 c7513de..ad6aca1 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,10 +34,8 @@
 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.shared.NotificationIconContainerRefactor
 import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
 import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
-import com.android.systemui.statusbar.phone.NotificationIconAreaController
 import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.withArgCaptor
@@ -47,8 +45,8 @@
 import org.mockito.Mock
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.MockitoAnnotations.initMocks
 import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations.initMocks
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
@@ -61,10 +59,10 @@
 
     @Mock private lateinit var pipeline: NotifPipeline
     @Mock private lateinit var groupExpansionManagerImpl: GroupExpansionManagerImpl
-    @Mock private lateinit var notificationIconAreaController: NotificationIconAreaController
     @Mock private lateinit var renderListInteractor: RenderNotificationListInteractor
     @Mock private lateinit var activeNotificationsInteractor: ActiveNotificationsInteractor
-    @Mock private lateinit var sensitiveNotificationProtectionController:
+    @Mock
+    private lateinit var sensitiveNotificationProtectionController:
         SensitiveNotificationProtectionController
     @Mock private lateinit var stackController: NotifStackController
     @Mock private lateinit var section: NotifSection
@@ -73,14 +71,12 @@
     fun setUp() {
         initMocks(this)
 
-        whenever(sensitiveNotificationProtectionController.isSensitiveStateActive)
-            .thenReturn(false)
+        whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(false)
 
         entry = NotificationEntryBuilder().setSection(section).build()
         coordinator =
             StackCoordinator(
                 groupExpansionManagerImpl,
-                notificationIconAreaController,
                 renderListInteractor,
                 activeNotificationsInteractor,
                 sensitiveNotificationProtectionController,
@@ -92,15 +88,7 @@
     }
 
     @Test
-    @DisableFlags(NotificationIconContainerRefactor.FLAG_NAME)
-    fun testUpdateNotificationIcons() {
-        afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
-        verify(notificationIconAreaController).updateNotificationIcons(eq(listOf(entry)))
-    }
-
-    @Test
-    @EnableFlags(NotificationIconContainerRefactor.FLAG_NAME)
-    fun testSetRenderedListOnInteractor_iconContainerFlagOn() {
+    fun testSetRenderedListOnInteractor() {
         afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
         verify(renderListInteractor).setRenderedList(eq(listOf(entry)))
     }
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 3f28164..491919a 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
@@ -44,6 +44,7 @@
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag
 import com.android.systemui.statusbar.notification.row.shared.HeadsUpStatusBarModel
+import com.android.systemui.statusbar.notification.row.shared.LockscreenOtpRedaction
 import com.android.systemui.statusbar.notification.row.shared.NewRemoteViews
 import com.android.systemui.statusbar.notification.row.shared.NotificationContentModel
 import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor
@@ -78,7 +79,7 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 @RunWithLooper
-@EnableFlags(NotificationRowContentBinderRefactor.FLAG_NAME)
+@EnableFlags(NotificationRowContentBinderRefactor.FLAG_NAME, LockscreenOtpRedaction.FLAG_NAME)
 class NotificationRowContentBinderImplTest : SysuiTestCase() {
     private lateinit var notificationInflater: NotificationRowContentBinderImpl
     private lateinit var builder: Notification.Builder
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 c1f2cb77..e4945fc 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
@@ -1,6 +1,5 @@
 package com.android.systemui.statusbar.notification.stack
 
-import android.platform.test.annotations.DisableFlags
 import android.service.notification.StatusBarNotification
 import android.testing.TestableLooper.RunWithLooper
 import android.view.LayoutInflater
@@ -21,7 +20,6 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.ExpandableView
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
 import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.StackScrollAlgorithmState
 import com.android.systemui.util.mockito.mock
 import junit.framework.Assert.assertEquals
@@ -72,32 +70,6 @@
     }
 
     @Test
-    @DisableFlags(NotificationIconContainerRefactor.FLAG_NAME)
-    fun testShadeWidth_BasedOnFractionToShade() {
-        setFractionToShade(0f)
-        setOnLockscreen(true)
-
-        shelf.updateActualWidth(/* fractionToShade */ 0f, /* shortestWidth */ 10f)
-        assertTrue(shelf.actualWidth == 10)
-
-        shelf.updateActualWidth(/* fractionToShade */ 0.5f, /* shortestWidth */ 10f)
-        assertTrue(shelf.actualWidth == 20)
-
-        shelf.updateActualWidth(/* fractionToShade */ 1f, /* shortestWidth */ 10f)
-        assertTrue(shelf.actualWidth == 30)
-    }
-
-    @Test
-    @DisableFlags(NotificationIconContainerRefactor.FLAG_NAME)
-    fun testShelfIsLong_WhenNotOnLockscreen() {
-        setFractionToShade(0f)
-        setOnLockscreen(false)
-
-        shelf.updateActualWidth(/* fraction */ 0f, /* shortestWidth */ 10f)
-        assertTrue(shelf.actualWidth == 30)
-    }
-
-    @Test
     fun testX_inViewForClick() {
         val isXInView =
             shelf.isXInView(/* localX */ 5f, /* slop */ 5f, /* left */ 0f, /* right */ 10f)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index a6b2729..e9c16c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -295,7 +295,6 @@
     @Mock private Bubbles mBubbles;
     @Mock private NoteTaskController mNoteTaskController;
     @Mock private NotificationShadeWindowController mNotificationShadeWindowController;
-    @Mock private NotificationIconAreaController mNotificationIconAreaController;
     @Mock private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
     @Mock private Lazy<NotificationShadeWindowViewController>
             mNotificationShadeWindowViewControllerLazy;
@@ -580,7 +579,6 @@
                 mDemoModeController,
                 mNotificationShadeDepthControllerLazy,
                 mStatusBarTouchableRegionManager,
-                mNotificationIconAreaController,
                 mBrightnessSliderFactory,
                 mScreenOffAnimationController,
                 mWallpaperController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index 0d06b64..dd03ab3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
@@ -110,7 +110,6 @@
         mNotificationRoundnessManager = mock(NotificationRoundnessManager.class);
         when(mShadeViewController.getShadeHeadsUpTracker()).thenReturn(mShadeHeadsUpTracker);
         mHeadsUpAppearanceController = new HeadsUpAppearanceController(
-                mock(NotificationIconAreaController.class),
                 mHeadsUpManager,
                 mStatusbarStateController,
                 mPhoneStatusBarTransitions,
@@ -197,7 +196,6 @@
         when(mStackScrollerController.getExpandedHeight()).thenReturn(expandedHeight);
 
         HeadsUpAppearanceController newController = new HeadsUpAppearanceController(
-                mock(NotificationIconAreaController.class),
                 mHeadsUpManager,
                 mStatusbarStateController,
                 mPhoneStatusBarTransitions,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
index 7f33c23..eb1e28b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
@@ -26,13 +26,10 @@
 import static org.mockito.Mockito.when;
 
 import android.content.res.Resources;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.doze.util.BurnInHelperKt;
 import com.android.systemui.log.LogBuffer;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java
deleted file mode 100644
index 8dfbb37..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java
+++ /dev/null
@@ -1,129 +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.systemui.statusbar.phone;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.platform.test.annotations.DisableFlags;
-import android.testing.TestableLooper;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.Flags;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.demomode.DemoModeController;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.plugins.DarkIconDispatcher;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.NotificationListener;
-import com.android.systemui.statusbar.NotificationMediaManager;
-import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
-import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider;
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
-import com.android.systemui.statusbar.window.StatusBarWindowController;
-import com.android.wm.shell.bubbles.Bubbles;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.Optional;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-@TestableLooper.RunWithLooper
-@DisableFlags(NotificationIconContainerRefactor.FLAG_NAME)
-public class LegacyNotificationIconAreaControllerImplTest extends SysuiTestCase {
-
-    @Mock
-    private NotificationListener mListener;
-    @Mock
-    StatusBarStateController mStatusBarStateController;
-    @Mock
-    NotificationWakeUpCoordinator mWakeUpCoordinator;
-    @Mock
-    KeyguardBypassController mKeyguardBypassController;
-    @Mock
-    NotificationMediaManager mNotificationMediaManager;
-    @Mock
-    DozeParameters mDozeParameters;
-    @Mock
-    SectionStyleProvider mSectionStyleProvider;
-    @Mock
-    DarkIconDispatcher mDarkIconDispatcher;
-    @Mock
-    StatusBarWindowController mStatusBarWindowController;
-    @Mock
-    ScreenOffAnimationController mScreenOffAnimationController;
-    private LegacyNotificationIconAreaControllerImpl mController;
-    @Mock
-    private Bubbles mBubbles;
-    @Mock private DemoModeController mDemoModeController;
-    @Mock
-    private NotificationIconContainer mAodIcons;
-    @Mock
-    private FeatureFlags mFeatureFlags;
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-        mController = new LegacyNotificationIconAreaControllerImpl(
-                mContext,
-                mStatusBarStateController,
-                mWakeUpCoordinator,
-                mKeyguardBypassController,
-                mNotificationMediaManager,
-                mListener,
-                mDozeParameters,
-                mSectionStyleProvider,
-                Optional.of(mBubbles),
-                mDemoModeController,
-                mDarkIconDispatcher,
-                mFeatureFlags,
-                mStatusBarWindowController,
-                mScreenOffAnimationController);
-    }
-
-    @Test
-    public void testNotificationIcons_settingHideIcons() {
-        mController.mSettingsListener.onStatusBarIconsBehaviorChanged(true);
-
-        assertFalse(mController.shouldShouldLowPriorityIcons());
-    }
-
-    @Test
-    public void testNotificationIcons_settingShowIcons() {
-        mController.mSettingsListener.onStatusBarIconsBehaviorChanged(false);
-
-        assertTrue(mController.shouldShouldLowPriorityIcons());
-    }
-
-    @Test
-    @DisableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void testAppearResetsTranslation() {
-        mController.setupAodIcons(mAodIcons);
-        when(mDozeParameters.shouldControlScreenOff()).thenReturn(false);
-        mController.appearAodIcons();
-        verify(mAodIcons).setTranslationY(0);
-        verify(mAodIcons).setAlpha(1.0f);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
index 9d97e5a..15958ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
@@ -23,7 +23,6 @@
 import com.android.systemui.statusbar.StatusBarIconView
 import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
 import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
 import junit.framework.Assert.assertEquals
 import junit.framework.Assert.assertFalse
 import junit.framework.Assert.assertTrue
@@ -73,18 +72,6 @@
     }
 
     @Test
-    fun calculateWidthFor_fiveIcons_widthForFourIcons() {
-        mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME)
-        iconContainer.setActualPaddingStart(10f)
-        iconContainer.setActualPaddingEnd(10f)
-        iconContainer.setIconSize(10)
-        assertEquals(
-            /* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 5f),
-            /* actual= */ 60f
-        )
-    }
-
-    @Test
     fun calculateIconXTranslations_shortShelfOneIcon_atCorrectXWithoutOverflowDot() {
         iconContainer.setActualPaddingStart(10f)
         iconContainer.setActualPaddingEnd(10f)
@@ -213,19 +200,6 @@
     }
 
     @Test
-    fun shouldForceOverflow_appearingAboveSpeedBump_true() {
-        mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME)
-        val forceOverflow =
-            iconContainer.shouldForceOverflow(
-                /* i= */ 1,
-                /* speedBumpIndex= */ 0,
-                /* iconAppearAmount= */ 1f,
-                /* maxVisibleIcons= */ 5
-            )
-        assertTrue(forceOverflow)
-    }
-
-    @Test
     fun shouldForceOverflow_moreThanMaxVisible_true() {
         val forceOverflow =
             iconContainer.shouldForceOverflow(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index 25314f3..5b45781 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -20,6 +20,8 @@
 import android.app.StatusBarManager.WINDOW_STATE_HIDING
 import android.app.StatusBarManager.WINDOW_STATE_SHOWING
 import android.app.StatusBarManager.WINDOW_STATUS_BAR
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import android.view.InputDevice
 import android.view.LayoutInflater
 import android.view.MotionEvent
@@ -104,7 +106,7 @@
             val parent = FrameLayout(mContext) // add parent to keep layout params
             view =
                 LayoutInflater.from(mContext).inflate(R.layout.status_bar, parent, false)
-                        as PhoneStatusBarView
+                    as PhoneStatusBarView
             controller = createAndInitController(view)
         }
     }
@@ -112,8 +114,8 @@
     @Test
     fun onViewAttachedAndDrawn_startListeningConfigurationControllerCallback() {
         val view = createViewMock()
-        val argumentCaptor = ArgumentCaptor.forClass(
-                ConfigurationController.ConfigurationListener::class.java)
+        val argumentCaptor =
+            ArgumentCaptor.forClass(ConfigurationController.ConfigurationListener::class.java)
         InstrumentationRegistry.getInstrumentation().runOnMainSync {
             controller = createAndInitController(view)
         }
@@ -159,7 +161,7 @@
     fun handleTouchEventFromStatusBar_panelsNotEnabled_returnsFalseAndNoViewEvent() {
         `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(false)
         val returnVal =
-            view.onTouchEvent(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0))
+            view.onTouchEvent(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 2f, 0))
         assertThat(returnVal).isFalse()
         verify(shadeViewController, never()).handleExternalTouch(any())
     }
@@ -169,7 +171,7 @@
         `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true)
         `when`(shadeViewController.isViewEnabled).thenReturn(false)
         val returnVal =
-            view.onTouchEvent(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0))
+            view.onTouchEvent(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 2f, 0))
         assertThat(returnVal).isTrue()
         verify(shadeViewController, never()).handleExternalTouch(any())
     }
@@ -178,7 +180,7 @@
     fun handleTouchEventFromStatusBar_viewNotEnabledButIsMoveEvent_viewReceivesEvent() {
         `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true)
         `when`(shadeViewController.isViewEnabled).thenReturn(false)
-        val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
+        val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 2f, 0)
 
         view.onTouchEvent(event)
 
@@ -208,6 +210,50 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_STATUS_BAR_SWIPE_OVER_CHIP)
+    fun handleInterceptTouchEventFromStatusBar_shadeReturnsFalse_flagOff_viewReturnsFalse() {
+        `when`(shadeViewController.handleExternalInterceptTouch(any())).thenReturn(false)
+        val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 2f, 0)
+
+        val returnVal = view.onInterceptTouchEvent(event)
+
+        assertThat(returnVal).isFalse()
+    }
+
+    @Test
+    @EnableFlags(com.android.systemui.Flags.FLAG_STATUS_BAR_SWIPE_OVER_CHIP)
+    fun handleInterceptTouchEventFromStatusBar_shadeReturnsFalse_flagOn_viewReturnsFalse() {
+        `when`(shadeViewController.handleExternalInterceptTouch(any())).thenReturn(false)
+        val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 2f, 0)
+
+        val returnVal = view.onInterceptTouchEvent(event)
+
+        assertThat(returnVal).isFalse()
+    }
+
+    @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_STATUS_BAR_SWIPE_OVER_CHIP)
+    fun handleInterceptTouchEventFromStatusBar_shadeReturnsTrue_flagOff_viewReturnsFalse() {
+        `when`(shadeViewController.handleExternalInterceptTouch(any())).thenReturn(true)
+        val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 2f, 0)
+
+        val returnVal = view.onInterceptTouchEvent(event)
+
+        assertThat(returnVal).isFalse()
+    }
+
+    @Test
+    @EnableFlags(com.android.systemui.Flags.FLAG_STATUS_BAR_SWIPE_OVER_CHIP)
+    fun handleInterceptTouchEventFromStatusBar_shadeReturnsTrue_flagOn_viewReturnsTrue() {
+        `when`(shadeViewController.handleExternalInterceptTouch(any())).thenReturn(true)
+        val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 2f, 0)
+
+        val returnVal = view.onInterceptTouchEvent(event)
+
+        assertThat(returnVal).isTrue()
+    }
+
+    @Test
     fun onTouch_windowHidden_centralSurfacesNotNotified() {
         val callback = getCommandQueueCallback()
         callback.setWindowState(DISPLAY_ID, WINDOW_STATUS_BAR, WINDOW_STATE_HIDDEN)
@@ -244,9 +290,7 @@
             controller = createAndInitController(view)
         }
         val statusContainer = view.requireViewById<View>(R.id.system_icons)
-        statusContainer.dispatchTouchEvent(
-            getActionUpEventFromSource(InputDevice.SOURCE_MOUSE)
-        )
+        statusContainer.dispatchTouchEvent(getActionUpEventFromSource(InputDevice.SOURCE_MOUSE))
         verify(shadeControllerImpl).animateExpandShade()
     }
 
@@ -257,9 +301,10 @@
             controller = createAndInitController(view)
         }
         val statusContainer = view.requireViewById<View>(R.id.system_icons)
-        val handled = statusContainer.dispatchTouchEvent(
-            getActionUpEventFromSource(InputDevice.SOURCE_TOUCHSCREEN)
-        )
+        val handled =
+            statusContainer.dispatchTouchEvent(
+                getActionUpEventFromSource(InputDevice.SOURCE_TOUCHSCREEN)
+            )
         assertThat(handled).isFalse()
     }
 
@@ -295,21 +340,21 @@
 
     private fun createAndInitController(view: PhoneStatusBarView): PhoneStatusBarViewController {
         return PhoneStatusBarViewController.Factory(
-            Optional.of(sysuiUnfoldComponent),
-            Optional.of(progressProvider),
-            featureFlags,
-            userChipViewModel,
-            centralSurfacesImpl,
-            statusBarWindowStateController,
-            shadeControllerImpl,
-            shadeViewController,
-            panelExpansionInteractor,
-            windowRootView,
-            shadeLogger,
-            viewUtil,
-            configurationController,
-            mStatusOverlayHoverListenerFactory
-        )
+                Optional.of(sysuiUnfoldComponent),
+                Optional.of(progressProvider),
+                featureFlags,
+                userChipViewModel,
+                centralSurfacesImpl,
+                statusBarWindowStateController,
+                shadeControllerImpl,
+                shadeViewController,
+                panelExpansionInteractor,
+                windowRootView,
+                shadeLogger,
+                viewUtil,
+                configurationController,
+                mStatusOverlayHoverListenerFactory
+            )
             .create(view)
             .also { it.init() }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
index eae4f23..abc50bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
@@ -19,6 +19,8 @@
 import android.content.res.Configuration
 import android.graphics.Insets
 import android.graphics.Rect
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import android.testing.TestableLooper.RunWithLooper
 import android.view.DisplayCutout
 import android.view.DisplayShape
@@ -30,6 +32,7 @@
 import android.view.WindowInsets
 import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_STATUS_BAR_SWIPE_OVER_CHIP
 import com.android.systemui.Gefingerpoken
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.plugins.DarkIconDispatcher
@@ -82,7 +85,8 @@
     }
 
     @Test
-    fun onInterceptTouchEvent_listenerNotified() {
+    @DisableFlags(FLAG_STATUS_BAR_SWIPE_OVER_CHIP)
+    fun onInterceptTouchEvent_flagOff_listenerNotified() {
         val handler = TestTouchEventHandler()
         view.setTouchEventHandler(handler)
 
@@ -93,6 +97,66 @@
     }
 
     @Test
+    @EnableFlags(FLAG_STATUS_BAR_SWIPE_OVER_CHIP)
+    fun onInterceptTouchEvent_flagOn_listenerNotified() {
+        val handler = TestTouchEventHandler()
+        view.setTouchEventHandler(handler)
+
+        val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+        view.onInterceptTouchEvent(event)
+
+        assertThat(handler.lastInterceptEvent).isEqualTo(event)
+    }
+
+    @Test
+    @DisableFlags(FLAG_STATUS_BAR_SWIPE_OVER_CHIP)
+    fun onInterceptTouchEvent_listenerReturnsFalse_flagOff_viewReturnsFalse() {
+        val handler = TestTouchEventHandler()
+        view.setTouchEventHandler(handler)
+        val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+
+        handler.handleTouchReturnValue = false
+
+        assertThat(view.onInterceptTouchEvent(event)).isFalse()
+    }
+
+    @Test
+    @EnableFlags(FLAG_STATUS_BAR_SWIPE_OVER_CHIP)
+    fun onInterceptTouchEvent_listenerReturnsFalse_flagOn_viewReturnsFalse() {
+        val handler = TestTouchEventHandler()
+        view.setTouchEventHandler(handler)
+        val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+
+        handler.handleTouchReturnValue = false
+
+        assertThat(view.onInterceptTouchEvent(event)).isFalse()
+    }
+
+    @Test
+    @DisableFlags(FLAG_STATUS_BAR_SWIPE_OVER_CHIP)
+    fun onInterceptTouchEvent_listenerReturnsTrue_flagOff_viewReturnsFalse() {
+        val handler = TestTouchEventHandler()
+        view.setTouchEventHandler(handler)
+        val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+
+        handler.handleTouchReturnValue = true
+
+        assertThat(view.onInterceptTouchEvent(event)).isFalse()
+    }
+
+    @Test
+    @EnableFlags(FLAG_STATUS_BAR_SWIPE_OVER_CHIP)
+    fun onInterceptTouchEvent_listenerReturnsTrue_flagOn_viewReturnsTrue() {
+        val handler = TestTouchEventHandler()
+        view.setTouchEventHandler(handler)
+        val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+
+        handler.handleTouchReturnValue = true
+
+        assertThat(view.onInterceptTouchEvent(event)).isTrue()
+    }
+
+    @Test
     fun onTouchEvent_listenerReturnsTrue_viewReturnsTrue() {
         val handler = TestTouchEventHandler()
         view.setTouchEventHandler(handler)
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 af5e60e..9b61105 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
@@ -1068,7 +1068,7 @@
     public void testShowBouncerOrKeyguard_needsFullScreen() {
         when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
                 KeyguardSecurityModel.SecurityMode.SimPin);
-        mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false);
+        mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false, false);
         verify(mCentralSurfaces).hideKeyguard();
         verify(mPrimaryBouncerInteractor).show(true);
     }
@@ -1084,7 +1084,7 @@
                 .thenReturn(KeyguardState.LOCKSCREEN);
 
         reset(mCentralSurfaces);
-        mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false);
+        mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false, false);
         verify(mPrimaryBouncerInteractor).show(true);
         verify(mCentralSurfaces).showKeyguard();
     }
@@ -1092,11 +1092,26 @@
     @Test
     @DisableSceneContainer
     public void testShowBouncerOrKeyguard_needsFullScreen_bouncerAlreadyShowing() {
+        boolean isFalsingReset = false;
         when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
                 KeyguardSecurityModel.SecurityMode.SimPin);
         when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true);
-        mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false);
+        mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false, isFalsingReset);
         verify(mCentralSurfaces, never()).hideKeyguard();
+        verify(mPrimaryBouncerInteractor).show(true);
+    }
+
+    @Test
+    @DisableSceneContainer
+    public void testShowBouncerOrKeyguard_needsFullScreen_bouncerAlreadyShowing_onFalsing() {
+        boolean isFalsingReset = true;
+        when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
+                KeyguardSecurityModel.SecurityMode.SimPin);
+        when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true);
+        mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false, isFalsingReset);
+        verify(mCentralSurfaces, never()).hideKeyguard();
+
+        // Do not refresh the full screen bouncer if the call is from falsing
         verify(mPrimaryBouncerInteractor, never()).show(true);
     }
 
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 58ad835..bea027f 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
@@ -39,7 +39,6 @@
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
-import android.view.LayoutInflater;
 import android.view.View;
 
 import androidx.test.filters.SmallTest;
@@ -64,7 +63,6 @@
 import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder;
 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
-import com.android.systemui.statusbar.phone.NotificationIconAreaController;
 import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
 import com.android.systemui.statusbar.phone.StatusBarLocationPublisher;
 import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent;
@@ -95,7 +93,6 @@
 @RunWithLooper(setAsMainLooper = true)
 @SmallTest
 public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
-    private NotificationIconAreaController mMockNotificationAreaController;
     private ShadeExpansionStateManager mShadeExpansionStateManager;
     private OngoingCallController mOngoingCallController;
     private SystemStatusAnimationScheduler mAnimationScheduler;
@@ -931,13 +928,11 @@
         mCollapsedStatusBarViewModel = new FakeCollapsedStatusBarViewModel();
         mCollapsedStatusBarViewBinder = new FakeCollapsedStatusBarViewBinder();
 
-        setUpNotificationIconAreaController();
         return new CollapsedStatusBarFragment(
                 mStatusBarFragmentComponentFactory,
                 mOngoingCallController,
                 mAnimationScheduler,
                 mLocationPublisher,
-                mMockNotificationAreaController,
                 mShadeExpansionStateManager,
                 mStatusBarIconController,
                 mIconManagerFactory,
@@ -970,14 +965,6 @@
                 .thenReturn(mHeadsUpAppearanceController);
     }
 
-    private void setUpNotificationIconAreaController() {
-        mMockNotificationAreaController = mock(NotificationIconAreaController.class);
-        View notificationAreaInner =
-                LayoutInflater.from(mContext).inflate(R.layout.notification_icon_area, null);
-        when(mMockNotificationAreaController.getNotificationInnerAreaView())
-                .thenReturn(notificationAreaInner);
-    }
-
     /**
      * Configure mocks to return values consistent with the secure camera animating itself launched
      * over the keyguard.
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 8fd0b31..171520f 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
@@ -21,6 +21,7 @@
 import android.content.Intent
 import android.net.ConnectivityManager
 import android.net.ConnectivityManager.NetworkCallback
+import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WLAN
 import android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN
@@ -59,6 +60,7 @@
 import android.telephony.TelephonyManager.ERI_OFF
 import android.telephony.TelephonyManager.ERI_ON
 import android.telephony.TelephonyManager.EXTRA_CARRIER_ID
+import android.telephony.TelephonyManager.EXTRA_DATA_SPN
 import android.telephony.TelephonyManager.EXTRA_PLMN
 import android.telephony.TelephonyManager.EXTRA_SHOW_PLMN
 import android.telephony.TelephonyManager.EXTRA_SHOW_SPN
@@ -69,6 +71,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.settingslib.mobile.MobileMappings
+import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.FakeFeatureFlagsClassic
@@ -85,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.model.toNetworkNameModel
 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
@@ -93,8 +95,6 @@
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
 import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.mockito.withArgCaptor
@@ -112,6 +112,8 @@
 import org.mockito.Mock
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
 
 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -807,6 +809,7 @@
         }
 
     @Test
+    @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
     fun networkName_usesBroadcastInfo_returnsDerived() =
         testScope.runTest {
             var latest: NetworkNameModel? = null
@@ -815,14 +818,34 @@
             val intent = spnIntent()
             val captor = argumentCaptor<BroadcastReceiver>()
             verify(context).registerReceiver(captor.capture(), any())
-            captor.value!!.onReceive(context, intent)
+            captor.lastValue.onReceive(context, intent)
 
-            assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP))
+            // spnIntent() sets all values to true and test strings
+            assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN"))
 
             job.cancel()
         }
 
     @Test
+    @DisableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+    fun networkName_usesBroadcastInfo_returnsDerived_flagOff() =
+        testScope.runTest {
+            var latest: NetworkNameModel? = null
+            val job = underTest.networkName.onEach { latest = it }.launchIn(this)
+
+            val intent = spnIntent()
+            val captor = argumentCaptor<BroadcastReceiver>()
+            verify(context).registerReceiver(captor.capture(), any())
+            captor.lastValue.onReceive(context, intent)
+
+            // spnIntent() sets all values to true and test strings
+            assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
+
+            job.cancel()
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
     fun networkName_broadcastNotForThisSubId_keepsOldValue() =
         testScope.runTest {
             var latest: NetworkNameModel? = null
@@ -831,22 +854,48 @@
             val intent = spnIntent()
             val captor = argumentCaptor<BroadcastReceiver>()
             verify(context).registerReceiver(captor.capture(), any())
-            captor.value!!.onReceive(context, intent)
+            captor.lastValue.onReceive(context, intent)
 
-            assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP))
+            assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN"))
 
             // WHEN an intent with a different subId is sent
             val wrongSubIntent = spnIntent(subId = 101)
 
-            captor.value!!.onReceive(context, wrongSubIntent)
+            captor.lastValue.onReceive(context, wrongSubIntent)
 
             // THEN the previous intent's name is still used
-            assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP))
+            assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN"))
 
             job.cancel()
         }
 
     @Test
+    @DisableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+    fun networkName_broadcastNotForThisSubId_keepsOldValue_flagOff() =
+        testScope.runTest {
+            var latest: NetworkNameModel? = null
+            val job = underTest.networkName.onEach { latest = it }.launchIn(this)
+
+            val intent = spnIntent()
+            val captor = argumentCaptor<BroadcastReceiver>()
+            verify(context).registerReceiver(captor.capture(), any())
+            captor.lastValue.onReceive(context, intent)
+
+            assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
+
+            // WHEN an intent with a different subId is sent
+            val wrongSubIntent = spnIntent(subId = 101)
+
+            captor.lastValue.onReceive(context, wrongSubIntent)
+
+            // THEN the previous intent's name is still used
+            assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
+
+            job.cancel()
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
     fun networkName_broadcastHasNoData_updatesToDefault() =
         testScope.runTest {
             var latest: NetworkNameModel? = null
@@ -855,9 +904,9 @@
             val intent = spnIntent()
             val captor = argumentCaptor<BroadcastReceiver>()
             verify(context).registerReceiver(captor.capture(), any())
-            captor.value!!.onReceive(context, intent)
+            captor.lastValue.onReceive(context, intent)
 
-            assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP))
+            assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN"))
 
             val intentWithoutInfo =
                 spnIntent(
@@ -865,7 +914,7 @@
                     showPlmn = false,
                 )
 
-            captor.value!!.onReceive(context, intentWithoutInfo)
+            captor.lastValue.onReceive(context, intentWithoutInfo)
 
             assertThat(latest).isEqualTo(DEFAULT_NAME_MODEL)
 
@@ -873,6 +922,34 @@
         }
 
     @Test
+    @DisableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+    fun networkName_broadcastHasNoData_updatesToDefault_flagOff() =
+        testScope.runTest {
+            var latest: NetworkNameModel? = null
+            val job = underTest.networkName.onEach { latest = it }.launchIn(this)
+
+            val intent = spnIntent()
+            val captor = argumentCaptor<BroadcastReceiver>()
+            verify(context).registerReceiver(captor.capture(), any())
+            captor.lastValue.onReceive(context, intent)
+
+            assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
+
+            val intentWithoutInfo =
+                spnIntent(
+                    showSpn = false,
+                    showPlmn = false,
+                )
+
+            captor.lastValue.onReceive(context, intentWithoutInfo)
+
+            assertThat(latest).isEqualTo(DEFAULT_NAME_MODEL)
+
+            job.cancel()
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
     fun networkName_usingEagerStrategy_retainsNameBetweenSubscribers() =
         testScope.runTest {
             // Use the [StateFlow.value] getter so we can prove that the collection happens
@@ -884,10 +961,172 @@
             val intent = spnIntent()
             val captor = argumentCaptor<BroadcastReceiver>()
             verify(context).registerReceiver(captor.capture(), any())
-            captor.value!!.onReceive(context, intent)
+            captor.lastValue.onReceive(context, intent)
 
             // The value is still there despite no active subscribers
-            assertThat(underTest.networkName.value).isEqualTo(intent.toNetworkNameModel(SEP))
+            assertThat(underTest.networkName.value)
+                .isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN"))
+        }
+
+    @Test
+    @DisableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+    fun networkName_usingEagerStrategy_retainsNameBetweenSubscribers_flagOff() =
+        testScope.runTest {
+            // Use the [StateFlow.value] getter so we can prove that the collection happens
+            // even when there is no [Job]
+
+            // Starts out default
+            assertThat(underTest.networkName.value).isEqualTo(DEFAULT_NAME_MODEL)
+
+            val intent = spnIntent()
+            val captor = argumentCaptor<BroadcastReceiver>()
+            verify(context).registerReceiver(captor.capture(), any())
+            captor.lastValue.onReceive(context, intent)
+
+            // The value is still there despite no active subscribers
+            assertThat(underTest.networkName.value)
+                .isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+    fun networkName_allFieldsSet_doesNotUseDataSpn() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.networkName)
+            val captor = argumentCaptor<BroadcastReceiver>()
+            verify(context).registerReceiver(captor.capture(), any())
+
+            val intent =
+                spnIntent(
+                    subId = SUB_1_ID,
+                    showSpn = true,
+                    spn = SPN,
+                    dataSpn = DATA_SPN,
+                    showPlmn = true,
+                    plmn = PLMN,
+                )
+            captor.lastValue.onReceive(context, intent)
+            assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN"))
+        }
+
+    @Test
+    @DisableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+    fun networkName_allFieldsSet_flagOff() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.networkName)
+            val captor = argumentCaptor<BroadcastReceiver>()
+            verify(context).registerReceiver(captor.capture(), any())
+
+            val intent =
+                spnIntent(
+                    subId = SUB_1_ID,
+                    showSpn = true,
+                    spn = SPN,
+                    dataSpn = DATA_SPN,
+                    showPlmn = true,
+                    plmn = PLMN,
+                )
+            captor.lastValue.onReceive(context, intent)
+            assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+    fun networkName_showPlmn_plmnNotNull_showSpn_spnNull_dataSpnNotNull() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.networkName)
+            val captor = argumentCaptor<BroadcastReceiver>()
+            verify(context).registerReceiver(captor.capture(), any())
+            val intent =
+                spnIntent(
+                    subId = SUB_1_ID,
+                    showSpn = true,
+                    spn = null,
+                    dataSpn = DATA_SPN,
+                    showPlmn = true,
+                    plmn = PLMN,
+                )
+            captor.lastValue.onReceive(context, intent)
+            assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN"))
+        }
+
+    @Test
+    @DisableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+    fun networkName_showPlmn_plmnNotNull_showSpn_spnNull_dataSpnNotNull_flagOff() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.networkName)
+            val captor = argumentCaptor<BroadcastReceiver>()
+            verify(context).registerReceiver(captor.capture(), any())
+            val intent =
+                spnIntent(
+                    subId = SUB_1_ID,
+                    showSpn = true,
+                    spn = null,
+                    dataSpn = DATA_SPN,
+                    showPlmn = true,
+                    plmn = PLMN,
+                )
+            captor.lastValue.onReceive(context, intent)
+            assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
+        }
+
+    @Test
+    fun networkName_showPlmn_noShowSPN() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.networkName)
+            val captor = argumentCaptor<BroadcastReceiver>()
+            verify(context).registerReceiver(captor.capture(), any())
+            val intent =
+                spnIntent(
+                    subId = SUB_1_ID,
+                    showSpn = false,
+                    spn = SPN,
+                    dataSpn = DATA_SPN,
+                    showPlmn = true,
+                    plmn = PLMN,
+                )
+            captor.lastValue.onReceive(context, intent)
+            assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN"))
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+    fun networkName_showPlmn_plmnNull_showSpn() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.networkName)
+            val captor = argumentCaptor<BroadcastReceiver>()
+            verify(context).registerReceiver(captor.capture(), any())
+            val intent =
+                spnIntent(
+                    subId = SUB_1_ID,
+                    showSpn = true,
+                    spn = SPN,
+                    dataSpn = DATA_SPN,
+                    showPlmn = true,
+                    plmn = null,
+                )
+            captor.lastValue.onReceive(context, intent)
+            assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$SPN"))
+        }
+
+    @Test
+    @DisableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+    fun networkName_showPlmn_plmnNull_showSpn_flagOff() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.networkName)
+            val captor = argumentCaptor<BroadcastReceiver>()
+            verify(context).registerReceiver(captor.capture(), any())
+            val intent =
+                spnIntent(
+                    subId = SUB_1_ID,
+                    showSpn = true,
+                    spn = SPN,
+                    dataSpn = DATA_SPN,
+                    showPlmn = true,
+                    plmn = null,
+                )
+            captor.lastValue.onReceive(context, intent)
+            assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$DATA_SPN"))
         }
 
     @Test
@@ -1128,14 +1367,16 @@
     private fun spnIntent(
         subId: Int = SUB_1_ID,
         showSpn: Boolean = true,
-        spn: String = SPN,
+        spn: String? = SPN,
+        dataSpn: String? = DATA_SPN,
         showPlmn: Boolean = true,
-        plmn: String = PLMN,
+        plmn: String? = PLMN,
     ): Intent =
         Intent(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED).apply {
             putExtra(EXTRA_SUBSCRIPTION_INDEX, subId)
             putExtra(EXTRA_SHOW_SPN, showSpn)
             putExtra(EXTRA_SPN, spn)
+            putExtra(EXTRA_DATA_SPN, dataSpn)
             putExtra(EXTRA_SHOW_PLMN, showPlmn)
             putExtra(EXTRA_PLMN, plmn)
         }
@@ -1148,6 +1389,7 @@
         private const val SEP = "-"
 
         private const val SPN = "testSpn"
+        private const val DATA_SPN = "testDataSpn"
         private const val PLMN = "testPlmn"
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt
index b0acd03..2e6d0fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt
@@ -385,7 +385,7 @@
     private class FakeSettingsProxy(val testDispatcher: CoroutineDispatcher) : SettingsProxy {
 
         private val mContentResolver = mock(ContentResolver::class.java)
-        private val settingToValueMap: MutableMap<String, String> = mutableMapOf()
+        private val settingToValueMap: MutableMap<String, String?> = mutableMapOf()
 
         override fun getContentResolver() = mContentResolver
 
@@ -399,15 +399,15 @@
             return settingToValueMap[name] ?: ""
         }
 
-        override fun putString(name: String, value: String): Boolean {
+        override fun putString(name: String, value: String?): Boolean {
             settingToValueMap[name] = value
             return true
         }
 
         override fun putString(
             name: String,
-            value: String,
-            tag: String,
+            value: String?,
+            tag: String?,
             makeDefault: Boolean
         ): Boolean {
             settingToValueMap[name] = value
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt
index eaeece9..00b8cd0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt
@@ -561,7 +561,7 @@
     ) : UserSettingsProxy {
 
         private val mContentResolver = mock(ContentResolver::class.java)
-        private val userIdToSettingsValueMap: MutableMap<Int, MutableMap<String, String>> =
+        private val userIdToSettingsValueMap: MutableMap<Int, MutableMap<String, String?>> =
             mutableMapOf()
 
         override fun getContentResolver() = mContentResolver
@@ -577,7 +577,7 @@
 
         override fun putString(
             name: String,
-            value: String,
+            value: String?,
             overrideableByRestore: Boolean
         ): Boolean {
             userIdToSettingsValueMap[DEFAULT_USER_ID]?.put(name, value)
@@ -586,22 +586,22 @@
 
         override fun putString(
             name: String,
-            value: String,
-            tag: String,
+            value: String?,
+            tag: String?,
             makeDefault: Boolean
         ): Boolean {
             putStringForUser(name, value, DEFAULT_USER_ID)
             return true
         }
 
-        override fun putStringForUser(name: String, value: String, userHandle: Int): Boolean {
+        override fun putStringForUser(name: String, value: String?, userHandle: Int): Boolean {
             userIdToSettingsValueMap[userHandle] = mutableMapOf(Pair(name, value))
             return true
         }
 
         override fun putStringForUser(
             name: String,
-            value: String,
+            value: String?,
             tag: String?,
             makeDefault: Boolean,
             userHandle: Int,
diff --git a/packages/SystemUI/tests/utils/src/android/content/ContextKosmos.kt b/packages/SystemUI/tests/utils/src/android/content/ContextKosmos.kt
index 185deea..a61233a 100644
--- a/packages/SystemUI/tests/utils/src/android/content/ContextKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/android/content/ContextKosmos.kt
@@ -16,10 +16,12 @@
 
 package android.content
 
+import com.android.systemui.SysuiTestableContext
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testCase
 import com.android.systemui.util.mockito.mock
 
-var Kosmos.applicationContext: Context by
+var Kosmos.testableContext: SysuiTestableContext by
     Kosmos.Fixture { testCase.context.apply { ensureTestableResources() } }
+var Kosmos.applicationContext: Context by Kosmos.Fixture { testableContext }
 val Kosmos.mockedContext: Context by Kosmos.Fixture { mock<Context>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorKosmos.kt
index d5411ad..3680e65 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorKosmos.kt
@@ -19,6 +19,7 @@
 import android.content.mockedContext
 import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
 import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
 import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
@@ -42,5 +43,6 @@
             activityStarter = activityStarter,
             powerInteractor = powerInteractor,
             keyguardTransitionInteractor = keyguardTransitionInteractor,
+            communalSceneInteractor = communalSceneInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt
index edf4bcc..1d2bce2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt
@@ -17,10 +17,9 @@
 package com.android.systemui.education.data.repository
 
 import com.android.systemui.kosmos.Kosmos
-import java.time.Clock
 import java.time.Instant
 
 var Kosmos.contextualEducationRepository: ContextualEducationRepository by
-    Kosmos.Fixture { FakeContextualEducationRepository(fakeEduClock) }
+    Kosmos.Fixture { FakeContextualEducationRepository() }
 
-var Kosmos.fakeEduClock: Clock by Kosmos.Fixture { FakeEduClock(Instant.MIN) }
+var Kosmos.fakeEduClock: FakeEduClock by Kosmos.Fixture { FakeEduClock(Instant.MIN) }
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 3816e1b..aa1968a 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
@@ -18,12 +18,11 @@
 
 import com.android.systemui.contextualeducation.GestureType
 import com.android.systemui.education.data.model.GestureEduModel
-import java.time.Clock
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
 
-class FakeContextualEducationRepository(private val clock: Clock) : ContextualEducationRepository {
+class FakeContextualEducationRepository : ContextualEducationRepository {
 
     private val userGestureMap = mutableMapOf<Int, GestureEduModel>()
     private val _gestureEduModels = MutableStateFlow(GestureEduModel())
@@ -44,16 +43,11 @@
         return gestureEduModelsFlow
     }
 
-    override suspend fun incrementSignalCount(gestureType: GestureType) {
-        val originalModel = _gestureEduModels.value
-        _gestureEduModels.value =
-            originalModel.copy(
-                signalCount = _gestureEduModels.value.signalCount + 1,
-            )
-    }
-
-    override suspend fun updateShortcutTriggerTime(gestureType: GestureType) {
-        val originalModel = _gestureEduModels.value
-        _gestureEduModels.value = originalModel.copy(lastShortcutTriggeredTime = clock.instant())
+    override suspend fun updateGestureEduModel(
+        gestureType: GestureType,
+        transform: (GestureEduModel) -> GestureEduModel
+    ) {
+        val currentModel = _gestureEduModels.value
+        _gestureEduModels.value = transform(currentModel)
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeEduClock.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeEduClock.kt
index 513c143..c9a5d4b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeEduClock.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeEduClock.kt
@@ -19,8 +19,9 @@
 import java.time.Clock
 import java.time.Instant
 import java.time.ZoneId
+import kotlin.time.Duration
 
-class FakeEduClock(private val base: Instant) : Clock() {
+class FakeEduClock(private var base: Instant) : Clock() {
     private val zone: ZoneId = ZoneId.of("UTC")
 
     override fun instant(): Instant {
@@ -34,4 +35,8 @@
     override fun getZone(): ZoneId {
         return zone
     }
+
+    fun offset(duration: Duration) {
+        base = base.plusSeconds(duration.inWholeSeconds)
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractorKosmos.kt
index a7b322b..5c99a7f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractorKosmos.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.education.domain.interactor
 
 import com.android.systemui.education.data.repository.contextualEducationRepository
+import com.android.systemui.education.data.repository.fakeEduClock
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
@@ -28,6 +29,7 @@
             backgroundScope = testScope.backgroundScope,
             backgroundDispatcher = testDispatcher,
             repository = contextualEducationRepository,
-            selectedUserInteractor = selectedUserInteractor
+            selectedUserInteractor = selectedUserInteractor,
+            clock = fakeEduClock
         )
     }
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 fb4e901..5088677 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.education.domain.interactor
 
+import com.android.systemui.education.data.repository.fakeEduClock
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testScope
 
@@ -23,7 +24,8 @@
     Kosmos.Fixture {
         KeyboardTouchpadEduInteractor(
             backgroundScope = testScope.backgroundScope,
-            contextualEducationInteractor = contextualEducationInteractor
+            contextualEducationInteractor = contextualEducationInteractor,
+            clock = fakeEduClock
         )
     }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 727de9e..4571c19 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -74,6 +74,8 @@
     private val _dozeTimeTick = MutableStateFlow<Long>(0L)
     override val dozeTimeTick = _dozeTimeTick
 
+    override val showDismissibleKeyguard = MutableStateFlow<Long>(0L)
+
     private val _lastDozeTapToWakePosition = MutableStateFlow<Point?>(null)
     override val lastDozeTapToWakePosition = _lastDozeTapToWakePosition.asStateFlow()
 
@@ -206,6 +208,10 @@
         _dozeTimeTick.value = millis
     }
 
+    override fun showDismissibleKeyguard() {
+        showDismissibleKeyguard.value = showDismissibleKeyguard.value + 1
+    }
+
     override fun setLastDozeTapToWakePosition(position: Point) {
         _lastDozeTapToWakePosition.value = position
     }
@@ -216,6 +222,9 @@
 
     override fun setDreaming(isDreaming: Boolean) {
         _isDreaming.value = isDreaming
+        // Intentionally set both for testing, to avoid races with merge() in the interactor that
+        // would make testing difficult
+        _isDreamingWithOverlay.value = isDreaming
     }
 
     fun setDreamingWithOverlay(isDreaming: Boolean) {
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 c5da10e..b68d6a0 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
@@ -33,6 +33,7 @@
             fromAodTransitionInteractor = { fromAodTransitionInteractor },
             fromAlternateBouncerTransitionInteractor = { fromAlternateBouncerTransitionInteractor },
             fromDozingTransitionInteractor = { fromDozingTransitionInteractor },
+            fromOccludedTransitionInteractor = { fromOccludedTransitionInteractor },
             sceneInteractor = sceneInteractor
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt
index 2919d3f..1e95fc1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.keyguard.ui.binder
 
 import android.content.applicationContext
-import android.view.layoutInflater
 import android.view.mockedLayoutInflater
 import android.view.windowManager
 import com.android.systemui.biometrics.domain.interactor.fingerprintPropertyInteractor
@@ -25,7 +24,6 @@
 import com.android.systemui.common.ui.domain.interactor.configurationInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
 import com.android.systemui.deviceentry.ui.viewmodel.AlternateBouncerUdfpsAccessibilityOverlayViewModel
-import com.android.systemui.keyguard.dismissCallbackRegistry
 import com.android.systemui.keyguard.ui.SwipeUpAnywhereGestureHandler
 import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies
 import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerMessageAreaViewModel
@@ -50,10 +48,10 @@
             alternateBouncerDependencies = { alternateBouncerDependencies },
             windowManager = { windowManager },
             layoutInflater = { mockedLayoutInflater },
-            dismissCallbackRegistry = dismissCallbackRegistry,
         )
     }
 
+@ExperimentalCoroutinesApi
 private val Kosmos.alternateBouncerDependencies by
     Kosmos.Fixture {
         AlternateBouncerDependencies(
@@ -69,6 +67,7 @@
         )
     }
 
+@ExperimentalCoroutinesApi
 private val Kosmos.alternateBouncerUdfpsIconViewModel by
     Kosmos.Fixture {
         AlternateBouncerUdfpsIconViewModel(
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelKosmos.kt
index bdd4afa..2958315 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelKosmos.kt
@@ -18,6 +18,8 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
+import com.android.systemui.keyguard.dismissCallbackRegistry
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
@@ -28,5 +30,7 @@
     AlternateBouncerViewModel(
         statusBarKeyguardViewManager = statusBarKeyguardViewManager,
         keyguardTransitionInteractor = keyguardTransitionInteractor,
+        dismissCallbackRegistry = dismissCallbackRegistry,
+        alternateBouncerInteractor = { alternateBouncerInteractor },
     )
 }
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 82860fc..b9443bc 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
@@ -39,6 +39,7 @@
         communalInteractor = communalInteractor,
         keyguardTransitionInteractor = keyguardTransitionInteractor,
         notificationsKeyguardInteractor = notificationsKeyguardInteractor,
+        alternateBouncerToAodTransitionViewModel = alternateBouncerToAodTransitionViewModel,
         alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel,
         alternateBouncerToLockscreenTransitionViewModel =
             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 550ecb3..19b32bc 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
@@ -20,7 +20,6 @@
 import com.android.systemui.keyguard.domain.interactor.keyguardBlueprintInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.unfold.domain.interactor.unfoldTransitionInteractor
@@ -33,7 +32,6 @@
             authController = authController,
             touchHandling = keyguardTouchHandlingViewModel,
             shadeInteractor = shadeInteractor,
-            applicationScope = applicationCoroutineScope,
             unfoldTransitionInteractor = unfoldTransitionInteractor,
             occlusionInteractor = sceneContainerOcclusionInteractor,
         )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/controller/KeyguardMediaController.kt
similarity index 69%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/controller/KeyguardMediaController.kt
index d44e061..aed320c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/controller/KeyguardMediaController.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.
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone
+package com.android.systemui.media.controls.controller
 
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.media.controls.ui.controller.KeyguardMediaController
 import com.android.systemui.util.mockito.mock
 
-var Kosmos.notificationIconAreaController by Fixture { mock<NotificationIconAreaController>() }
+val Kosmos.keyguardMediaController by Kosmos.Fixture { mock<KeyguardMediaController>() }
diff --git a/telephony/java/android/telephony/satellite/stub/ProvisionSubscriberId.aidl b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/airplane/AirplaneModeTileKosmos.kt
similarity index 64%
copy from telephony/java/android/telephony/satellite/stub/ProvisionSubscriberId.aidl
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/airplane/AirplaneModeTileKosmos.kt
index 460de8c..73b1859 100644
--- a/telephony/java/android/telephony/satellite/stub/ProvisionSubscriberId.aidl
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/airplane/AirplaneModeTileKosmos.kt
@@ -14,18 +14,11 @@
  * limitations under the License.
  */
 
-package android.telephony.satellite.stub;
+package com.android.systemui.qs.tiles.impl.airplane
 
-/**
- * {@hide}
- */
-parcelable ProvisionSubscriberId {
-    /** provision subscriberId */
-    String subscriberId;
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.qsEventLogger
+import com.android.systemui.statusbar.connectivity.ConnectivityModule
 
-    /** carrier id */
-    int mCarrierId;
-
-    /** apn */
-    String mNiddApn;
-}
+val Kosmos.qsAirplaneModeTileConfig by
+    Kosmos.Fixture { ConnectivityModule.provideAirplaneModeTileConfig(qsEventLogger) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt
index b6194e3..bbe753e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt
@@ -27,7 +27,9 @@
 class FakeQSSceneAdapter(
     private val inflateDelegate: suspend (Context) -> View,
     override val qqsHeight: Int = 0,
+    override val squishedQqsHeight: Int = 0,
     override val qsHeight: Int = 0,
+    override val squishedQsHeight: Int = 0,
 ) : QSSceneAdapter {
     private val _customizerState = MutableStateFlow<CustomizerState>(CustomizerState.Hidden)
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModelKosmos.kt
similarity index 78%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModelKosmos.kt
index 299b22e..5d70ed6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModelKosmos.kt
@@ -18,13 +18,11 @@
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.shade.domain.interactor.shadeInteractor
-import com.android.systemui.shade.ui.viewmodel.overlayShadeViewModel
 
-val Kosmos.quickSettingsShadeSceneViewModel: QuickSettingsShadeSceneViewModel by
+val Kosmos.quickSettingsShadeSceneActionsViewModel: QuickSettingsShadeSceneActionsViewModel by
     Kosmos.Fixture {
-        QuickSettingsShadeSceneViewModel(
+        QuickSettingsShadeSceneActionsViewModel(
             shadeInteractor = shadeInteractor,
-            overlayShadeViewModel = overlayShadeViewModel,
             quickSettingsContainerViewModel = quickSettingsContainerViewModel,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneContentViewModelKosmos.kt
similarity index 73%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneContentViewModelKosmos.kt
index 299b22e..5ad5cb2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneContentViewModelKosmos.kt
@@ -14,17 +14,18 @@
  * 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.domain.interactor.shadeInteractor
-import com.android.systemui.shade.ui.viewmodel.overlayShadeViewModel
+import com.android.systemui.shade.ui.viewmodel.overlayShadeViewModelFactory
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 
-val Kosmos.quickSettingsShadeSceneViewModel: QuickSettingsShadeSceneViewModel by
+val Kosmos.quickSettingsShadeSceneContentViewModel: QuickSettingsShadeSceneContentViewModel by
     Kosmos.Fixture {
-        QuickSettingsShadeSceneViewModel(
-            shadeInteractor = shadeInteractor,
-            overlayShadeViewModel = overlayShadeViewModel,
+        QuickSettingsShadeSceneContentViewModel(
+            overlayShadeViewModelFactory = overlayShadeViewModelFactory,
             quickSettingsContainerViewModel = quickSettingsContainerViewModel,
         )
     }
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 8e76a0b..53b6a2e 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
@@ -18,6 +18,7 @@
 
 import com.android.internal.logging.uiEventLogger
 import com.android.systemui.authentication.domain.interactor.authenticationInteractor
+import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
 import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
 import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
 import com.android.systemui.classifier.falsingCollector
@@ -80,5 +81,6 @@
         keyguardEnabledInteractor = keyguardEnabledInteractor,
         dismissCallbackRegistry = dismissCallbackRegistry,
         statusBarStateController = sysuiStatusBarStateController,
+        alternateBouncerInteractor = alternateBouncerInteractor,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelKosmos.kt
index 8fb370c..32a5614 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelKosmos.kt
@@ -18,15 +18,23 @@
 
 import android.content.res.mainResources
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor
 import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel
 import com.android.systemui.settings.brightnessSliderControllerFactory
 
-val Kosmos.brightnessMirrorViewModel by
-    Kosmos.Fixture {
-        BrightnessMirrorViewModel(
-            brightnessMirrorShowingInteractor,
-            mainResources,
-            brightnessSliderControllerFactory,
-        )
+val Kosmos.brightnessMirrorViewModel by Fixture {
+    BrightnessMirrorViewModel(
+        brightnessMirrorShowingInteractor,
+        mainResources,
+        brightnessSliderControllerFactory,
+    )
+}
+
+val Kosmos.brightnessMirrorViewModelFactory by Fixture {
+    object : BrightnessMirrorViewModel.Factory {
+        override fun create(): BrightnessMirrorViewModel {
+            return brightnessMirrorViewModel
+        }
     }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt
index ea02d0c..6d488d2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt
@@ -18,10 +18,13 @@
 
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.SysuiTestableContext
+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.shade.data.repository.FakeShadeRepository
+import com.android.systemui.shade.data.repository.ShadeRepository
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.TestScope
@@ -86,6 +89,11 @@
         delegate.assertFlagValid()
         delegate.setLegacyExpandedOrAwaitingInputTransfer(legacyExpandedOrAwaitingInputTransfer)
     }
+
+    fun setSplitShade(splitShade: Boolean) {
+        delegate.assertFlagValid()
+        delegate.setSplitShade(splitShade)
+    }
 }
 
 /** Sets up shade state for tests for a specific value of the scene container flag. */
@@ -117,11 +125,16 @@
     fun setQsFullscreen(qsFullscreen: Boolean)
 
     fun setLegacyExpandedOrAwaitingInputTransfer(legacyExpandedOrAwaitingInputTransfer: Boolean)
+
+    fun setSplitShade(splitShade: Boolean)
 }
 
 /** Sets up shade state for tests when the scene container flag is disabled. */
-class ShadeTestUtilLegacyImpl(val testScope: TestScope, val shadeRepository: FakeShadeRepository) :
-    ShadeTestUtilDelegate {
+class ShadeTestUtilLegacyImpl(
+    val testScope: TestScope,
+    val shadeRepository: FakeShadeRepository,
+    val context: SysuiTestableContext
+) : ShadeTestUtilDelegate {
     override fun setShadeAndQsExpansion(shadeExpansion: Float, qsExpansion: Float) {
         shadeRepository.setLegacyShadeExpansion(shadeExpansion)
         shadeRepository.setQsExpansion(qsExpansion)
@@ -168,11 +181,22 @@
     override fun setLegacyExpandedOrAwaitingInputTransfer(expanded: Boolean) {
         shadeRepository.setLegacyExpandedOrAwaitingInputTransfer(expanded)
     }
+
+    override fun setSplitShade(splitShade: Boolean) {
+        context
+            .getOrCreateTestableResources()
+            .addOverride(R.bool.config_use_split_notification_shade, splitShade)
+        testScope.runCurrent()
+    }
 }
 
 /** Sets up shade state for tests when the scene container flag is enabled. */
-class ShadeTestUtilSceneImpl(val testScope: TestScope, val sceneInteractor: SceneInteractor) :
-    ShadeTestUtilDelegate {
+class ShadeTestUtilSceneImpl(
+    val testScope: TestScope,
+    val sceneInteractor: SceneInteractor,
+    val shadeRepository: ShadeRepository,
+    val context: SysuiTestableContext,
+) : ShadeTestUtilDelegate {
     val isUserInputOngoing = MutableStateFlow(true)
 
     override fun setShadeAndQsExpansion(shadeExpansion: Float, qsExpansion: Float) {
@@ -263,6 +287,14 @@
         testScope.runCurrent()
     }
 
+    override fun setSplitShade(splitShade: Boolean) {
+        context
+            .getOrCreateTestableResources()
+            .addOverride(R.bool.config_use_split_notification_shade, splitShade)
+        shadeRepository.setShadeLayoutWide(splitShade)
+        testScope.runCurrent()
+    }
+
     override fun assertFlagValid() {
         Assert.assertTrue(SceneContainerFlag.isEnabled)
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtilKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtilKosmos.kt
index 9eeb345..a1551e0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtilKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtilKosmos.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.shade
 
+import android.content.testableContext
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -26,9 +27,14 @@
     Kosmos.Fixture {
         ShadeTestUtil(
             if (SceneContainerFlag.isEnabled) {
-                ShadeTestUtilSceneImpl(testScope, sceneInteractor)
+                ShadeTestUtilSceneImpl(
+                    testScope,
+                    sceneInteractor,
+                    fakeShadeRepository,
+                    testableContext
+                )
             } else {
-                ShadeTestUtilLegacyImpl(testScope, fakeShadeRepository)
+                ShadeTestUtilLegacyImpl(testScope, fakeShadeRepository, testableContext)
             }
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
index bfd6614..54208b9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
@@ -44,7 +44,7 @@
         ShadeInteractorSceneContainerImpl(
             scope = applicationCoroutineScope,
             sceneInteractor = sceneInteractor,
-            sharedNotificationContainerInteractor = sharedNotificationContainerInteractor,
+            shadeRepository = shadeRepository,
         )
     }
 val Kosmos.shadeInteractorLegacyImpl by
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneActionsViewModelKosmos.kt
similarity index 74%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneViewModelKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneActionsViewModelKosmos.kt
index 72a80d4..9bf4756 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneActionsViewModelKosmos.kt
@@ -17,8 +17,13 @@
 package com.android.systemui.shade.ui.viewmodel
 
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeSceneViewModel
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeSceneActionsViewModel
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 
-val Kosmos.notificationsShadeSceneViewModel: NotificationsShadeSceneViewModel by
-    Kosmos.Fixture { NotificationsShadeSceneViewModel(shadeInteractor) }
+val Kosmos.notificationsShadeSceneActionsViewModel:
+    NotificationsShadeSceneActionsViewModel by Fixture {
+    NotificationsShadeSceneActionsViewModel(
+        shadeInteractor = shadeInteractor,
+    )
+}
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 cd4fab8..6252d44 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,8 +16,14 @@
 
 package com.android.systemui.shade.ui.viewmodel
 
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.kosmos.Kosmos
 
 val Kosmos.notificationShadeWindowModel: NotificationShadeWindowModel by
-    Kosmos.Fixture { NotificationShadeWindowModel(keyguardTransitionInteractor) }
+    Kosmos.Fixture {
+        NotificationShadeWindowModel(
+            keyguardTransitionInteractor,
+            keyguardInteractor,
+        )
+    }
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
index 8d4d547..00f1526 100644
--- 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
@@ -17,15 +17,22 @@
 package com.android.systemui.shade.ui.viewmodel
 
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 
 val Kosmos.overlayShadeViewModel: OverlayShadeViewModel by
     Kosmos.Fixture {
         OverlayShadeViewModel(
-            applicationScope = applicationCoroutineScope,
             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/ShadeHeaderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt
index 0e21698..7eb9f34 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt
@@ -19,7 +19,6 @@
 import android.content.applicationContext
 import com.android.systemui.broadcast.broadcastDispatcher
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.plugins.activityStarter
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.shade.domain.interactor.privacyChipInteractor
@@ -31,7 +30,6 @@
 val Kosmos.shadeHeaderViewModel: ShadeHeaderViewModel by
     Kosmos.Fixture {
         ShadeHeaderViewModel(
-            applicationScope = applicationCoroutineScope,
             context = applicationContext,
             activityStarter = activityStarter,
             sceneInteractor = sceneInteractor,
@@ -43,3 +41,12 @@
             broadcastDispatcher = broadcastDispatcher,
         )
     }
+
+val Kosmos.shadeHeaderViewModelFactory: ShadeHeaderViewModel.Factory by
+    Kosmos.Fixture {
+        object : ShadeHeaderViewModel.Factory {
+            override fun create(): ShadeHeaderViewModel {
+                return shadeHeaderViewModel
+            }
+        }
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModelKosmos.kt
similarity index 66%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneViewModelKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModelKosmos.kt
index 72a80d4..2387aa8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModelKosmos.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2024 The Android Open Source Project
+ * 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.
@@ -17,8 +17,13 @@
 package com.android.systemui.shade.ui.viewmodel
 
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeSceneViewModel
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.qs.ui.adapter.qsSceneAdapter
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 
-val Kosmos.notificationsShadeSceneViewModel: NotificationsShadeSceneViewModel by
-    Kosmos.Fixture { NotificationsShadeSceneViewModel(shadeInteractor) }
+val Kosmos.shadeSceneActionsViewModel: ShadeSceneActionsViewModel by Fixture {
+    ShadeSceneActionsViewModel(
+        qsSceneAdapter = qsSceneAdapter,
+        shadeInteractor = shadeInteractor,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModelKosmos.kt
new file mode 100644
index 0000000..7097d31
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModelKosmos.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.shade.ui.viewmodel
+
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
+import com.android.systemui.qs.footerActionsController
+import com.android.systemui.qs.footerActionsViewModelFactory
+import com.android.systemui.qs.ui.adapter.qsSceneAdapter
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModelFactory
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.unfold.domain.interactor.unfoldTransitionInteractor
+
+val Kosmos.shadeSceneContentViewModel: ShadeSceneContentViewModel by Fixture {
+    ShadeSceneContentViewModel(
+        shadeHeaderViewModelFactory = shadeHeaderViewModelFactory,
+        qsSceneAdapter = qsSceneAdapter,
+        brightnessMirrorViewModelFactory = brightnessMirrorViewModelFactory,
+        mediaCarouselInteractor = mediaCarouselInteractor,
+        shadeInteractor = shadeInteractor,
+        footerActionsViewModelFactory = footerActionsViewModelFactory,
+        footerActionsController = footerActionsController,
+        unfoldTransitionInteractor = unfoldTransitionInteractor,
+        deviceEntryInteractor = deviceEntryInteractor,
+        sceneInteractor = sceneInteractor,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelKosmos.kt
deleted file mode 100644
index 2c5a0f4..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelKosmos.kt
+++ /dev/null
@@ -1,42 +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.shade.ui.viewmodel
-
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
-import com.android.systemui.qs.footerActionsController
-import com.android.systemui.qs.footerActionsViewModelFactory
-import com.android.systemui.qs.ui.adapter.qsSceneAdapter
-import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModel
-import com.android.systemui.shade.domain.interactor.shadeInteractor
-import com.android.systemui.unfold.domain.interactor.unfoldTransitionInteractor
-
-val Kosmos.shadeSceneViewModel: ShadeSceneViewModel by
-    Kosmos.Fixture {
-        ShadeSceneViewModel(
-            shadeHeaderViewModel = shadeHeaderViewModel,
-            qsSceneAdapter = qsSceneAdapter,
-            brightnessMirrorViewModel = brightnessMirrorViewModel,
-            mediaCarouselInteractor = mediaCarouselInteractor,
-            shadeInteractor = shadeInteractor,
-            footerActionsViewModelFactory = footerActionsViewModelFactory,
-            footerActionsController = footerActionsController,
-            sceneInteractor = sceneInteractor,
-            unfoldTransitionInteractor = unfoldTransitionInteractor,
-        )
-    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
similarity index 72%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index d44e061..2a522ce 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.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.
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone
+package com.android.systemui.statusbar.lockscreen
 
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.util.mockito.mock
 
-var Kosmos.notificationIconAreaController by Fixture { mock<NotificationIconAreaController>() }
+val Kosmos.lockscreenSmartspaceController by
+    Kosmos.Fixture { mock<LockscreenSmartspaceController>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt
index 8909d75..3234e66 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.shade.largeScreenHeaderHelper
 import com.android.systemui.statusbar.policy.splitShadeStateController
 
@@ -29,7 +30,8 @@
         SharedNotificationContainerInteractor(
             configurationRepository = configurationRepository,
             context = applicationContext,
-            splitShadeStateController = splitShadeStateController,
+            splitShadeStateController = { splitShadeStateController },
+            shadeInteractor = { shadeInteractor },
             keyguardInteractor = keyguardInteractor,
             deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
             largeScreenHeaderHelperLazy = { largeScreenHeaderHelper }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
index ee3216b..bc1363a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
@@ -29,22 +29,20 @@
 import com.android.systemui.statusbar.notification.stack.ui.view.notificationStatsLogger
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationListViewModel
 import com.android.systemui.statusbar.notification.ui.viewbinder.headsUpNotificationViewBinder
-import com.android.systemui.statusbar.phone.notificationIconAreaController
 import java.util.Optional
 
 val Kosmos.notificationListViewBinder by Fixture {
     NotificationListViewBinder(
-        viewModel = notificationListViewModel,
         backgroundDispatcher = testDispatcher,
+        hiderTracker = displaySwitchNotificationsHiderTracker,
         configuration = configurationState,
         falsingManager = falsingManager,
         hunBinder = headsUpNotificationViewBinder,
-        iconAreaController = notificationIconAreaController,
         loggerOptional = Optional.of(notificationStatsLogger),
         metricsLogger = metricsLogger,
-        hiderTracker = displaySwitchNotificationsHiderTracker,
         nicBinder = notificationIconContainerShelfViewBinder,
         notificationActivityStarter = { notificationActivityStarter },
         silentHeaderController = silentHeaderController,
+        viewModel = notificationListViewModel,
     )
 }
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 afb8acb..20dc668 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
@@ -21,7 +21,6 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.shade.domain.interactor.shadeInteractor
-import com.android.systemui.shade.ui.viewmodel.shadeSceneViewModel
 import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
 
@@ -30,7 +29,6 @@
         dumpManager = dumpManager,
         interactor = notificationStackAppearanceInteractor,
         shadeInteractor = shadeInteractor,
-        shadeSceneViewModel = shadeSceneViewModel,
         headsUpNotificationInteractor = headsUpNotificationInteractor,
         featureFlags = featureFlagsClassic,
     )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/DozeServiceHostKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/DozeServiceHostKosmos.kt
index 0b5a68e..4a6757d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/DozeServiceHostKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/DozeServiceHostKosmos.kt
@@ -53,7 +53,6 @@
             notificationShadeWindowController,
             notificationWakeUpCoordinator,
             authController,
-            notificationIconAreaController,
             shadeLockscreenInteractor,
             dozeInteractor,
         )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
deleted file mode 100644
index d117466..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
+++ /dev/null
@@ -1,198 +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.systemui.util.settings;
-
-import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
-
-import android.annotation.UserIdInt;
-import android.content.ContentResolver;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.UserHandle;
-import android.util.Pair;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
-
-import kotlinx.coroutines.CoroutineDispatcher;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-public class FakeSettings implements SecureSettings, SystemSettings {
-    private final Map<SettingsKey, String> mValues = new HashMap<>();
-    private final Map<SettingsKey, List<ContentObserver>> mContentObservers =
-            new HashMap<>();
-    private final Map<String, List<ContentObserver>> mContentObserversAllUsers = new HashMap<>();
-    private final CoroutineDispatcher mDispatcher;
-
-    public static final Uri CONTENT_URI = Uri.parse("content://settings/fake");
-    @UserIdInt
-    private int mUserId = UserHandle.USER_CURRENT;
-
-    private final CurrentUserIdProvider mCurrentUserProvider;
-
-    /**
-     * @deprecated Please use FakeSettings(testDispatcher) to provide the same dispatcher used
-     * by main test scope.
-     */
-    @Deprecated
-    public FakeSettings() {
-        mDispatcher = StandardTestDispatcher(/* scheduler = */ null, /* name = */ null);
-        mCurrentUserProvider = () -> mUserId;
-    }
-
-    public FakeSettings(CoroutineDispatcher dispatcher) {
-        mDispatcher = dispatcher;
-        mCurrentUserProvider = () -> mUserId;
-    }
-
-    public FakeSettings(CoroutineDispatcher dispatcher, CurrentUserIdProvider currentUserProvider) {
-        mDispatcher = dispatcher;
-        mCurrentUserProvider = currentUserProvider;
-    }
-
-    @VisibleForTesting
-    FakeSettings(String initialKey, String initialValue) {
-        this();
-        putString(initialKey, initialValue);
-    }
-
-    @VisibleForTesting
-    FakeSettings(Map<String, String> initialValues) {
-        this();
-        for (Map.Entry<String, String> kv : initialValues.entrySet()) {
-            putString(kv.getKey(), kv.getValue());
-        }
-    }
-
-    @Override
-    @NonNull
-    public ContentResolver getContentResolver() {
-        throw new UnsupportedOperationException(
-                "FakeSettings.getContentResolver is not implemented");
-    }
-
-    @NonNull
-    @Override
-    public CurrentUserIdProvider getCurrentUserProvider() {
-        return mCurrentUserProvider;
-    }
-
-    @NonNull
-    @Override
-    public CoroutineDispatcher getBackgroundDispatcher() {
-        return mDispatcher;
-    }
-
-    @Override
-    public void registerContentObserverForUserSync(@NonNull Uri uri, boolean notifyDescendants,
-            @NonNull ContentObserver settingsObserver, int userHandle) {
-        List<ContentObserver> observers;
-        if (userHandle == UserHandle.USER_ALL) {
-            mContentObserversAllUsers.putIfAbsent(uri.toString(), new ArrayList<>());
-            observers = mContentObserversAllUsers.get(uri.toString());
-        } else {
-            SettingsKey key = new SettingsKey(userHandle, uri.toString());
-            mContentObservers.putIfAbsent(key, new ArrayList<>());
-            observers = mContentObservers.get(key);
-        }
-        observers.add(settingsObserver);
-    }
-
-    @Override
-    public void unregisterContentObserverSync(@NonNull ContentObserver settingsObserver) {
-        for (List<ContentObserver> observers : mContentObservers.values()) {
-            observers.remove(settingsObserver);
-        }
-        for (List<ContentObserver> observers : mContentObserversAllUsers.values()) {
-            observers.remove(settingsObserver);
-        }
-    }
-
-    @NonNull
-    @Override
-    public Uri getUriFor(@NonNull String name) {
-        return Uri.withAppendedPath(CONTENT_URI, name);
-    }
-
-    public void setUserId(@UserIdInt int userId) {
-        mUserId = userId;
-    }
-
-    @Override
-    public int getUserId() {
-        return mUserId;
-    }
-
-    @Override
-    public String getString(@NonNull String name) {
-        return getStringForUser(name, getUserId());
-    }
-
-    @Override
-    public String getStringForUser(@NonNull String name, int userHandle) {
-        return mValues.get(new SettingsKey(userHandle, getUriFor(name).toString()));
-    }
-
-    @Override
-    public boolean putString(@NonNull String name, @NonNull String value,
-            boolean overrideableByRestore) {
-        return putStringForUser(name, value, null, false, getUserId(), overrideableByRestore);
-    }
-
-    @Override
-    public boolean putString(@NonNull String name, @NonNull String value) {
-        return putString(name, value, false);
-    }
-
-    @Override
-    public boolean putStringForUser(@NonNull String name, @NonNull String value, int userHandle) {
-        return putStringForUser(name, value, null, false, userHandle, false);
-    }
-
-    @Override
-    public boolean putStringForUser(@NonNull String name, @NonNull String value, String tag,
-            boolean makeDefault, int userHandle, boolean overrideableByRestore) {
-        SettingsKey key = new SettingsKey(userHandle, getUriFor(name).toString());
-        mValues.put(key, value);
-
-        Uri uri = getUriFor(name);
-        for (ContentObserver observer : mContentObservers.getOrDefault(key, new ArrayList<>())) {
-            observer.dispatchChange(false, List.of(uri), 0, userHandle);
-        }
-        for (ContentObserver observer :
-                mContentObserversAllUsers.getOrDefault(uri.toString(), new ArrayList<>())) {
-            observer.dispatchChange(false, List.of(uri), 0, userHandle);
-        }
-        return true;
-    }
-
-    @Override
-    public boolean putString(@NonNull String name, @NonNull String value, @NonNull String tag,
-            boolean makeDefault) {
-        return putString(name, value);
-    }
-
-    private static class SettingsKey extends Pair<Integer, String> {
-        SettingsKey(Integer first, String second) {
-            super(first, second);
-        }
-    }
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.kt
new file mode 100644
index 0000000..e5d113b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.kt
@@ -0,0 +1,307 @@
+/*
+ * 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.systemui.util.settings
+
+import android.annotation.UserIdInt
+import android.content.ContentResolver
+import android.database.ContentObserver
+import android.net.Uri
+import android.os.UserHandle
+import android.util.Pair
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.util.settings.SettingsProxy.CurrentUserIdProvider
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestDispatcher
+
+class FakeSettings : SecureSettings, SystemSettings, UserSettingsProxy {
+    private val values = mutableMapOf<SettingsKey, String?>()
+    private val contentObservers = mutableMapOf<SettingsKey, MutableList<ContentObserver>>()
+    private val contentObserversAllUsers = mutableMapOf<String, MutableList<ContentObserver>>()
+
+    override val backgroundDispatcher: CoroutineDispatcher
+
+    @UserIdInt override var userId = UserHandle.USER_CURRENT
+    override val currentUserProvider: CurrentUserIdProvider
+
+    @Deprecated(
+        """Please use FakeSettings(testDispatcher) to provide the same dispatcher used
+      by main test scope."""
+    )
+    constructor() {
+        backgroundDispatcher = StandardTestDispatcher(scheduler = null, name = null)
+        currentUserProvider = CurrentUserIdProvider { userId }
+    }
+
+    constructor(dispatcher: CoroutineDispatcher) {
+        backgroundDispatcher = dispatcher
+        currentUserProvider = CurrentUserIdProvider { userId }
+    }
+
+    constructor(dispatcher: CoroutineDispatcher, currentUserProvider: CurrentUserIdProvider) {
+        backgroundDispatcher = dispatcher
+        this.currentUserProvider = currentUserProvider
+    }
+
+    @VisibleForTesting
+    internal constructor(initialKey: String, initialValue: String) : this() {
+        putString(initialKey, initialValue)
+    }
+
+    @VisibleForTesting
+    internal constructor(initialValues: Map<String, String>) : this() {
+        for ((key, value) in initialValues) {
+            putString(key, value)
+        }
+    }
+
+    override fun getContentResolver(): ContentResolver {
+        throw UnsupportedOperationException("FakeSettings.getContentResolver is not implemented")
+    }
+
+    override fun registerContentObserverForUserSync(
+        uri: Uri,
+        notifyForDescendants: Boolean,
+        settingsObserver: ContentObserver,
+        userHandle: Int
+    ) {
+        if (userHandle == UserHandle.USER_ALL) {
+            contentObserversAllUsers
+                .getOrPut(uri.toString()) { mutableListOf() }
+                .add(settingsObserver)
+        } else {
+            val key = SettingsKey(userHandle, uri.toString())
+            contentObservers.getOrPut(key) { mutableListOf() }.add(settingsObserver)
+        }
+    }
+
+    override fun unregisterContentObserverSync(settingsObserver: ContentObserver) {
+        contentObservers.values.onEach { it.remove(settingsObserver) }
+        contentObserversAllUsers.values.onEach { it.remove(settingsObserver) }
+    }
+
+    override suspend fun registerContentObserver(uri: Uri, settingsObserver: ContentObserver) =
+        suspendAdvanceDispatcher {
+            super<UserSettingsProxy>.registerContentObserver(uri, settingsObserver)
+        }
+
+    override fun registerContentObserverAsync(uri: Uri, settingsObserver: ContentObserver): Job =
+        advanceDispatcher {
+            super<UserSettingsProxy>.registerContentObserverAsync(uri, settingsObserver)
+        }
+
+    override suspend fun registerContentObserver(
+        uri: Uri,
+        notifyForDescendants: Boolean,
+        settingsObserver: ContentObserver
+    ) = suspendAdvanceDispatcher {
+        super<UserSettingsProxy>.registerContentObserver(
+            uri,
+            notifyForDescendants,
+            settingsObserver
+        )
+    }
+
+    override fun registerContentObserverAsync(
+        uri: Uri,
+        notifyForDescendants: Boolean,
+        settingsObserver: ContentObserver
+    ): Job = advanceDispatcher {
+        super<UserSettingsProxy>.registerContentObserverAsync(
+            uri,
+            notifyForDescendants,
+            settingsObserver
+        )
+    }
+
+    override suspend fun registerContentObserverForUser(
+        name: String,
+        settingsObserver: ContentObserver,
+        userHandle: Int
+    ) = suspendAdvanceDispatcher {
+        super<UserSettingsProxy>.registerContentObserverForUser(name, settingsObserver, userHandle)
+    }
+
+    override fun registerContentObserverForUserAsync(
+        name: String,
+        settingsObserver: ContentObserver,
+        userHandle: Int
+    ): Job = advanceDispatcher {
+        super<UserSettingsProxy>.registerContentObserverForUserAsync(
+            name,
+            settingsObserver,
+            userHandle
+        )
+    }
+
+    override fun unregisterContentObserverAsync(settingsObserver: ContentObserver): Job =
+        advanceDispatcher {
+            super<UserSettingsProxy>.unregisterContentObserverAsync(settingsObserver)
+        }
+
+    override suspend fun registerContentObserverForUser(
+        uri: Uri,
+        settingsObserver: ContentObserver,
+        userHandle: Int
+    ) = suspendAdvanceDispatcher {
+        super<UserSettingsProxy>.registerContentObserverForUser(uri, settingsObserver, userHandle)
+    }
+
+    override fun registerContentObserverForUserAsync(
+        uri: Uri,
+        settingsObserver: ContentObserver,
+        userHandle: Int
+    ): Job = advanceDispatcher {
+        super<UserSettingsProxy>.registerContentObserverForUserAsync(
+            uri,
+            settingsObserver,
+            userHandle
+        )
+    }
+
+    override fun registerContentObserverForUserAsync(
+        uri: Uri,
+        settingsObserver: ContentObserver,
+        userHandle: Int,
+        registered: Runnable
+    ): Job = advanceDispatcher {
+        super<UserSettingsProxy>.registerContentObserverForUserAsync(
+            uri,
+            settingsObserver,
+            userHandle,
+            registered
+        )
+    }
+
+    override suspend fun registerContentObserverForUser(
+        name: String,
+        notifyForDescendants: Boolean,
+        settingsObserver: ContentObserver,
+        userHandle: Int
+    ) = suspendAdvanceDispatcher {
+        super<UserSettingsProxy>.registerContentObserverForUser(
+            name,
+            notifyForDescendants,
+            settingsObserver,
+            userHandle
+        )
+    }
+
+    override fun registerContentObserverForUserAsync(
+        name: String,
+        notifyForDescendants: Boolean,
+        settingsObserver: ContentObserver,
+        userHandle: Int
+    ) = advanceDispatcher {
+        super<UserSettingsProxy>.registerContentObserverForUserAsync(
+            name,
+            notifyForDescendants,
+            settingsObserver,
+            userHandle
+        )
+    }
+
+    override fun registerContentObserverForUserAsync(
+        uri: Uri,
+        notifyForDescendants: Boolean,
+        settingsObserver: ContentObserver,
+        userHandle: Int
+    ): Job = advanceDispatcher {
+        super<UserSettingsProxy>.registerContentObserverForUserAsync(
+            uri,
+            notifyForDescendants,
+            settingsObserver,
+            userHandle
+        )
+    }
+
+    override fun getUriFor(name: String): Uri {
+        return Uri.withAppendedPath(CONTENT_URI, name)
+    }
+
+    override fun getString(name: String): String? {
+        return getStringForUser(name, userId)
+    }
+
+    override fun getStringForUser(name: String, userHandle: Int): String? {
+        return values[SettingsKey(userHandle, getUriFor(name).toString())]
+    }
+
+    override fun putString(name: String, value: String?, overrideableByRestore: Boolean): Boolean {
+        return putStringForUser(name, value, null, false, userId, overrideableByRestore)
+    }
+
+    override fun putString(name: String, value: String?): Boolean {
+        return putString(name, value, false)
+    }
+
+    override fun putStringForUser(name: String, value: String?, userHandle: Int): Boolean {
+        return putStringForUser(name, value, null, false, userHandle, false)
+    }
+
+    override fun putStringForUser(
+        name: String,
+        value: String?,
+        tag: String?,
+        makeDefault: Boolean,
+        userHandle: Int,
+        overrideableByRestore: Boolean
+    ): Boolean {
+        val key = SettingsKey(userHandle, getUriFor(name).toString())
+        values[key] = value
+        val uri = getUriFor(name)
+        contentObservers[key]?.onEach { it.dispatchChange(false, listOf(uri), 0, userHandle) }
+        contentObserversAllUsers[uri.toString()]?.onEach {
+            it.dispatchChange(false, listOf(uri), 0, userHandle)
+        }
+        return true
+    }
+
+    override fun putString(
+        name: String,
+        value: String?,
+        tag: String?,
+        makeDefault: Boolean
+    ): Boolean {
+        return putString(name, value)
+    }
+
+    /** Runs current jobs on dispatcher after calling the method. */
+    private fun <T> advanceDispatcher(f: () -> T): T {
+        val result = f()
+        testDispatcherRunCurrent()
+        return result
+    }
+
+    private suspend fun <T> suspendAdvanceDispatcher(f: suspend () -> T): T {
+        val result = f()
+        testDispatcherRunCurrent()
+        return result
+    }
+
+    private fun testDispatcherRunCurrent() {
+        val testDispatcher = backgroundDispatcher as? TestDispatcher
+        testDispatcher?.scheduler?.runCurrent()
+    }
+
+    private data class SettingsKey(val first: Int, val second: String) :
+        Pair<Int, String>(first, second)
+
+    companion object {
+        val CONTENT_URI = Uri.parse("content://settings/fake")
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/data/repository/VolumePanelGlobalStateRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/data/repository/VolumePanelGlobalStateRepositoryKosmos.kt
index 2ba1211..0b438d1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/data/repository/VolumePanelGlobalStateRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/data/repository/VolumePanelGlobalStateRepositoryKosmos.kt
@@ -18,6 +18,7 @@
 
 import com.android.systemui.dump.dumpManager
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.volume.shared.volumePanelLogger
 
 val Kosmos.volumePanelGlobalStateRepository by
-    Kosmos.Fixture { VolumePanelGlobalStateRepository(dumpManager) }
+    Kosmos.Fixture { VolumePanelGlobalStateRepository(dumpManager, volumePanelLogger) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorKosmos.kt
index a18f498..3804a9f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorKosmos.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.volume.panel.domain.defaultCriteria
 import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
 import com.android.systemui.volume.panel.ui.composable.enabledComponents
+import com.android.systemui.volume.shared.volumePanelLogger
 import javax.inject.Provider
 
 var Kosmos.criteriaByKey: Map<VolumePanelComponentKey, Provider<ComponentAvailabilityCriteria>> by
@@ -50,6 +51,7 @@
             enabledComponents,
             { defaultCriteria },
             testScope.backgroundScope,
+            volumePanelLogger,
             criteriaByKey,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelKosmos.kt
index 34a008f..c4fb9e4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelKosmos.kt
@@ -18,17 +18,19 @@
 
 import android.content.applicationContext
 import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.dump.dumpManager
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.statusbar.policy.configurationController
 import com.android.systemui.volume.panel.dagger.factory.volumePanelComponentFactory
 import com.android.systemui.volume.panel.domain.VolumePanelStartable
 import com.android.systemui.volume.panel.domain.interactor.volumePanelGlobalStateInteractor
+import com.android.systemui.volume.shared.volumePanelLogger
 
 var Kosmos.volumePanelStartables: Set<VolumePanelStartable> by Kosmos.Fixture { emptySet() }
 
 var Kosmos.volumePanelViewModel: VolumePanelViewModel by
-    Kosmos.Fixture { volumePanelViewModelFactory.create(testScope.backgroundScope) }
+    Kosmos.Fixture { volumePanelViewModelFactory.create(applicationCoroutineScope) }
 
 val Kosmos.volumePanelViewModelFactory: VolumePanelViewModel.Factory by
     Kosmos.Fixture {
@@ -37,6 +39,8 @@
             volumePanelComponentFactory,
             configurationController,
             broadcastDispatcher,
+            dumpManager,
+            volumePanelLogger,
             volumePanelGlobalStateInteractor,
         )
     }
diff --git a/packages/overlays/HsumConfigOverlay/Android.bp b/packages/overlays/HsumDefaultConfigOverlay/Android.bp
similarity index 92%
rename from packages/overlays/HsumConfigOverlay/Android.bp
rename to packages/overlays/HsumDefaultConfigOverlay/Android.bp
index 050b1f0..bff2f9b 100644
--- a/packages/overlays/HsumConfigOverlay/Android.bp
+++ b/packages/overlays/HsumDefaultConfigOverlay/Android.bp
@@ -8,7 +8,7 @@
 }
 
 runtime_resource_overlay {
-    name: "HsumConfigOverlay",
+    name: "HsumDefaultConfigOverlay",
     certificate: "platform",
 
     product_specific: true,
diff --git a/packages/overlays/HsumConfigOverlay/AndroidManifest.xml b/packages/overlays/HsumDefaultConfigOverlay/AndroidManifest.xml
similarity index 93%
rename from packages/overlays/HsumConfigOverlay/AndroidManifest.xml
rename to packages/overlays/HsumDefaultConfigOverlay/AndroidManifest.xml
index cd7a879..dcd1741 100644
--- a/packages/overlays/HsumConfigOverlay/AndroidManifest.xml
+++ b/packages/overlays/HsumDefaultConfigOverlay/AndroidManifest.xml
@@ -15,7 +15,7 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.internal.overlay.hsumconfig"
+    package="com.android.internal.overlay.hsum.defaultconfig"
     android:versionCode="1"
     android:versionName="1.0">
     <overlay android:targetPackage="android" android:priority="2" android:isStatic="true" />
diff --git a/packages/overlays/HsumConfigOverlay/OWNERS b/packages/overlays/HsumDefaultConfigOverlay/OWNERS
similarity index 100%
rename from packages/overlays/HsumConfigOverlay/OWNERS
rename to packages/overlays/HsumDefaultConfigOverlay/OWNERS
diff --git a/packages/overlays/HsumConfigOverlay/res/values/config.xml b/packages/overlays/HsumDefaultConfigOverlay/res/values/config.xml
similarity index 100%
rename from packages/overlays/HsumConfigOverlay/res/values/config.xml
rename to packages/overlays/HsumDefaultConfigOverlay/res/values/config.xml
diff --git a/proto/src/windowmanager.proto b/proto/src/windowmanager.proto
index da4dfa9..6c8a486 100644
--- a/proto/src/windowmanager.proto
+++ b/proto/src/windowmanager.proto
@@ -45,6 +45,7 @@
   int32 letterbox_inset_top = 18;
   int32 letterbox_inset_right = 19;
   int32 letterbox_inset_bottom = 20;
+  int32 ui_mode = 21;
 }
 
 // Persistent letterboxing configurations
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index 2de3c5e..6150343 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -280,10 +280,10 @@
     src: "scripts/ravenwood-stats-checker.sh",
     test_suites: ["general-tests"],
     data: [
-        ":framework-minus-apex.ravenwood-base{hoststubgen_framework-minus-apex_stats.csv}",
-        ":framework-minus-apex.ravenwood-base{hoststubgen_framework-minus-apex_apis.csv}",
-        ":framework-minus-apex.ravenwood-base{hoststubgen_framework-minus-apex_keep_all.txt}",
-        ":framework-minus-apex.ravenwood-base{hoststubgen_framework-minus-apex_dump.txt}",
+        ":framework-minus-apex.ravenwood-base_all{hoststubgen_framework-minus-apex_stats.csv}",
+        ":framework-minus-apex.ravenwood-base_all{hoststubgen_framework-minus-apex_apis.csv}",
+        ":framework-minus-apex.ravenwood-base_all{hoststubgen_framework-minus-apex_keep_all.txt}",
+        ":framework-minus-apex.ravenwood-base_all{hoststubgen_framework-minus-apex_dump.txt}",
         ":services.core.ravenwood-base{hoststubgen_services.core_stats.csv}",
         ":services.core.ravenwood-base{hoststubgen_services.core_apis.csv}",
         ":services.core.ravenwood-base{hoststubgen_services.core_keep_all.txt}",
diff --git a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
index 68f185e..cc9b70e 100644
--- a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
@@ -281,6 +281,12 @@
 
 com.android.server.LocalServices
 
+com.android.internal.graphics.cam.Cam
+com.android.internal.graphics.cam.CamUtils
+com.android.internal.graphics.cam.Frame
+com.android.internal.graphics.cam.HctSolver
+com.android.internal.graphics.ColorUtils
+
 com.android.internal.util.BitUtils
 com.android.internal.util.BitwiseInputStream
 com.android.internal.util.BitwiseOutputStream
diff --git a/services/Android.bp b/services/Android.bp
index ded7379..0006455 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -120,6 +120,7 @@
         ":services.backup-sources",
         ":services.companion-sources",
         ":services.contentcapture-sources",
+        ":services.appfunctions-sources",
         ":services.contentsuggestions-sources",
         ":services.contextualsearch-sources",
         ":services.coverage-sources",
@@ -217,6 +218,7 @@
         "services.autofill",
         "services.backup",
         "services.companion",
+        "services.appfunctions",
         "services.contentcapture",
         "services.contentsuggestions",
         "services.contextualsearch",
diff --git a/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java b/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java
index 56da231..2ce5c2b 100644
--- a/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java
+++ b/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java
@@ -78,6 +78,9 @@
     private final AccessibilityManagerService mAms;
     private final Handler mHandler;
 
+    /** Thread to wait for virtual mouse creation to complete */
+    private final Thread mCreateVirtualMouseThread;
+
     VirtualDeviceManager.VirtualDevice mVirtualDevice = null;
 
     private VirtualMouse mVirtualMouse = null;
@@ -154,34 +157,47 @@
         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.
-        // This is because virtual device creation is a blocking operation and can cause a
-        // deadlock if it is called from the handler's thread.
-        new Thread(() -> {
+        // This is because the handler thread is the same as the main thread,
+        // and the main thread will be blocked waiting for the virtual device to be created.
+        mCreateVirtualMouseThread = new Thread(() -> {
             mVirtualMouse = createVirtualMouse(displayId);
-        }).start();
+        });
+        mCreateVirtualMouseThread.start();
+    }
 
+    /**
+     * Wait for {@code mVirtualMouse} to be created.
+     * This will ensure that {@code mVirtualMouse} is always created before
+     * trying to send mouse events.
+     **/
+    private void waitForVirtualMouseCreation() {
+        try {
+            // Block the current thread until the virtual mouse creation thread completes.
+            mCreateVirtualMouseThread.join();
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new RuntimeException(e);
+        }
     }
 
     @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     private void sendVirtualMouseRelativeEvent(float x, float y) {
-        if (mVirtualMouse != null) {
-            mVirtualMouse.sendRelativeEvent(new VirtualMouseRelativeEvent.Builder()
-                    .setRelativeX(x)
-                    .setRelativeY(y)
-                    .build()
-            );
-        }
+        waitForVirtualMouseCreation();
+        mVirtualMouse.sendRelativeEvent(new VirtualMouseRelativeEvent.Builder()
+                .setRelativeX(x)
+                .setRelativeY(y)
+                .build()
+        );
     }
 
     @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     private void sendVirtualMouseButtonEvent(int buttonCode, int actionCode) {
-        if (mVirtualMouse != null) {
-            mVirtualMouse.sendButtonEvent(new VirtualMouseButtonEvent.Builder()
-                    .setAction(actionCode)
-                    .setButtonCode(buttonCode)
-                    .build()
-            );
-        }
+        waitForVirtualMouseCreation();
+        mVirtualMouse.sendButtonEvent(new VirtualMouseButtonEvent.Builder()
+                .setAction(actionCode)
+                .setButtonCode(buttonCode)
+                .build()
+        );
     }
 
     /**
@@ -205,12 +221,11 @@
             case DOWN_MOVE_OR_SCROLL -> -1.0f;
             default -> 0.0f;
         };
-        if (mVirtualMouse != null) {
-            mVirtualMouse.sendScrollEvent(new VirtualMouseScrollEvent.Builder()
-                    .setYAxisMovement(y)
-                    .build()
-            );
-        }
+        waitForVirtualMouseCreation();
+        mVirtualMouse.sendScrollEvent(new VirtualMouseScrollEvent.Builder()
+                .setYAxisMovement(y)
+                .build()
+        );
         if (DEBUG) {
             Slog.d(LOG_TAG, "Performed mouse key event: " + mouseKeyEvent.name()
                     + " for scroll action with axis movement (y=" + y + ")");
diff --git a/services/appfunctions/Android.bp b/services/appfunctions/Android.bp
new file mode 100644
index 0000000..f8ee823
--- /dev/null
+++ b/services/appfunctions/Android.bp
@@ -0,0 +1,25 @@
+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.appfunctions-sources",
+    srcs: ["java/**/*.java"],
+    path: "java",
+    visibility: ["//frameworks/base/services"],
+}
+
+java_library_static {
+    name: "services.appfunctions",
+    defaults: ["platform_service_defaults"],
+    srcs: [
+        ":services.appfunctions-sources",
+        "java/**/*.logtags",
+    ],
+    libs: ["services.core"],
+}
diff --git a/services/appfunctions/OWNERS b/services/appfunctions/OWNERS
new file mode 100644
index 0000000..b310894
--- /dev/null
+++ b/services/appfunctions/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/app/appfunctions/OWNERS
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java
new file mode 100644
index 0000000..f30e770
--- /dev/null
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java
@@ -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.server.appfunctions;
+
+import static android.app.appfunctions.flags.Flags.enableAppFunctionManager;
+
+import android.app.appfunctions.IAppFunctionManager;
+import android.content.Context;
+
+import com.android.server.SystemService;
+
+/**
+ * Service that manages app functions.
+ */
+public class AppFunctionManagerService extends SystemService {
+
+    public AppFunctionManagerService(Context context) {
+        super(context);
+    }
+
+    @Override
+    public void onStart() {
+        if (enableAppFunctionManager()) {
+            publishBinderService(Context.APP_FUNCTION_SERVICE, new AppFunctionManagerStub());
+        }
+    }
+
+    private static class AppFunctionManagerStub extends IAppFunctionManager.Stub {
+
+    }
+}
diff --git a/services/autofill/java/com/android/server/autofill/Helper.java b/services/autofill/java/com/android/server/autofill/Helper.java
index 668852b..cd2a535 100644
--- a/services/autofill/java/com/android/server/autofill/Helper.java
+++ b/services/autofill/java/com/android/server/autofill/Helper.java
@@ -16,10 +16,13 @@
 
 package com.android.server.autofill;
 
+import static android.Manifest.permission.INTERACT_ACROSS_USERS;
+
 import static com.android.server.autofill.Helper.sDebug;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.assist.AssistStructure;
@@ -29,6 +32,7 @@
 import android.content.Context;
 import android.hardware.display.DisplayManager;
 import android.metrics.LogMaker;
+import android.os.UserHandle;
 import android.os.UserManager;
 import android.service.autofill.Dataset;
 import android.service.autofill.FillResponse;
@@ -57,7 +61,6 @@
 import java.util.Arrays;
 import java.util.concurrent.atomic.AtomicBoolean;
 
-
 public final class Helper {
 
     private static final String TAG = "AutofillHelper";
@@ -69,7 +72,7 @@
      * {@code cmd autofill set log_level debug} or through
      * {@link android.provider.Settings.Global#AUTOFILL_LOGGING_LEVEL}.
      */
-    public static boolean sDebug = false;
+    public static boolean sDebug = true;
 
     /**
      * Defines a logging flag that can be dynamically changed at runtime using
@@ -104,6 +107,26 @@
     }
 
     /**
+     * Creates the context as the foreground user
+     *
+     * <p>Returns the current context as the current foreground user
+     */
+    @RequiresPermission(INTERACT_ACROSS_USERS)
+    public static Context getUserContext(Context context) {
+        int userId = ActivityManager.getCurrentUser();
+        Context c = context.createContextAsUser(UserHandle.of(userId), /* flags= */ 0);
+        if (sDebug) {
+            Slog.d(
+                    TAG,
+                    "Current User: "
+                            + userId
+                            + ", context created as: "
+                            + c.getContentResolver().getUserId());
+        }
+        return c;
+    }
+
+    /**
      * Checks the URI permissions of the remote view,
      * to see if the current userId is able to access it.
      *
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index b7508b4..6dea8b0 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -5084,11 +5084,17 @@
         // Try to get the custom Icon, if one was passed through FillResponse
         int iconResourceId = response.getIconResourceId();
         if (iconResourceId != 0) {
-            serviceIcon = mService.getMaster().getContext().getPackageManager()
-                .getDrawable(
-                    mService.getServicePackageName(),
-                    iconResourceId,
-                    null);
+            long token = Binder.clearCallingIdentity();
+            try {
+                serviceIcon =
+                        mService.getMaster()
+                                .getContext()
+                                .getPackageManager()
+                                .getDrawable(
+                                        mService.getServicePackageName(), iconResourceId, null);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
 
         // Custom icon wasn't fetched, use the default package icon instead
@@ -5114,11 +5120,19 @@
         // Try to get the custom Service name, if one was passed through FillResponse
         int customServiceNameId = response.getServiceDisplayNameResourceId();
         if (customServiceNameId != 0) {
-            serviceLabel = mService.getMaster().getContext().getPackageManager()
-                .getText(
-                    mService.getServicePackageName(),
-                    customServiceNameId,
-                    null);
+            long token = Binder.clearCallingIdentity();
+            try {
+                serviceLabel =
+                        mService.getMaster()
+                                .getContext()
+                                .getPackageManager()
+                                .getText(
+                                        mService.getServicePackageName(),
+                                        customServiceNameId,
+                                        null);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
 
         // Custom label wasn't fetched, use the default package name instead
diff --git a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
index fa414e3..5a71b89 100644
--- a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
@@ -95,6 +95,7 @@
     private final ComponentName mComponentName;
     private final int mThemeId;
     private final @NonNull Context mContext;
+    private final @NonNull Context mUserContext;
     private final @NonNull UiCallback mCallback;
     private final @NonNull ListView mListView;
     private final @Nullable ItemsAdapter mAdapter;
@@ -104,6 +105,8 @@
     private @Nullable AnnounceFilterResult mAnnounceFilterResult;
     private boolean mDestroyed;
 
+    // System has all permissions, see b/228957088
+    @SuppressWarnings("AndroidFrameworkRequiresPermission")
     DialogFillUi(@NonNull Context context, @NonNull FillResponse response,
             @NonNull AutofillId focusedViewId, @Nullable String filterText,
             @Nullable Drawable serviceIcon, @Nullable String servicePackageName,
@@ -117,6 +120,7 @@
         mComponentName = componentName;
 
         mContext = new ContextThemeWrapper(context, mThemeId);
+        mUserContext = Helper.getUserContext(mContext);
         final LayoutInflater inflater = LayoutInflater.from(mContext);
         final View decor = inflater.inflate(R.layout.autofill_fill_dialog, null);
 
@@ -224,7 +228,7 @@
         };
 
         final View content = presentation.applyWithTheme(
-                mContext, (ViewGroup) decor, interceptionHandler, mThemeId);
+                mUserContext, (ViewGroup) decor, interceptionHandler, mThemeId);
         container.addView(content);
         container.setVisibility(View.VISIBLE);
     }
@@ -263,7 +267,7 @@
             return true;
         };
         final View content = presentation.applyWithTheme(
-                mContext, (ViewGroup) decor, interceptionHandler, mThemeId);
+                mUserContext, (ViewGroup) decor, interceptionHandler, mThemeId);
         container.addView(content);
         container.setVisibility(View.VISIBLE);
         container.setFocusable(true);
@@ -305,7 +309,7 @@
                 try {
                     if (sVerbose) Slog.v(TAG, "setting remote view for " + focusedViewId);
                     view = presentation.applyWithTheme(
-                            mContext, null, interceptionHandler, mThemeId);
+                            mUserContext, null, interceptionHandler, mThemeId);
                 } catch (RuntimeException e) {
                     Slog.e(TAG, "Error inflating remote views", e);
                     continue;
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 1bda70d..14a3211 100644
--- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
@@ -107,6 +107,7 @@
             new AutofillWindowPresenter();
 
     private final @NonNull Context mContext;
+    private final @NonNull Context mUserContext;
 
     private final @NonNull AnchoredWindow mWindow;
 
@@ -141,6 +142,8 @@
         return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
     }
 
+    // System has all permissions, see b/228957088
+    @SuppressWarnings("AndroidFrameworkRequiresPermission")
     FillUi(@NonNull Context context, @NonNull FillResponse response,
             @NonNull AutofillId focusedViewId, @Nullable String filterText,
             @NonNull OverlayControl overlayControl, @NonNull CharSequence serviceLabel,
@@ -153,6 +156,7 @@
         mCallback = callback;
         mFullScreen = isFullScreen(context);
         mContext = new ContextThemeWrapper(context, mThemeId);
+        mUserContext = Helper.getUserContext(mContext);
         mMaxInputLengthForAutofill = maxInputLengthForAutofill;
 
         final LayoutInflater inflater = LayoutInflater.from(mContext);
@@ -245,7 +249,7 @@
                     throw new RuntimeException("Permission error accessing RemoteView");
                 }
                 content = response.getPresentation().applyWithTheme(
-                        mContext, decor, interceptionHandler, mThemeId);
+                        mUserContext, decor, interceptionHandler, mThemeId);
                 container.addView(content);
             } catch (RuntimeException e) {
                 callback.onCanceled();
@@ -286,7 +290,7 @@
             if (headerPresentation != null) {
                 interactionBlocker = newInteractionBlocker();
                 mHeader = headerPresentation.applyWithTheme(
-                        mContext, null, interactionBlocker, mThemeId);
+                        mUserContext, null, interactionBlocker, mThemeId);
                 final LinearLayout headerContainer =
                         decor.findViewById(R.id.autofill_dataset_header);
                 applyCancelAction(mHeader, response.getCancelIds());
@@ -305,7 +309,7 @@
                         interactionBlocker = newInteractionBlocker();
                     }
                     mFooter = footerPresentation.applyWithTheme(
-                            mContext, null, interactionBlocker, mThemeId);
+                            mUserContext, null, interactionBlocker, mThemeId);
                     applyCancelAction(mFooter, response.getCancelIds());
                     // Footer not supported on some platform e.g. TV
                     if (sVerbose) Slog.v(TAG, "adding footer");
@@ -334,7 +338,7 @@
                     try {
                         if (sVerbose) Slog.v(TAG, "setting remote view for " + focusedViewId);
                         view = presentation.applyWithTheme(
-                                mContext, null, interceptionHandler, mThemeId);
+                                mUserContext, null, interceptionHandler, mThemeId);
                     } catch (RuntimeException e) {
                         Slog.e(TAG, "Error inflating remote views", e);
                         continue;
@@ -812,6 +816,7 @@
         pw.print(prefix); pw.print("mContentHeight: "); pw.println(mContentHeight);
         pw.print(prefix); pw.print("mDestroyed: "); pw.println(mDestroyed);
         pw.print(prefix); pw.print("mContext: "); pw.println(mContext);
+        pw.print(prefix); pw.print("mUserContext: "); pw.println(mUserContext);
         pw.print(prefix); pw.print("theme id: "); pw.print(mThemeId);
         switch (mThemeId) {
             case THEME_ID_DARK:
diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
index 4d42f15..2ecce0b 100644
--- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
@@ -176,6 +176,8 @@
 
     private boolean mDestroyed;
 
+    // System has all permissions, see b/228957088
+    @SuppressWarnings("AndroidFrameworkRequiresPermission")
     SaveUi(@NonNull Context context, @NonNull PendingUi pendingUi,
            @NonNull CharSequence serviceLabel, @NonNull Drawable serviceIcon,
            @Nullable String servicePackageName, @NonNull ComponentName componentName,
@@ -193,7 +195,7 @@
         mComponentName = componentName;
         mCompatMode = compatMode;
 
-        context = new ContextThemeWrapper(context, mThemeId) {
+        context = new ContextThemeWrapper(Helper.getUserContext(context), mThemeId) {
             @Override
             public void startActivity(Intent intent) {
                 if (resolveActivity(intent) == null) {
@@ -235,6 +237,7 @@
                 return null;
             }
         };
+
         final LayoutInflater inflater = LayoutInflater.from(context);
         final View view = inflater.inflate(R.layout.autofill_save, null);
 
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 0ab6bbc..42f69e9 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -223,8 +223,8 @@
             // delays (even in case of the Main Thread). It may be fine overall, but would require
             // updating the tests (adding a delay there).
             mPackageMonitor.register(context, FgThread.get().getLooper(), UserHandle.ALL, true);
-            mDevicePresenceProcessor.init(context);
         } else if (phase == PHASE_BOOT_COMPLETED) {
+            mDevicePresenceProcessor.init(context);
             // Run the Inactive Association Removal job service daily.
             InactiveAssociationsRemovalService.schedule(getContext());
             mCrossDeviceSyncController.onBootCompleted();
diff --git a/services/core/java/com/android/server/AlarmManagerInternal.java b/services/core/java/com/android/server/AlarmManagerInternal.java
index b7f2b8d..191137a 100644
--- a/services/core/java/com/android/server/AlarmManagerInternal.java
+++ b/services/core/java/com/android/server/AlarmManagerInternal.java
@@ -17,6 +17,7 @@
 package com.android.server;
 
 import android.annotation.CurrentTimeMillisLong;
+import android.annotation.UserIdInt;
 import android.app.PendingIntent;
 
 import com.android.server.SystemClockTime.TimeConfidence;
@@ -36,6 +37,16 @@
     /** Returns true if AlarmManager is delaying alarms due to device idle. */
     boolean isIdling();
 
+    /**
+     * Returns the time at which the next alarm for the given user is going to trigger, or 0 if
+     * there is none.
+     *
+     * <p>This value is UTC wall clock time in milliseconds, as returned by
+     * {@link System#currentTimeMillis()} for example.
+     * @see android.app.AlarmManager.AlarmClockInfo#getTriggerTime()
+     */
+    long getNextAlarmTriggerTimeForUser(@UserIdInt int userId);
+
     public void removeAlarmsForUid(int uid);
 
     public void registerInFlightListener(InFlightListener callback);
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 2e1416b..d4f729c 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -6962,7 +6962,8 @@
     }
 
     private boolean collectPackageServicesLocked(String packageName, Set<String> filterByClasses,
-            boolean evenPersistent, boolean doit, ArrayMap<ComponentName, ServiceRecord> services) {
+            boolean evenPersistent, boolean doit, int minOomAdj,
+            ArrayMap<ComponentName, ServiceRecord> services) {
         boolean didSomething = false;
         for (int i = services.size() - 1; i >= 0; i--) {
             ServiceRecord service = services.valueAt(i);
@@ -6970,6 +6971,11 @@
                     || (service.packageName.equals(packageName)
                         && (filterByClasses == null
                             || filterByClasses.contains(service.name.getClassName())));
+            if (service.app != null && service.app.mState.getCurAdj() < minOomAdj) {
+                Slog.i(TAG, "Skip force stopping service " + service
+                            + ": below minimum oom adj level");
+                continue;
+            }
             if (sameComponent
                     && (service.app == null || evenPersistent || !service.app.isPersistent())) {
                 if (!doit) {
@@ -6993,6 +6999,12 @@
 
     boolean bringDownDisabledPackageServicesLocked(String packageName, Set<String> filterByClasses,
             int userId, boolean evenPersistent, boolean fullStop, boolean doit) {
+        return bringDownDisabledPackageServicesLocked(packageName, filterByClasses, userId,
+                evenPersistent, fullStop, doit, ProcessList.INVALID_ADJ);
+    }
+
+    boolean bringDownDisabledPackageServicesLocked(String packageName, Set<String> filterByClasses,
+            int userId, boolean evenPersistent, boolean fullStop, boolean doit, int minOomAdj) {
         boolean didSomething = false;
 
         if (mTmpCollectionResults != null) {
@@ -7002,7 +7014,8 @@
         if (userId == UserHandle.USER_ALL) {
             for (int i = mServiceMap.size() - 1; i >= 0; i--) {
                 didSomething |= collectPackageServicesLocked(packageName, filterByClasses,
-                        evenPersistent, doit, mServiceMap.valueAt(i).mServicesByInstanceName);
+                        evenPersistent, doit, minOomAdj,
+                        mServiceMap.valueAt(i).mServicesByInstanceName);
                 if (!doit && didSomething) {
                     return true;
                 }
@@ -7015,7 +7028,7 @@
             if (smap != null) {
                 ArrayMap<ComponentName, ServiceRecord> items = smap.mServicesByInstanceName;
                 didSomething = collectPackageServicesLocked(packageName, filterByClasses,
-                        evenPersistent, doit, items);
+                        evenPersistent, doit, minOomAdj, items);
             }
             if (doit && filterByClasses == null) {
                 forceStopPackageLocked(packageName, userId);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 9d8f337..4a18cb1 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -4377,6 +4377,16 @@
     }
 
     @GuardedBy("this")
+    final boolean forceStopUserPackagesLocked(int userId, String reasonString,
+            boolean evenImportantServices) {
+        int minOomAdj = evenImportantServices ? ProcessList.INVALID_ADJ
+                : ProcessList.FOREGROUND_APP_ADJ;
+        return forceStopPackageInternalLocked(null, -1, false, false,
+                true, false, false, false, userId, reasonString,
+                ApplicationExitInfo.REASON_USER_STOPPED, minOomAdj);
+    }
+
+    @GuardedBy("this")
     final boolean forceStopPackageLocked(String packageName, int appId,
             boolean callerWillRestart, boolean purgeCache, boolean doit,
             boolean evenPersistent, boolean uninstalling, boolean packageStateStopped,
@@ -4385,7 +4395,6 @@
                 : ApplicationExitInfo.REASON_USER_REQUESTED;
         return forceStopPackageLocked(packageName, appId, callerWillRestart, purgeCache, doit,
                 evenPersistent, uninstalling, packageStateStopped, userId, reasonString, reason);
-
     }
 
     @GuardedBy("this")
@@ -4393,6 +4402,16 @@
             boolean callerWillRestart, boolean purgeCache, boolean doit,
             boolean evenPersistent, boolean uninstalling, boolean packageStateStopped,
             int userId, String reasonString, int reason) {
+        return forceStopPackageInternalLocked(packageName, appId, callerWillRestart, purgeCache,
+                doit, evenPersistent, uninstalling, packageStateStopped, userId, reasonString,
+                reason, ProcessList.INVALID_ADJ);
+    }
+
+    @GuardedBy("this")
+    private boolean forceStopPackageInternalLocked(String packageName, int appId,
+            boolean callerWillRestart, boolean purgeCache, boolean doit,
+            boolean evenPersistent, boolean uninstalling, boolean packageStateStopped,
+            int userId, String reasonString, int reason, int minOomAdj) {
         int i;
 
         if (userId == UserHandle.USER_ALL && packageName == null) {
@@ -4431,7 +4450,7 @@
             }
 
             didSomething |= mProcessList.killPackageProcessesLSP(packageName, appId, userId,
-                    ProcessList.INVALID_ADJ, callerWillRestart, false /* allowRestart */, doit,
+                    minOomAdj, callerWillRestart, false /* allowRestart */, doit,
                     evenPersistent, true /* setRemoved */, uninstalling,
                     reason,
                     subReason,
@@ -4440,7 +4459,8 @@
         }
 
         if (mServices.bringDownDisabledPackageServicesLocked(
-                packageName, null /* filterByClasses */, userId, evenPersistent, true, doit)) {
+                packageName, null /* filterByClasses */, userId, evenPersistent,
+                true, doit, minOomAdj)) {
             if (!doit) {
                 return true;
             }
@@ -19872,6 +19892,11 @@
         }
 
         @Override
+        public boolean isEarlyPackageKillEnabledForUserSwitch(int fromUserId, int toUserId) {
+            return mUserController.isEarlyPackageKillEnabledForUserSwitch(fromUserId, toUserId);
+        }
+
+        @Override
         public void setStopUserOnSwitch(int value) {
             ActivityManagerService.this.setStopUserOnSwitch(value);
         }
diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java
index 4a31fd1..4c87e1c 100644
--- a/services/core/java/com/android/server/am/AppRestrictionController.java
+++ b/services/core/java/com/android/server/am/AppRestrictionController.java
@@ -308,7 +308,7 @@
     /**
      * Cache the package name and information about if it's a system module.
      */
-    @GuardedBy("mLock")
+    @GuardedBy("mSystemModulesCache")
     private final HashMap<String, Boolean> mSystemModulesCache = new HashMap<>();
 
     /**
@@ -1603,7 +1603,7 @@
         if (moduleInfos == null) {
             return;
         }
-        synchronized (mLock) {
+        synchronized (mSystemModulesCache) {
             for (ModuleInfo info : moduleInfos) {
                 mSystemModulesCache.put(info.getPackageName(), Boolean.TRUE);
             }
@@ -1611,7 +1611,7 @@
     }
 
     private boolean isSystemModule(String packageName) {
-        synchronized (mLock) {
+        synchronized (mSystemModulesCache) {
             final Boolean val = mSystemModulesCache.get(packageName);
             if (val != null) {
                 return val.booleanValue();
@@ -1639,7 +1639,7 @@
             }
         }
         // Update the cache.
-        synchronized (mLock) {
+        synchronized (mSystemModulesCache) {
             mSystemModulesCache.put(packageName, isSystemModule);
         }
         return isSystemModule;
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 3123268..0b6d135 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -51,6 +51,7 @@
 import java.util.Map;
 import java.util.List;
 import java.util.ArrayList;
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
 
 /**
  * Maps system settings to system properties.
@@ -345,7 +346,7 @@
         // add sys prop sync callback for staged flag values
         DeviceConfig.addOnPropertiesChangedListener(
             NAMESPACE_REBOOT_STAGING,
-            AsyncTask.THREAD_POOL_EXECUTOR,
+            newSingleThreadScheduledExecutor(),
             (DeviceConfig.Properties properties) -> {
 
               for (String flagName : properties.getKeyset()) {
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 30efa3e..bdba6bc 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -131,6 +131,7 @@
 import com.android.internal.util.ObjectUtils;
 import com.android.internal.util.Preconditions;
 import com.android.internal.widget.LockPatternUtils;
+import com.android.server.AlarmManagerInternal;
 import com.android.server.FactoryResetter;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
@@ -247,6 +248,12 @@
     private static final int USER_COMPLETED_EVENT_DELAY_MS = 5 * 1000;
 
     /**
+     * If a user has an alarm in the next this many milliseconds, avoid stopping it due to
+     * scheduled background stopping.
+     */
+    private static final long TIME_BEFORE_USERS_ALARM_TO_AVOID_STOPPING_MS = 60 * 60_000; // 60 mins
+
+    /**
      * Maximum number of users we allow to be running at a time, including system user.
      *
      * <p>This parameter only affects how many background users will be stopped when switching to a
@@ -439,6 +446,15 @@
     @GuardedBy("mLock")
     private final List<PendingUserStart> mPendingUserStarts = new ArrayList<>();
 
+    /**
+     * Contains users which cannot abort the shutdown process.
+     *
+     * <p> For example, we don't abort shutdown for users whose processes have already been stopped
+     * due to {@link #isEarlyPackageKillEnabledForUserSwitch(int, int)}.
+     */
+    @GuardedBy("mLock")
+    private final ArraySet<Integer> mDoNotAbortShutdownUserIds = new ArraySet<>();
+
     private final UserLifecycleListener mUserLifecycleListener = new UserLifecycleListener() {
         @Override
         public void onUserCreated(UserInfo user, Object token) {
@@ -509,11 +525,11 @@
         }
     }
 
-    private boolean shouldStopUserOnSwitch() {
+    private boolean isStopUserOnSwitchEnabled() {
         synchronized (mLock) {
             if (mStopUserOnSwitch != STOP_USER_ON_SWITCH_DEFAULT) {
                 final boolean value = mStopUserOnSwitch == STOP_USER_ON_SWITCH_TRUE;
-                Slogf.i(TAG, "shouldStopUserOnSwitch(): returning overridden value (%b)", value);
+                Slogf.i(TAG, "isStopUserOnSwitchEnabled(): returning overridden value (%b)", value);
                 return value;
             }
         }
@@ -521,6 +537,26 @@
         return property == -1 ? mDelayUserDataLocking : property == 1;
     }
 
+    /**
+     * Get whether or not the previous user's packages will be killed before the user is
+     * stopped during a user switch.
+     *
+     * <p> The primary use case of this method is for {@link com.android.server.SystemService}
+     * classes to call this API in their
+     * {@link com.android.server.SystemService#onUserSwitching} method implementation to prevent
+     * restarting any of the previous user's processes that will be killed during the user switch.
+     */
+    boolean isEarlyPackageKillEnabledForUserSwitch(int fromUserId, int toUserId) {
+        // NOTE: The logic in this method could be extended to cover other cases where
+        // the previous user is also stopped like: guest users, ephemeral users,
+        // and users with DISALLOW_RUN_IN_BACKGROUND. Currently, this is not done
+        // because early killing is not enabled for these cases by default.
+        if (fromUserId == UserHandle.USER_SYSTEM) {
+            return false;
+        }
+        return isStopUserOnSwitchEnabled();
+    }
+
     void finishUserSwitch(UserState uss) {
         // This call holds the AM lock so we post to the handler.
         mHandler.post(() -> {
@@ -1247,6 +1283,7 @@
                 return;
             }
             uss.setState(UserState.STATE_SHUTDOWN);
+            mDoNotAbortShutdownUserIds.remove(userId);
         }
         TimingsTraceAndSlog t = new TimingsTraceAndSlog();
         t.traceBegin("setUserState-STATE_SHUTDOWN-" + userId + "-[stopUser]");
@@ -1555,7 +1592,8 @@
 
     private void stopPackagesOfStoppedUser(@UserIdInt int userId, String reason) {
         if (DEBUG_MU) Slogf.i(TAG, "stopPackagesOfStoppedUser(%d): %s", userId, reason);
-        mInjector.activityManagerForceStopPackage(userId, reason);
+        mInjector.activityManagerForceStopUserPackages(userId, reason,
+                /* evenImportantServices= */ true);
         if (mInjector.getUserManager().isPreCreated(userId)) {
             // Don't fire intent for precreated.
             return;
@@ -1608,6 +1646,21 @@
         }
     }
 
+    private void stopPreviousUserPackagesIfEnabled(int fromUserId, int toUserId) {
+        if (!android.multiuser.Flags.stopPreviousUserApps()
+                || !isEarlyPackageKillEnabledForUserSwitch(fromUserId, toUserId)) {
+            return;
+        }
+        // Stop the previous user's packages early to reduce resource usage
+        // during user switching. Only do this when the previous user will
+        // be stopped regardless.
+        synchronized (mLock) {
+            mDoNotAbortShutdownUserIds.add(fromUserId);
+        }
+        mInjector.activityManagerForceStopUserPackages(fromUserId,
+                "early stop user packages", /* evenImportantServices= */ false);
+    }
+
     void scheduleStartProfiles() {
         // Parent user transition to RUNNING_UNLOCKING happens on FgThread, so it is busy, there is
         // a chance the profile will reach RUNNING_LOCKED while parent is still locked, so no
@@ -1889,7 +1942,8 @@
                     updateStartedUserArrayLU();
                     needStart = true;
                     updateUmState = true;
-                } else if (uss.state == UserState.STATE_SHUTDOWN) {
+                } else if (uss.state == UserState.STATE_SHUTDOWN
+                        || mDoNotAbortShutdownUserIds.contains(userId)) {
                     Slogf.i(TAG, "User #" + userId
                             + " is shutting down - will start after full shutdown");
                     mPendingUserStarts.add(new PendingUserStart(userId, userStartMode,
@@ -2293,7 +2347,7 @@
                 hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, oldUserId);
         synchronized (mLock) {
             // If running in background is disabled or mStopUserOnSwitch mode, stop the user.
-            if (hasRestriction || shouldStopUserOnSwitch()) {
+            if (hasRestriction || isStopUserOnSwitchEnabled()) {
                 Slogf.i(TAG, "Stopping user %d and its profiles on user switch", oldUserId);
                 stopUsersLU(oldUserId, /* allowDelayedLocking= */ false, null, null);
                 return;
@@ -2371,6 +2425,12 @@
     void processScheduledStopOfBackgroundUser(Integer userIdInteger) {
         final int userId = userIdInteger;
         Slogf.d(TAG, "Considering stopping background user %d due to inactivity", userId);
+
+        if (avoidStoppingUserDueToUpcomingAlarm(userId)) {
+            // We want this user running soon for alarm-purposes, so don't stop it now. Reschedule.
+            scheduleStopOfBackgroundUser(userId);
+            return;
+        }
         synchronized (mLock) {
             if (getCurrentOrTargetUserIdLU() == userId) {
                 return;
@@ -2390,6 +2450,18 @@
         }
     }
 
+    /**
+     * Returns whether we should avoid stopping the user now due to it having an alarm set to fire
+     * soon.
+     */
+    private boolean avoidStoppingUserDueToUpcomingAlarm(@UserIdInt int userId) {
+        final long alarmWallclockMs
+                = mInjector.getAlarmManagerInternal().getNextAlarmTriggerTimeForUser(userId);
+        return System.currentTimeMillis() <  alarmWallclockMs
+                && (alarmWallclockMs
+                    < System.currentTimeMillis() + TIME_BEFORE_USERS_ALARM_TO_AVOID_STOPPING_MS);
+    }
+
     private void timeoutUserSwitch(UserState uss, int oldUserId, int newUserId) {
         TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
         t.traceBegin("timeoutUserSwitch-" + oldUserId + "-to-" + newUserId);
@@ -3425,7 +3497,7 @@
             pw.println("  mLastActiveUsersForDelayedLocking:" + mLastActiveUsersForDelayedLocking);
             pw.println("  mDelayUserDataLocking:" + mDelayUserDataLocking);
             pw.println("  mAllowUserUnlocking:" + mAllowUserUnlocking);
-            pw.println("  shouldStopUserOnSwitch():" + shouldStopUserOnSwitch());
+            pw.println("  isStopUserOnSwitchEnabled():" + isStopUserOnSwitchEnabled());
             pw.println("  mStopUserOnSwitch:" + mStopUserOnSwitch);
             pw.println("  mMaxRunningUsers:" + mMaxRunningUsers);
             pw.println("  mBackgroundUserScheduledStopTimeSecs:"
@@ -3522,6 +3594,7 @@
                         Integer.toString(msg.arg1), msg.arg1);
 
                 mInjector.getSystemServiceManager().onUserSwitching(msg.arg2, msg.arg1);
+                stopPreviousUserPackagesIfEnabled(msg.arg2, msg.arg1);
                 scheduleOnUserCompletedEvent(msg.arg1,
                         UserCompletedEventType.EVENT_TYPE_USER_SWITCHING,
                         USER_COMPLETED_EVENT_DELAY_MS);
@@ -3860,6 +3933,10 @@
             return mPowerManagerInternal;
         }
 
+        AlarmManagerInternal getAlarmManagerInternal() {
+            return LocalServices.getService(AlarmManagerInternal.class);
+        }
+
         KeyguardManager getKeyguardManager() {
             return mService.mContext.getSystemService(KeyguardManager.class);
         }
@@ -3896,10 +3973,10 @@
             }.sendNext();
         }
 
-        void activityManagerForceStopPackage(@UserIdInt int userId, String reason) {
+        void activityManagerForceStopUserPackages(@UserIdInt int userId, String reason,
+                boolean evenImportantServices) {
             synchronized (mService) {
-                mService.forceStopPackageLocked(null, -1, false, false, true, false, false, false,
-                        userId, reason);
+                mService.forceStopUserPackagesLocked(userId, reason, evenImportantServices);
             }
         };
 
diff --git a/services/core/java/com/android/server/appop/DiscreteRegistry.java b/services/core/java/com/android/server/appop/DiscreteRegistry.java
index 539dbca..2ce4623 100644
--- a/services/core/java/com/android/server/appop/DiscreteRegistry.java
+++ b/services/core/java/com/android/server/appop/DiscreteRegistry.java
@@ -1413,11 +1413,11 @@
             pw.print("-");
             pw.print(flagsToString(mOpFlag));
             pw.print("] at ");
-            date.setTime(discretizeTimeStamp(mNoteTime));
+            date.setTime(mNoteTime);
             pw.print(sdf.format(date));
             if (mNoteDuration != -1) {
                 pw.print(" for ");
-                pw.print(discretizeDuration(mNoteDuration));
+                pw.print(mNoteDuration);
                 pw.print(" milliseconds ");
             }
             if (mAttributionFlags != AppOpsManager.ATTRIBUTION_FLAGS_NONE) {
diff --git a/services/core/java/com/android/server/audio/TEST_MAPPING b/services/core/java/com/android/server/audio/TEST_MAPPING
index 2cea32a..f050090 100644
--- a/services/core/java/com/android/server/audio/TEST_MAPPING
+++ b/services/core/java/com/android/server/audio/TEST_MAPPING
@@ -10,6 +10,9 @@
                     "include-filter": "android.media.audio.cts.AudioFocusTest"
                 },
                 {
+                    "include-filter": "android.media.audio.cts.AudioPlaybackCaptureTest"
+                },
+                {
                     "include-filter": "android.media.audio.cts.SpatializerTest"
                 }
             ]
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index fe73bfe..feef540 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -385,9 +385,9 @@
                         DEFAULT_APP_ENABLED ? 1 : 0 /* default */,
                         userId) != 0);
             } else if (MANDATORY_BIOMETRICS_ENABLED.equals(uri)) {
-                updateMandatoryBiometricsForAllProfiles();
+                updateMandatoryBiometricsForAllProfiles(userId);
             } else if (MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED.equals(uri)) {
-                updateMandatoryBiometricsRequirementsForAllProfiles();
+                updateMandatoryBiometricsRequirementsForAllProfiles(userId);
             }
         }
 
@@ -431,16 +431,15 @@
 
         public boolean getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser(int userId) {
             if (!mMandatoryBiometricsEnabled.containsKey(userId)) {
-                updateMandatoryBiometricsForAllProfiles();
+                updateMandatoryBiometricsForAllProfiles(userId);
             }
             if (!mMandatoryBiometricsRequirementsSatisfied.containsKey(userId)) {
-                updateMandatoryBiometricsRequirementsForAllProfiles();
+                updateMandatoryBiometricsRequirementsForAllProfiles(userId);
             }
             return mMandatoryBiometricsEnabled.getOrDefault(userId,
                     DEFAULT_MANDATORY_BIOMETRICS_STATUS)
                     && mMandatoryBiometricsRequirementsSatisfied.getOrDefault(userId,
                     DEFAULT_MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED_STATUS)
-                    && mBiometricEnabledForApps.getOrDefault(userId, DEFAULT_APP_ENABLED)
                     && getEnabledForApps(userId)
                     && (mFingerprintEnrolledForUser.getOrDefault(userId, false /* default */)
                     || mFaceEnrolledForUser.getOrDefault(userId, false /* default */));
@@ -455,25 +454,31 @@
             }
         }
 
-        private void updateMandatoryBiometricsForAllProfiles() {
-            final int mainUserId = mUserManager.getMainUser().getIdentifier();
-            for (UserHandle userHandle: mUserManager.getUserProfiles()) {
-                mMandatoryBiometricsEnabled.put(userHandle.getIdentifier(),
+        private void updateMandatoryBiometricsForAllProfiles(int userId) {
+            int effectiveUserId = userId;
+            if (mUserManager.getMainUser() != null) {
+                effectiveUserId = mUserManager.getMainUser().getIdentifier();
+            }
+            for (int profileUserId: mUserManager.getEnabledProfileIds(effectiveUserId)) {
+                mMandatoryBiometricsEnabled.put(profileUserId,
                         Settings.Secure.getIntForUser(
                                 mContentResolver, Settings.Secure.MANDATORY_BIOMETRICS,
                                 DEFAULT_MANDATORY_BIOMETRICS_STATUS ? 1 : 0,
-                                mainUserId) != 0);
+                                effectiveUserId) != 0);
             }
         }
 
-        private void updateMandatoryBiometricsRequirementsForAllProfiles() {
-            final int mainUserId = mUserManager.getMainUser().getIdentifier();
-            for (UserHandle userHandle: mUserManager.getUserProfiles()) {
-                mMandatoryBiometricsRequirementsSatisfied.put(userHandle.getIdentifier(),
+        private void updateMandatoryBiometricsRequirementsForAllProfiles(int userId) {
+            int effectiveUserId = userId;
+            if (mUserManager.getMainUser() != null) {
+                effectiveUserId = mUserManager.getMainUser().getIdentifier();
+            }
+            for (int profileUserId: mUserManager.getEnabledProfileIds(effectiveUserId)) {
+                mMandatoryBiometricsRequirementsSatisfied.put(profileUserId,
                         Settings.Secure.getIntForUser(mContentResolver,
                                 Settings.Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED,
                                 DEFAULT_MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED_STATUS ? 1 : 0,
-                                mainUserId) != 0);
+                                effectiveUserId) != 0);
             }
         }
 
diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java
index df29ca4..fb8a81b 100644
--- a/services/core/java/com/android/server/biometrics/Utils.java
+++ b/services/core/java/com/android/server/biometrics/Utils.java
@@ -586,6 +586,8 @@
         }
     }
 
+    // LINT.IfChange
+
     /**
      * Checks if a client package is running in the background.
      *
@@ -618,4 +620,6 @@
 
         return true;
     }
+    // LINT.ThenChange(frameworks/base/packages/SystemUI/shared/biometrics/src/com/android
+    // /systemui/biometrics/Utils.kt)
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
index 0fdd57d..dca1491 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
@@ -264,4 +264,11 @@
             }
         });
     }
+
+    @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
+    @Override
+    public int getSensorId() {
+        super.getSensorId_enforcePermission();
+        return mSensorId;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
index 8dc560b..caa2c1c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
@@ -293,4 +293,11 @@
             }
         });
     }
+
+    @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
+    @Override
+    public int getSensorId() {
+        super.getSensorId_enforcePermission();
+        return mSensorId;
+    }
 }
\ No newline at end of file
diff --git a/services/core/java/com/android/server/hdmi/DelayedMessageBuffer.java b/services/core/java/com/android/server/hdmi/DelayedMessageBuffer.java
index 46b4f48..4490745 100644
--- a/services/core/java/com/android/server/hdmi/DelayedMessageBuffer.java
+++ b/services/core/java/com/android/server/hdmi/DelayedMessageBuffer.java
@@ -17,8 +17,10 @@
 package com.android.server.hdmi;
 
 import android.hardware.hdmi.HdmiDeviceInfo;
+
 import java.util.ArrayList;
 import java.util.Iterator;
+import java.util.List;
 
 /**
  * Buffer storage to keep incoming messages for later processing. Used to
@@ -83,6 +85,16 @@
         return false;
     }
 
+    List<HdmiCecMessage> getBufferedMessagesWithOpcode(int opcode) {
+        List<HdmiCecMessage> messages = new ArrayList<>();
+        for (HdmiCecMessage message : mBuffer) {
+            if (message.getOpcode() == opcode) {
+                messages.add(message);
+            }
+        }
+        return messages;
+    }
+
     void processAllMessages() {
         // Use the copied buffer.
         ArrayList<HdmiCecMessage> copiedBuffer = new ArrayList<>(mBuffer);
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 49888db..154710f 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -217,7 +217,9 @@
         resetSelectRequestBuffer();
         launchDeviceDiscovery();
         startQueuedActions();
-        if (!mDelayedMessageBuffer.isBuffered(Constants.MESSAGE_ACTIVE_SOURCE)) {
+        List<HdmiCecMessage> bufferedActiveSource = mDelayedMessageBuffer
+                .getBufferedMessagesWithOpcode(Constants.MESSAGE_ACTIVE_SOURCE);
+        if (bufferedActiveSource.isEmpty()) {
             if (hasAction(RequestActiveSourceAction.class)) {
                 Slog.i(TAG, "RequestActiveSourceAction is in progress. Restarting.");
                 removeAction(RequestActiveSourceAction.class);
@@ -236,9 +238,33 @@
                     }
                 }
             }));
+        } else {
+            addCecDeviceForBufferedActiveSource(bufferedActiveSource.get(0));
         }
     }
 
+    // Add a new CEC device with known information from the buffered <Active Source> message. This
+    // helps TvInputCallback#onInputAdded to be called such that the message can be processed and
+    // the TV to switch to the new active input.
+    @ServiceThreadOnly
+    private void addCecDeviceForBufferedActiveSource(HdmiCecMessage bufferedActiveSource) {
+        assertRunOnServiceThread();
+        if (bufferedActiveSource == null) {
+            return;
+        }
+        int source = bufferedActiveSource.getSource();
+        int physicalAddress = HdmiUtils.twoBytesToInt(bufferedActiveSource.getParams());
+        List<Integer> deviceTypes = HdmiUtils.getTypeFromAddress(source);
+        HdmiDeviceInfo newDevice = HdmiDeviceInfo.cecDeviceBuilder()
+                .setLogicalAddress(source)
+                .setPhysicalAddress(physicalAddress)
+                .setDisplayName(HdmiUtils.getDefaultDeviceName(source))
+                .setDeviceType(deviceTypes.get(0))
+                .setVendorId(Constants.VENDOR_ID_UNKNOWN)
+                .build();
+        mService.getHdmiCecNetwork().addCecDevice(newDevice);
+    }
+
     @ServiceThreadOnly
     public void setSelectRequestBuffer(SelectRequestBuffer requestBuffer) {
         assertRunOnServiceThread();
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
index 7746276..310f592 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
@@ -485,6 +485,7 @@
      * @return true if the hour is valid
      */
     private static boolean isValidHour(int value) {
+        value = bcdToDecimal(value);
         return isWithinRange(value, 0, 23);
     }
 
@@ -496,6 +497,7 @@
      * @return true if the minute is valid
      */
     private static boolean isValidMinute(int value) {
+        value = bcdToDecimal(value);
         return isWithinRange(value, 0, 59);
     }
 
@@ -507,10 +509,24 @@
      * @return true if the duration hours is valid
      */
     private static boolean isValidDurationHours(int value) {
+        value = bcdToDecimal(value);
         return isWithinRange(value, 0, 99);
     }
 
     /**
+     * Convert BCD value to decimal value.
+     *
+     * @param value BCD value
+     * @return decimal value
+     */
+    private static int bcdToDecimal(int value) {
+        int tens = (value & 0xF0) >> 4;
+        int ones = (value & 0x0F);
+
+        return tens * 10 + ones;
+    }
+
+    /**
      * Check if the given value is a valid recording sequence. A valid value is adheres to range
      * description defined in CEC 1.4 Specification : Operand Descriptions (Section 17)
      *
@@ -523,8 +539,7 @@
         if ((value & 0x80) != 0x00) {
             return false;
         }
-        // Validate than not more than one bit is set
-        return (Integer.bitCount(value) <= 1);
+        return true;
     }
 
     /**
diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java
index d32a5ed..819b9a1 100644
--- a/services/core/java/com/android/server/input/InputManagerInternal.java
+++ b/services/core/java/com/android/server/input/InputManagerInternal.java
@@ -21,6 +21,7 @@
 import android.annotation.UserIdInt;
 import android.graphics.PointF;
 import android.hardware.display.DisplayViewport;
+import android.hardware.input.KeyboardSystemShortcut;
 import android.os.IBinder;
 import android.view.InputChannel;
 import android.view.inputmethod.InputMethodSubtype;
@@ -227,4 +228,20 @@
      *     since boot.
      */
     public abstract int getLastUsedInputDeviceId();
+
+    /**
+     * Notify Keyboard system shortcut was triggered by the user and handled by the framework.
+     *
+     * NOTE: This is just to notify that a system shortcut was triggered. No further action is
+     * required to execute the said shortcut. This callback is meant for purposes of providing user
+     * hints or logging, etc.
+     *
+     * @param deviceId the device ID of the keyboard using which the shortcut was triggered
+     * @param keycodes the keys pressed for triggering the shortcut
+     * @param modifierState the modifier state of the key event that triggered the shortcut
+     * @param shortcut the shortcut that was triggered
+     *
+     */
+    public abstract void notifyKeyboardShortcutTriggered(int deviceId, int[] keycodes,
+            int modifierState, @KeyboardSystemShortcut.SystemShortcut int shortcut);
 }
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index a06ad14..e555761 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -47,6 +47,7 @@
 import android.hardware.input.IInputManager;
 import android.hardware.input.IInputSensorEventListener;
 import android.hardware.input.IKeyboardBacklightListener;
+import android.hardware.input.IKeyboardSystemShortcutListener;
 import android.hardware.input.IStickyModifierStateListener;
 import android.hardware.input.ITabletModeChangedListener;
 import android.hardware.input.InputDeviceIdentifier;
@@ -56,6 +57,7 @@
 import android.hardware.input.KeyGlyphMap;
 import android.hardware.input.KeyboardLayout;
 import android.hardware.input.KeyboardLayoutSelectionResult;
+import android.hardware.input.KeyboardSystemShortcut;
 import android.hardware.input.TouchCalibration;
 import android.hardware.lights.Light;
 import android.hardware.lights.LightState;
@@ -157,6 +159,7 @@
     private static final int MSG_DELIVER_INPUT_DEVICES_CHANGED = 1;
     private static final int MSG_RELOAD_DEVICE_ALIASES = 2;
     private static final int MSG_DELIVER_TABLET_MODE_CHANGED = 3;
+    private static final int MSG_KEYBOARD_SYSTEM_SHORTCUT_TRIGGERED = 4;
 
     private static final int DEFAULT_VIBRATION_MAGNITUDE = 192;
     private static final AdditionalDisplayInputProperties
@@ -306,6 +309,9 @@
     // Manages Sticky modifier state
     private final StickyModifierStateController mStickyModifierStateController;
 
+    // Manages keyboard system shortcut callbacks
+    private final KeyboardShortcutCallbackHandler mKeyboardShortcutCallbackHandler;
+
     // Manages Keyboard microphone mute led
     private final KeyboardLedController mKeyboardLedController;
 
@@ -461,6 +467,7 @@
                         injector.getLooper(), injector.getUEventManager())
                 : new KeyboardBacklightControllerInterface() {};
         mStickyModifierStateController = new StickyModifierStateController();
+        mKeyboardShortcutCallbackHandler = new KeyboardShortcutCallbackHandler();
         mKeyboardLedController = new KeyboardLedController(mContext, injector.getLooper(),
                 mNative);
         mKeyRemapper = new KeyRemapper(mContext, mNative, mDataStore, injector.getLooper());
@@ -2703,6 +2710,36 @@
                 lockedModifierState);
     }
 
+    @Override
+    @EnforcePermission(Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS)
+    public void registerKeyboardSystemShortcutListener(
+            @NonNull IKeyboardSystemShortcutListener listener) {
+        super.registerKeyboardSystemShortcutListener_enforcePermission();
+        Objects.requireNonNull(listener);
+        mKeyboardShortcutCallbackHandler.registerKeyboardSystemShortcutListener(listener,
+                Binder.getCallingPid());
+    }
+
+    @Override
+    @EnforcePermission(Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS)
+    public void unregisterKeyboardSystemShortcutListener(
+            @NonNull IKeyboardSystemShortcutListener listener) {
+        super.unregisterKeyboardSystemShortcutListener_enforcePermission();
+        Objects.requireNonNull(listener);
+        mKeyboardShortcutCallbackHandler.unregisterKeyboardSystemShortcutListener(listener,
+                Binder.getCallingPid());
+    }
+
+    private void handleKeyboardSystemShortcutTriggered(int deviceId,
+            KeyboardSystemShortcut shortcut) {
+        InputDevice device = getInputDevice(deviceId);
+        if (device == null || device.isVirtual() || !device.isFullKeyboard()) {
+            return;
+        }
+        KeyboardMetricsCollector.logKeyboardSystemsEventReportedAtom(device, shortcut);
+        mKeyboardShortcutCallbackHandler.onKeyboardSystemShortcutTriggered(deviceId, shortcut);
+    }
+
     /**
      * Callback interface implemented by the Window Manager.
      */
@@ -2871,6 +2908,10 @@
                     boolean inTabletMode = (boolean) args.arg1;
                     deliverTabletModeChanged(whenNanos, inTabletMode);
                     break;
+                case MSG_KEYBOARD_SYSTEM_SHORTCUT_TRIGGERED:
+                    int deviceId = msg.arg1;
+                    KeyboardSystemShortcut shortcut = (KeyboardSystemShortcut) msg.obj;
+                    handleKeyboardSystemShortcutTriggered(deviceId, shortcut);
             }
         }
     }
@@ -3196,6 +3237,13 @@
         public int getLastUsedInputDeviceId() {
             return mNative.getLastUsedInputDeviceId();
         }
+
+        @Override
+        public void notifyKeyboardShortcutTriggered(int deviceId, int[] keycodes, int modifierState,
+                @KeyboardSystemShortcut.SystemShortcut int shortcut) {
+            mHandler.obtainMessage(MSG_KEYBOARD_SYSTEM_SHORTCUT_TRIGGERED, deviceId, 0,
+                    new KeyboardSystemShortcut(keycodes, modifierState, shortcut)).sendToTarget();
+        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
index f21fd41..3d2f951 100644
--- a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
+++ b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
@@ -24,31 +24,25 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.role.RoleManager;
-import android.content.Intent;
 import android.hardware.input.KeyboardLayout;
 import android.hardware.input.KeyboardLayoutSelectionResult.LayoutSelectionCriteria;
+import android.hardware.input.KeyboardSystemShortcut;
 import android.icu.util.ULocale;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Slog;
-import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
 import android.view.InputDevice;
-import android.view.KeyEvent;
 import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.KeyboardConfiguredProto.KeyboardLayoutConfig;
 import com.android.internal.os.KeyboardConfiguredProto.RepeatedKeyboardLayoutConfig;
 import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.policy.ModifierShortcutManager;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
-import java.util.Set;
 
 /**
  * Collect Keyboard metrics
@@ -66,336 +60,20 @@
     @VisibleForTesting
     public static final String DEFAULT_LANGUAGE_TAG = "None";
 
-    public enum KeyboardLogEvent {
-        UNSPECIFIED(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__UNSPECIFIED,
-                "INVALID_KEYBOARD_EVENT"),
-        HOME(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__HOME,
-                "HOME"),
-        RECENT_APPS(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__RECENT_APPS,
-                "RECENT_APPS"),
-        BACK(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BACK,
-                "BACK"),
-        APP_SWITCH(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__APP_SWITCH,
-                "APP_SWITCH"),
-        LAUNCH_ASSISTANT(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_ASSISTANT,
-                "LAUNCH_ASSISTANT"),
-        LAUNCH_VOICE_ASSISTANT(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_VOICE_ASSISTANT,
-                "LAUNCH_VOICE_ASSISTANT"),
-        LAUNCH_SYSTEM_SETTINGS(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_SYSTEM_SETTINGS,
-                "LAUNCH_SYSTEM_SETTINGS"),
-        TOGGLE_NOTIFICATION_PANEL(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_NOTIFICATION_PANEL,
-                "TOGGLE_NOTIFICATION_PANEL"),
-        TOGGLE_TASKBAR(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_TASKBAR,
-                "TOGGLE_TASKBAR"),
-        TAKE_SCREENSHOT(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TAKE_SCREENSHOT,
-                "TAKE_SCREENSHOT"),
-        OPEN_SHORTCUT_HELPER(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__OPEN_SHORTCUT_HELPER,
-                "OPEN_SHORTCUT_HELPER"),
-        BRIGHTNESS_UP(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BRIGHTNESS_UP,
-                "BRIGHTNESS_UP"),
-        BRIGHTNESS_DOWN(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BRIGHTNESS_DOWN,
-                "BRIGHTNESS_DOWN"),
-        KEYBOARD_BACKLIGHT_UP(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__KEYBOARD_BACKLIGHT_UP,
-                "KEYBOARD_BACKLIGHT_UP"),
-        KEYBOARD_BACKLIGHT_DOWN(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__KEYBOARD_BACKLIGHT_DOWN,
-                "KEYBOARD_BACKLIGHT_DOWN"),
-        KEYBOARD_BACKLIGHT_TOGGLE(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__KEYBOARD_BACKLIGHT_TOGGLE,
-                "KEYBOARD_BACKLIGHT_TOGGLE"),
-        VOLUME_UP(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__VOLUME_UP,
-                "VOLUME_UP"),
-        VOLUME_DOWN(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__VOLUME_DOWN,
-                "VOLUME_DOWN"),
-        VOLUME_MUTE(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__VOLUME_MUTE,
-                "VOLUME_MUTE"),
-        ALL_APPS(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__ALL_APPS,
-                "ALL_APPS"),
-        LAUNCH_SEARCH(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_SEARCH,
-                "LAUNCH_SEARCH"),
-        LANGUAGE_SWITCH(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LANGUAGE_SWITCH,
-                "LANGUAGE_SWITCH"),
-        ACCESSIBILITY_ALL_APPS(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__ACCESSIBILITY_ALL_APPS,
-                "ACCESSIBILITY_ALL_APPS"),
-        TOGGLE_CAPS_LOCK(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_CAPS_LOCK,
-                "TOGGLE_CAPS_LOCK"),
-        SYSTEM_MUTE(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SYSTEM_MUTE,
-                "SYSTEM_MUTE"),
-        SPLIT_SCREEN_NAVIGATION(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SPLIT_SCREEN_NAVIGATION,
-                "SPLIT_SCREEN_NAVIGATION"),
-
-        CHANGE_SPLITSCREEN_FOCUS(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__CHANGE_SPLITSCREEN_FOCUS,
-                "CHANGE_SPLITSCREEN_FOCUS"),
-        TRIGGER_BUG_REPORT(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TRIGGER_BUG_REPORT,
-                "TRIGGER_BUG_REPORT"),
-        LOCK_SCREEN(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LOCK_SCREEN,
-                "LOCK_SCREEN"),
-        OPEN_NOTES(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__OPEN_NOTES,
-                "OPEN_NOTES"),
-        TOGGLE_POWER(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_POWER,
-                "TOGGLE_POWER"),
-        SYSTEM_NAVIGATION(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SYSTEM_NAVIGATION,
-                "SYSTEM_NAVIGATION"),
-        SLEEP(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SLEEP,
-                "SLEEP"),
-        WAKEUP(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__WAKEUP,
-                "WAKEUP"),
-        MEDIA_KEY(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__MEDIA_KEY,
-                "MEDIA_KEY"),
-        LAUNCH_DEFAULT_BROWSER(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_BROWSER,
-                "LAUNCH_DEFAULT_BROWSER"),
-        LAUNCH_DEFAULT_EMAIL(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_EMAIL,
-                "LAUNCH_DEFAULT_EMAIL"),
-        LAUNCH_DEFAULT_CONTACTS(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CONTACTS,
-                "LAUNCH_DEFAULT_CONTACTS"),
-        LAUNCH_DEFAULT_CALENDAR(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CALENDAR,
-                "LAUNCH_DEFAULT_CALENDAR"),
-        LAUNCH_DEFAULT_CALCULATOR(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CALCULATOR,
-                "LAUNCH_DEFAULT_CALCULATOR"),
-        LAUNCH_DEFAULT_MUSIC(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MUSIC,
-                "LAUNCH_DEFAULT_MUSIC"),
-        LAUNCH_DEFAULT_MAPS(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MAPS,
-                "LAUNCH_DEFAULT_MAPS"),
-        LAUNCH_DEFAULT_MESSAGING(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MESSAGING,
-                "LAUNCH_DEFAULT_MESSAGING"),
-        LAUNCH_DEFAULT_GALLERY(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_GALLERY,
-                "LAUNCH_DEFAULT_GALLERY"),
-        LAUNCH_DEFAULT_FILES(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_FILES,
-                "LAUNCH_DEFAULT_FILES"),
-        LAUNCH_DEFAULT_WEATHER(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_WEATHER,
-                "LAUNCH_DEFAULT_WEATHER"),
-        LAUNCH_DEFAULT_FITNESS(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_FITNESS,
-                "LAUNCH_DEFAULT_FITNESS"),
-        LAUNCH_APPLICATION_BY_PACKAGE_NAME(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_APPLICATION_BY_PACKAGE_NAME,
-                "LAUNCH_APPLICATION_BY_PACKAGE_NAME"),
-        DESKTOP_MODE(
-                FrameworkStatsLog
-                        .KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__DESKTOP_MODE,
-                "DESKTOP_MODE"),
-        MULTI_WINDOW_NAVIGATION(FrameworkStatsLog
-                .KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__MULTI_WINDOW_NAVIGATION,
-                "MULTIWINDOW_NAVIGATION");
-
-
-        private final int mValue;
-        private final String mName;
-
-        private static final SparseArray<KeyboardLogEvent> VALUE_TO_ENUM_MAP = new SparseArray<>();
-
-        static {
-            for (KeyboardLogEvent type : KeyboardLogEvent.values()) {
-                VALUE_TO_ENUM_MAP.put(type.mValue, type);
-            }
-        }
-
-        KeyboardLogEvent(int enumValue, String enumName) {
-            mValue = enumValue;
-            mName = enumName;
-        }
-
-        public int getIntValue() {
-            return mValue;
-        }
-
-        /**
-         * Convert int value to corresponding KeyboardLogEvent enum. If can't find any matching
-         * value will return {@code null}
-         */
-        @Nullable
-        public static KeyboardLogEvent from(int value) {
-            return VALUE_TO_ENUM_MAP.get(value);
-        }
-
-        /**
-         * Find KeyboardLogEvent corresponding to volume up/down/mute key events.
-         */
-        @Nullable
-        public static KeyboardLogEvent getVolumeEvent(int keycode) {
-            switch (keycode) {
-                case KeyEvent.KEYCODE_VOLUME_DOWN:
-                    return VOLUME_DOWN;
-                case KeyEvent.KEYCODE_VOLUME_UP:
-                    return VOLUME_UP;
-                case KeyEvent.KEYCODE_VOLUME_MUTE:
-                    return VOLUME_MUTE;
-                default:
-                    return null;
-            }
-        }
-
-        /**
-         * Find KeyboardLogEvent corresponding to brightness up/down key events.
-         */
-        @Nullable
-        public static KeyboardLogEvent getBrightnessEvent(int keycode) {
-            switch (keycode) {
-                case KeyEvent.KEYCODE_BRIGHTNESS_DOWN:
-                    return BRIGHTNESS_DOWN;
-                case KeyEvent.KEYCODE_BRIGHTNESS_UP:
-                    return BRIGHTNESS_UP;
-                default:
-                    return null;
-            }
-        }
-
-        /**
-         * Find KeyboardLogEvent corresponding to intent filter category. Returns
-         * {@code null if no matching event found}
-         */
-        @Nullable
-        public static KeyboardLogEvent getLogEventFromIntent(Intent intent) {
-            Intent selectorIntent = intent.getSelector();
-            if (selectorIntent != null) {
-                Set<String> selectorCategories = selectorIntent.getCategories();
-                if (selectorCategories != null && !selectorCategories.isEmpty()) {
-                    for (String intentCategory : selectorCategories) {
-                        KeyboardLogEvent logEvent = getEventFromSelectorCategory(intentCategory);
-                        if (logEvent == null) {
-                            continue;
-                        }
-                        return logEvent;
-                    }
-                }
-            }
-
-            // The shortcut may be targeting a system role rather than using an intent selector,
-            // so check for that.
-            String role = intent.getStringExtra(ModifierShortcutManager.EXTRA_ROLE);
-            if (!TextUtils.isEmpty(role)) {
-                return getLogEventFromRole(role);
-            }
-
-            Set<String> intentCategories = intent.getCategories();
-            if (intentCategories == null || intentCategories.isEmpty()
-                    || !intentCategories.contains(Intent.CATEGORY_LAUNCHER)) {
-                return null;
-            }
-            if (intent.getComponent() == null) {
-                return null;
-            }
-
-            // TODO(b/280423320): Add new field package name associated in the
-            //  KeyboardShortcutEvent atom and log it accordingly.
-            return LAUNCH_APPLICATION_BY_PACKAGE_NAME;
-        }
-
-        @Nullable
-        private static KeyboardLogEvent getEventFromSelectorCategory(String category) {
-            switch (category) {
-                case Intent.CATEGORY_APP_BROWSER:
-                    return LAUNCH_DEFAULT_BROWSER;
-                case Intent.CATEGORY_APP_EMAIL:
-                    return LAUNCH_DEFAULT_EMAIL;
-                case Intent.CATEGORY_APP_CONTACTS:
-                    return LAUNCH_DEFAULT_CONTACTS;
-                case Intent.CATEGORY_APP_CALENDAR:
-                    return LAUNCH_DEFAULT_CALENDAR;
-                case Intent.CATEGORY_APP_CALCULATOR:
-                    return LAUNCH_DEFAULT_CALCULATOR;
-                case Intent.CATEGORY_APP_MUSIC:
-                    return LAUNCH_DEFAULT_MUSIC;
-                case Intent.CATEGORY_APP_MAPS:
-                    return LAUNCH_DEFAULT_MAPS;
-                case Intent.CATEGORY_APP_MESSAGING:
-                    return LAUNCH_DEFAULT_MESSAGING;
-                case Intent.CATEGORY_APP_GALLERY:
-                    return LAUNCH_DEFAULT_GALLERY;
-                case Intent.CATEGORY_APP_FILES:
-                    return LAUNCH_DEFAULT_FILES;
-                case Intent.CATEGORY_APP_WEATHER:
-                    return LAUNCH_DEFAULT_WEATHER;
-                case Intent.CATEGORY_APP_FITNESS:
-                    return LAUNCH_DEFAULT_FITNESS;
-                default:
-                    return null;
-            }
-        }
-
-        /**
-         * Find KeyboardLogEvent corresponding to the provide system role name.
-         * Returns {@code null} if no matching event found.
-         */
-        @Nullable
-        private static KeyboardLogEvent getLogEventFromRole(String role) {
-            if (RoleManager.ROLE_BROWSER.equals(role)) {
-                return LAUNCH_DEFAULT_BROWSER;
-            } else if (RoleManager.ROLE_SMS.equals(role)) {
-                return LAUNCH_DEFAULT_MESSAGING;
-            } else {
-                Log.w(TAG, "Keyboard shortcut to launch "
-                        + role + " not supported for logging");
-                return null;
-            }
-        }
-    }
-
     /**
      * Log keyboard system shortcuts for the proto
      * {@link com.android.os.input.KeyboardSystemsEventReported}
      * defined in "stats/atoms/input/input_extension_atoms.proto"
      */
-    public static void logKeyboardSystemsEventReportedAtom(@Nullable InputDevice inputDevice,
-            @Nullable KeyboardLogEvent keyboardSystemEvent, int modifierState, int... keyCodes) {
-        // Logging Keyboard system event only for an external HW keyboard. We should not log events
-        // for virtual keyboards or internal Key events.
-        if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
-            return;
-        }
-        if (keyboardSystemEvent == null) {
-            Slog.w(TAG, "Invalid keyboard event logging, keycode = " + Arrays.toString(keyCodes)
-                    + ", modifier state = " + modifierState);
-            return;
-        }
+    public static void logKeyboardSystemsEventReportedAtom(@NonNull InputDevice inputDevice,
+            @NonNull KeyboardSystemShortcut keyboardSystemShortcut) {
         FrameworkStatsLog.write(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED,
                 inputDevice.getVendorId(), inputDevice.getProductId(),
-                keyboardSystemEvent.getIntValue(), keyCodes, modifierState,
-                inputDevice.getDeviceBus());
+                keyboardSystemShortcut.getSystemShortcut(), keyboardSystemShortcut.getKeycodes(),
+                keyboardSystemShortcut.getModifierState(), inputDevice.getDeviceBus());
 
         if (DEBUG) {
-            Slog.d(TAG, "Logging Keyboard system event: " + keyboardSystemEvent.mName);
+            Slog.d(TAG, "Logging Keyboard system event: " + keyboardSystemShortcut);
         }
     }
 
diff --git a/services/core/java/com/android/server/input/KeyboardShortcutCallbackHandler.java b/services/core/java/com/android/server/input/KeyboardShortcutCallbackHandler.java
new file mode 100644
index 0000000..092058e
--- /dev/null
+++ b/services/core/java/com/android/server/input/KeyboardShortcutCallbackHandler.java
@@ -0,0 +1,137 @@
+/*
+ * 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;
+
+import android.annotation.BinderThread;
+import android.hardware.input.IKeyboardSystemShortcutListener;
+import android.hardware.input.KeyboardSystemShortcut;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * A thread-safe component of {@link InputManagerService} responsible for managing callbacks when a
+ * keyboard shortcut is triggered.
+ */
+final class KeyboardShortcutCallbackHandler {
+
+    private static final String TAG = "KeyboardShortcut";
+
+    // To enable these logs, run:
+    // 'adb shell setprop log.tag.KeyboardShortcutCallbackHandler DEBUG' (requires restart)
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    // List of currently registered keyboard system shortcut listeners keyed by process pid
+    @GuardedBy("mKeyboardSystemShortcutListenerRecords")
+    private final SparseArray<KeyboardSystemShortcutListenerRecord>
+            mKeyboardSystemShortcutListenerRecords = new SparseArray<>();
+
+    public void onKeyboardSystemShortcutTriggered(int deviceId,
+            KeyboardSystemShortcut systemShortcut) {
+        if (DEBUG) {
+            Slog.d(TAG, "Keyboard system shortcut triggered, deviceId = " + deviceId
+                    + ", systemShortcut = " + systemShortcut);
+        }
+
+        synchronized (mKeyboardSystemShortcutListenerRecords) {
+            for (int i = 0; i < mKeyboardSystemShortcutListenerRecords.size(); i++) {
+                mKeyboardSystemShortcutListenerRecords.valueAt(i).onKeyboardSystemShortcutTriggered(
+                        deviceId, systemShortcut);
+            }
+        }
+    }
+
+    /** Register the keyboard system shortcut listener for a process. */
+    @BinderThread
+    public void registerKeyboardSystemShortcutListener(IKeyboardSystemShortcutListener listener,
+            int pid) {
+        synchronized (mKeyboardSystemShortcutListenerRecords) {
+            if (mKeyboardSystemShortcutListenerRecords.get(pid) != null) {
+                throw new IllegalStateException("The calling process has already registered "
+                        + "a KeyboardSystemShortcutListener.");
+            }
+            KeyboardSystemShortcutListenerRecord record = new KeyboardSystemShortcutListenerRecord(
+                    pid, listener);
+            try {
+                listener.asBinder().linkToDeath(record, 0);
+            } catch (RemoteException ex) {
+                throw new RuntimeException(ex);
+            }
+            mKeyboardSystemShortcutListenerRecords.put(pid, record);
+        }
+    }
+
+    /** Unregister the keyboard system shortcut listener for a process. */
+    @BinderThread
+    public void unregisterKeyboardSystemShortcutListener(IKeyboardSystemShortcutListener listener,
+            int pid) {
+        synchronized (mKeyboardSystemShortcutListenerRecords) {
+            KeyboardSystemShortcutListenerRecord record =
+                    mKeyboardSystemShortcutListenerRecords.get(pid);
+            if (record == null) {
+                throw new IllegalStateException("The calling process has no registered "
+                        + "KeyboardSystemShortcutListener.");
+            }
+            if (record.mListener.asBinder() != listener.asBinder()) {
+                throw new IllegalStateException("The calling process has a different registered "
+                        + "KeyboardSystemShortcutListener.");
+            }
+            record.mListener.asBinder().unlinkToDeath(record, 0);
+            mKeyboardSystemShortcutListenerRecords.remove(pid);
+        }
+    }
+
+    private void onKeyboardSystemShortcutListenerDied(int pid) {
+        synchronized (mKeyboardSystemShortcutListenerRecords) {
+            mKeyboardSystemShortcutListenerRecords.remove(pid);
+        }
+    }
+
+    // A record of a registered keyboard system shortcut listener from one process.
+    private class KeyboardSystemShortcutListenerRecord implements IBinder.DeathRecipient {
+        public final int mPid;
+        public final IKeyboardSystemShortcutListener mListener;
+
+        KeyboardSystemShortcutListenerRecord(int pid, IKeyboardSystemShortcutListener listener) {
+            mPid = pid;
+            mListener = listener;
+        }
+
+        @Override
+        public void binderDied() {
+            if (DEBUG) {
+                Slog.d(TAG, "Keyboard system shortcut listener for pid " + mPid + " died.");
+            }
+            onKeyboardSystemShortcutListenerDied(mPid);
+        }
+
+        public void onKeyboardSystemShortcutTriggered(int deviceId, KeyboardSystemShortcut data) {
+            try {
+                mListener.onKeyboardSystemShortcutTriggered(deviceId, data.getKeycodes(),
+                        data.getModifierState(), data.getSystemShortcut());
+            } catch (RemoteException ex) {
+                Slog.w(TAG, "Failed to notify process " + mPid
+                        + " that keyboard system shortcut was triggered, assuming it died.", ex);
+                binderDied();
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/input/OWNERS b/services/core/java/com/android/server/input/OWNERS
index 4c20c4d..e2834ec 100644
--- a/services/core/java/com/android/server/input/OWNERS
+++ b/services/core/java/com/android/server/input/OWNERS
@@ -1 +1,2 @@
+# Bug component: 136048
 include /INPUT_OWNERS
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
index a380bc1..0047ec2 100644
--- a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
@@ -112,7 +112,8 @@
     }
 
     @AnyThread
-    void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privilegedOperations,
+    void initializeInternal(@NonNull IBinder token,
+            @NonNull IInputMethodPrivilegedOperations privilegedOperations,
             @InputMethodNavButtonFlags int navigationBarFlags) {
         final IInputMethod.InitParams params = new IInputMethod.InitParams();
         params.token = token;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index 94b1473..ec1993a 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -18,6 +18,7 @@
 
 import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
 import static android.content.Context.DEVICE_ID_DEFAULT;
+import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.Display.INVALID_DISPLAY;
 
@@ -32,6 +33,8 @@
 import android.content.ServiceConnection;
 import android.content.pm.PackageManagerInternal;
 import android.inputmethodservice.InputMethodService;
+import android.inputmethodservice.InputMethodService.BackDispositionMode;
+import android.inputmethodservice.InputMethodService.ImeWindowVisibility;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.Process;
@@ -99,28 +102,16 @@
     /**
      * A set of status bits regarding the active IME.
      *
-     * <p>This value is a combination of following two bits:</p>
-     * <dl>
-     * <dt>{@link InputMethodService#IME_ACTIVE}</dt>
-     * <dd>
-     *   If this bit is ON, connected IME is ready to accept touch/key events.
-     * </dd>
-     * <dt>{@link InputMethodService#IME_VISIBLE}</dt>
-     * <dd>
-     *   If this bit is ON, some of IME view, e.g. software input, candidate view, is visible.
-     * </dd>
-     * <dt>{@link InputMethodService#IME_INVISIBLE}</dt>
-     * <dd> If this bit is ON, IME is ready with views from last EditorInfo but is
-     *    currently invisible.
-     * </dd>
-     * </dl>
-     * <em>Do not update this value outside of {@link #setImeWindowStatus(IBinder, int, int)} and
-     * {@link InputMethodBindingController#unbindCurrentMethod()}.</em>
+     * <em>Do not update this value outside of {@link #setImeWindowVis} and
+     * {@link InputMethodBindingController#unbindCurrentMethod}.</em>
      */
-    @GuardedBy("ImfLock.class") private int mImeWindowVis;
-
+    @ImeWindowVisibility
     @GuardedBy("ImfLock.class")
-    private int mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT;
+    private int mImeWindowVis;
+
+    @BackDispositionMode
+    @GuardedBy("ImfLock.class")
+    private int mBackDisposition = BACK_DISPOSITION_DEFAULT;
 
     @Nullable private CountDownLatch mLatchForTesting;
 
@@ -722,22 +713,24 @@
     }
 
     @GuardedBy("ImfLock.class")
-    void setImeWindowVis(int imeWindowVis) {
+    void setImeWindowVis(@ImeWindowVisibility int imeWindowVis) {
         mImeWindowVis = imeWindowVis;
     }
 
+    @ImeWindowVisibility
     @GuardedBy("ImfLock.class")
     int getImeWindowVis() {
         return mImeWindowVis;
     }
 
+    @BackDispositionMode
     @GuardedBy("ImfLock.class")
     int getBackDisposition() {
         return mBackDisposition;
     }
 
     @GuardedBy("ImfLock.class")
-    void setBackDisposition(int backDisposition) {
+    void setBackDisposition(@BackDispositionMode int backDisposition) {
         mBackDisposition = backDisposition;
     }
 }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index 4d06f50..e36d5bb 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -16,7 +16,7 @@
 
 package com.android.server.inputmethod;
 
-import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_ID;
+import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_INDEX;
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
@@ -140,23 +140,26 @@
      * to be switched.
      */
     public boolean switchToInputMethod(@NonNull String imeId, @UserIdInt int userId) {
-        return switchToInputMethod(imeId, NOT_A_SUBTYPE_ID, userId);
+        return switchToInputMethod(imeId, NOT_A_SUBTYPE_INDEX, userId);
     }
 
     /**
      * Force switch to the enabled input method by {@code imeId} for the current user. If the input
-     * method with {@code imeId} is not enabled or not installed, do nothing. If {@code subtypeId}
-     * is also supplied (not {@link InputMethodUtils#NOT_A_SUBTYPE_ID}) and valid, also switches to
-     * it, otherwise the system decides the most sensible default subtype to use.
+     * method with {@code imeId} is not enabled or not installed, do nothing. If
+     * {@code subtypeIndex} is also supplied (not {@link InputMethodUtils#NOT_A_SUBTYPE_INDEX}) and
+     * valid, also switches to it, otherwise the system decides the most sensible default subtype to
+     * use.
      *
-     * @param imeId the input method ID to be switched to
-     * @param subtypeId the input method subtype ID to be switched to
-     * @param userId the user ID to be queried
+     * @param imeId        the input method ID to be switched to
+     * @param subtypeIndex the subtype to be switched to, as an index in the input method's array of
+     *                     subtypes, or {@link InputMethodUtils#NOT_A_SUBTYPE_INDEX} if the system
+     *                     should decide the most sensible subtype
+     * @param userId       the user ID to be queried
      * @return {@code true} if the current input method was successfully switched to the input
      * method by {@code imeId}; {@code false} the input method with {@code imeId} is not available
      * to be switched.
      */
-    public abstract boolean switchToInputMethod(@NonNull String imeId, int subtypeId,
+    public abstract boolean switchToInputMethod(@NonNull String imeId, int subtypeIndex,
             @UserIdInt int userId);
 
     /**
@@ -376,7 +379,7 @@
                 }
 
                 @Override
-                public boolean switchToInputMethod(@NonNull String imeId, int subtypeId,
+                public boolean switchToInputMethod(@NonNull String imeId, int subtypeIndex,
                         @UserIdInt int userId) {
                     return false;
                 }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index e23bd31..3654283 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -49,10 +49,12 @@
 
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeTargetWindowState;
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeVisibilityResult;
-import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_SHOW_IME;
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME;
+import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_SHOW_IME;
 import static com.android.server.inputmethod.InputMethodBindingController.TIME_TO_RECONNECT;
+import static com.android.server.inputmethod.InputMethodSettings.INVALID_SUBTYPE_HASHCODE;
 import static com.android.server.inputmethod.InputMethodSubtypeSwitchingController.MODE_AUTO;
+import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_INDEX;
 import static com.android.server.inputmethod.InputMethodUtils.isSoftInputModeStateVisibleAllowed;
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -81,10 +83,11 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.pm.UserInfo;
-import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.hardware.input.InputManager;
 import android.inputmethodservice.InputMethodService;
+import android.inputmethodservice.InputMethodService.BackDispositionMode;
+import android.inputmethodservice.InputMethodService.ImeWindowVisibility;
 import android.media.AudioManagerInternal;
 import android.net.Uri;
 import android.os.Binder;
@@ -273,11 +276,6 @@
 
     private static final int MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE = 7000;
 
-    private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID;
-
-    private static final int INVALID_SUBTYPE_HASHCODE =
-            InputMethodSettings.INVALID_SUBTYPE_HASHCODE;
-
     private static final String TAG_TRY_SUPPRESSING_IME_SWITCHER = "TrySuppressingImeSwitcher";
     private static final String HANDLER_THREAD_NAME = "android.imms";
     private static final String PACKAGE_MONITOR_THREAD_NAME = "android.imms2";
@@ -303,28 +301,6 @@
     private final String[] mNonPreemptibleInputMethods;
 
     /**
-     * Whether the new Input Method Switcher menu is enabled.
-     *
-     * @see #shouldEnableNewInputMethodSwitcherMenu
-     */
-    @SharedByAllUsersField
-    private final boolean mNewInputMethodSwitcherMenuEnabled;
-
-    /**
-     * Returns {@code true} if the new Input Method Switcher menu is enabled. This will be
-     * {@code false} for watches and small screen devices.
-     *
-     * @param context the context to check the device configuration for.
-     */
-    private static boolean shouldEnableNewInputMethodSwitcherMenu(@NonNull Context context) {
-        final boolean isWatch = context.getPackageManager()
-                .hasSystemFeature(PackageManager.FEATURE_WATCH);
-        final boolean isSmallScreen = (context.getResources().getConfiguration().screenLayout
-                & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_SMALL;
-        return Flags.imeSwitcherRevamp() && !isWatch && !isSmallScreen;
-    }
-
-    /**
      * See {@link #shouldEnableConcurrentMultiUserMode(Context)} about when set to be {@code true}.
      */
     @SharedByAllUsersField
@@ -356,7 +332,7 @@
     @UserIdInt
     @BinderThread
     private int resolveImeUserIdLocked(@UserIdInt int callingProcessUserId) {
-        return mConcurrentMultiUserModeEnabled ? callingProcessUserId : mCurrentUserId;
+        return mConcurrentMultiUserModeEnabled ? callingProcessUserId : mCurrentImeUserId;
     }
 
     /**
@@ -369,7 +345,7 @@
     @UserIdInt
     private int resolveImeUserIdFromDisplayIdLocked(int displayId) {
         return mConcurrentMultiUserModeEnabled
-                ? mUserManagerInternal.getUserAssignedToDisplay(displayId) : mCurrentUserId;
+                ? mUserManagerInternal.getUserAssignedToDisplay(displayId) : mCurrentImeUserId;
     }
 
     /**
@@ -385,7 +361,7 @@
             final int displayId = mWindowManagerInternal.getDisplayIdForWindow(windowToken);
             return mUserManagerInternal.getUserAssignedToDisplay(displayId);
         }
-        return mCurrentUserId;
+        return mCurrentImeUserId;
     }
 
     final Context mContext;
@@ -396,10 +372,23 @@
     @NonNull
     private final Handler mIoHandler;
 
+    /**
+     * The user ID whose IME should be used if {@link #mConcurrentMultiUserModeEnabled} is
+     * {@code false}, otherwise remains to be the initial value, which is obtained by
+     * {@link ActivityManagerInternal#getCurrentUserId()} while the device is booting up.
+     *
+     * <p>Never get confused with {@link ActivityManagerInternal#getCurrentUserId()}, which is
+     * in general useless when designing and implementing interactions between apps and IMEs.</p>
+     *
+     * <p>You can also not assume that the IME client process belongs to {@link #mCurrentImeUserId}.
+     * A most important outlier is System UI process, which always runs under
+     * {@link UserHandle#USER_SYSTEM} in all the known configurations including Headless System User
+     * Mode (HSUM).</p>
+     */
     @MultiUserUnawareField
     @UserIdInt
     @GuardedBy("ImfLock.class")
-    private int mCurrentUserId;
+    private int mCurrentImeUserId;
 
     /** Holds all user related data */
     @SharedByAllUsersField
@@ -566,7 +555,7 @@
     @GuardedBy("ImfLock.class")
     @Nullable
     IInputMethodInvoker getCurMethodLocked() {
-        return getInputMethodBindingController(mCurrentUserId).getCurMethod();
+        return getInputMethodBindingController(mCurrentImeUserId).getCurMethod();
     }
 
     /**
@@ -612,8 +601,8 @@
     private void onSecureSettingsChangedLocked(@NonNull String key, @UserIdInt int userId) {
         switch (key) {
             case Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD: {
-                if (!mNewInputMethodSwitcherMenuEnabled) {
-                    if (userId == mCurrentUserId) {
+                if (!Flags.imeSwitcherRevamp()) {
+                    if (userId == mCurrentImeUserId) {
                         mMenuController.updateKeyboardFromSettingsLocked(userId);
                     }
                 }
@@ -675,12 +664,12 @@
                 // sender userId can be a real user ID or USER_ALL.
                 final int senderUserId = pendingResult.getSendingUserId();
                 synchronized (ImfLock.class) {
-                    if (senderUserId != UserHandle.USER_ALL && senderUserId != mCurrentUserId) {
+                    if (senderUserId != UserHandle.USER_ALL && senderUserId != mCurrentImeUserId) {
                         // A background user is trying to hide the dialog. Ignore.
                         return;
                     }
-                    final int userId = mCurrentUserId;
-                    if (mNewInputMethodSwitcherMenuEnabled) {
+                    final int userId = mCurrentImeUserId;
+                    if (Flags.imeSwitcherRevamp()) {
                         final var bindingController = getInputMethodBindingController(userId);
                         mMenuControllerNew.hide(bindingController.getCurTokenDisplayId(), userId);
                     } else {
@@ -718,7 +707,7 @@
                     userData.mRawInputMethodMap.set(rawMethodMap);
                     final var methodMap = rawMethodMap.toInputMethodMap(additionalSubtypeMap,
                             DirectBootAwareness.AUTO,
-                            mUserManagerInternal.isUserUnlockingOrUnlocked(userId));
+                            userData.mIsUnlockingOrUnlocked.get());
                     final var settings = InputMethodSettings.create(methodMap, userId);
                     InputMethodSettingsRepository.put(userId, settings);
                 }
@@ -797,63 +786,70 @@
             final int userId = getChangingUserId();
             final var userData = getUserData(userId);
 
-            // Instantiating InputMethodInfo requires disk I/O.
-            // Do them before acquiring the lock to minimize the chances of ANR (b/340221861).
             userData.mRawInputMethodMap.set(queryRawInputMethodServiceMap(mContext, userId));
 
+            final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
+
+            InputMethodInfo curIm = null;
+            String curInputMethodId = settings.getSelectedInputMethod();
+            final List<InputMethodInfo> methodList = settings.getMethodList();
+
+            final ArrayList<String> imesToClearAdditionalSubtypes = new ArrayList<>();
+            final ArrayList<String> imesToBeDisabled = new ArrayList<>();
+            final int numImes = methodList.size();
+            for (int i = 0; i < numImes; i++) {
+                InputMethodInfo imi = methodList.get(i);
+                final String imiId = imi.getId();
+                if (imiId.equals(curInputMethodId)) {
+                    curIm = imi;
+                }
+                if (mDataClearedPackages.contains(imi.getPackageName())) {
+                    imesToClearAdditionalSubtypes.add(imiId);
+                }
+                int change = isPackageDisappearing(imi.getPackageName());
+                if (change == PACKAGE_PERMANENT_CHANGE) {
+                    Slog.i(TAG, "Input method uninstalled, disabling: " + imi.getComponent());
+                    imesToBeDisabled.add(imi.getId());
+                } else if (change == PACKAGE_UPDATING) {
+                    Slog.i(TAG, "Input method reinstalling, clearing additional subtypes: "
+                            + imi.getComponent());
+                    imesToClearAdditionalSubtypes.add(imiId);
+                }
+            }
+
+            // Clear additional subtypes as a batch operation.
+            final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
+            final AdditionalSubtypeMap newAdditionalSubtypeMap =
+                    additionalSubtypeMap.cloneWithRemoveOrSelf(imesToClearAdditionalSubtypes);
+            final boolean additionalSubtypeChanged =
+                    (newAdditionalSubtypeMap != additionalSubtypeMap);
+            if (additionalSubtypeChanged) {
+                AdditionalSubtypeMapRepository.putAndSave(userId, newAdditionalSubtypeMap,
+                        settings.getMethodMap());
+            }
+
+            final var newMethodMap = userData.mRawInputMethodMap.get().toInputMethodMap(
+                    newAdditionalSubtypeMap,
+                    DirectBootAwareness.AUTO,
+                    userData.mIsUnlockingOrUnlocked.get());
+
+            final boolean noUpdate = InputMethodMap.areSame(settings.getMethodMap(), newMethodMap);
+            if (noUpdate && imesToBeDisabled.isEmpty()) {
+                return;
+            }
+
+            // Here we start remaining tasks that need to be done with the lock (b/340221861).
             synchronized (ImfLock.class) {
-                final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
-
-                InputMethodInfo curIm = null;
-                String curInputMethodId = settings.getSelectedInputMethod();
-                final List<InputMethodInfo> methodList = settings.getMethodList();
-
-                final ArrayList<String> imesToClearAdditionalSubtypes = new ArrayList<>();
-                final int numImes = methodList.size();
-                for (int i = 0; i < numImes; i++) {
-                    InputMethodInfo imi = methodList.get(i);
-                    final String imiId = imi.getId();
-                    if (imiId.equals(curInputMethodId)) {
-                        curIm = imi;
-                    }
-                    if (mDataClearedPackages.contains(imi.getPackageName())) {
-                        imesToClearAdditionalSubtypes.add(imiId);
-                    }
-                    int change = isPackageDisappearing(imi.getPackageName());
-                    if (change == PACKAGE_PERMANENT_CHANGE) {
-                        Slog.i(TAG, "Input method uninstalled, disabling: " + imi.getComponent());
-                        setInputMethodEnabledLocked(imi.getId(), false, userId);
-                    } else if (change == PACKAGE_UPDATING) {
-                        Slog.i(TAG, "Input method reinstalling, clearing additional subtypes: "
-                                + imi.getComponent());
-                        imesToClearAdditionalSubtypes.add(imiId);
-                    }
+                final int numImesToBeDisabled = imesToBeDisabled.size();
+                for (int i = 0; i < numImesToBeDisabled; ++i) {
+                    setInputMethodEnabledLocked(imesToBeDisabled.get(i), false /* enabled */,
+                            userId);
                 }
-
-                // Clear additional subtypes as a batch operation.
-                final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
-                final AdditionalSubtypeMap newAdditionalSubtypeMap =
-                        additionalSubtypeMap.cloneWithRemoveOrSelf(imesToClearAdditionalSubtypes);
-                final boolean additionalSubtypeChanged =
-                        (newAdditionalSubtypeMap != additionalSubtypeMap);
-                if (additionalSubtypeChanged) {
-                    AdditionalSubtypeMapRepository.putAndSave(userId, newAdditionalSubtypeMap,
-                            settings.getMethodMap());
-                }
-
-                final var newMethodMap = userData.mRawInputMethodMap.get().toInputMethodMap(
-                        newAdditionalSubtypeMap,
-                        DirectBootAwareness.AUTO,
-                        mUserManagerInternal.isUserUnlockingOrUnlocked(userId));
-
-                if (InputMethodMap.areSame(settings.getMethodMap(), newMethodMap)) {
-                    // No update in the actual IME map.
+                if (noUpdate) {
                     return;
                 }
-
-                final InputMethodSettings newSettings =
-                        InputMethodSettings.create(newMethodMap, userId);
-                InputMethodSettingsRepository.put(userId, newSettings);
+                InputMethodSettingsRepository.put(userId,
+                        InputMethodSettings.create(newMethodMap, userId));
                 postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */, userId);
 
                 boolean changed = false;
@@ -1063,13 +1059,16 @@
         public void onUserUnlocking(@NonNull TargetUser user) {
             // Called on ActivityManager thread. Do not block the calling thread.
             final int userId = user.getUserIdentifier();
+            final var userData = mService.getUserData(userId);
+            final boolean userUnlocked = true;
+            userData.mIsUnlockingOrUnlocked.set(userUnlocked);
             SecureSettingsWrapper.onUserUnlocking(userId);
+            final var methodMap = userData.mRawInputMethodMap.get().toInputMethodMap(
+                    AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO,
+                    userUnlocked);
+            final var newSettings = InputMethodSettings.create(methodMap, userId);
+            InputMethodSettingsRepository.put(userId, newSettings);
             mService.mIoHandler.post(() -> {
-                final var userData = mService.getUserData(userId);
-                final var methodMap = userData.mRawInputMethodMap.get().toInputMethodMap(
-                        AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO, true);
-                final var newSettings = InputMethodSettings.create(methodMap, userId);
-                InputMethodSettingsRepository.put(userId, newSettings);
                 synchronized (ImfLock.class) {
                     if (!mService.mSystemReady) {
                         return;
@@ -1113,9 +1112,12 @@
                     final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
                     final var rawMethodMap = queryRawInputMethodServiceMap(context, userId);
                     userData.mRawInputMethodMap.set(rawMethodMap);
+
+                    final boolean unlocked = userManagerInternal.isUserUnlockingOrUnlocked(userId);
+                    userData.mIsUnlockingOrUnlocked.set(unlocked);
                     final var methodMap = rawMethodMap.toInputMethodMap(additionalSubtypeMap,
-                            DirectBootAwareness.AUTO,
-                            userManagerInternal.isUserUnlockingOrUnlocked(userId));
+                            DirectBootAwareness.AUTO, unlocked);
+
                     final var settings = InputMethodSettings.create(methodMap, userId);
                     InputMethodSettingsRepository.put(userId, settings);
 
@@ -1135,16 +1137,19 @@
         public void onUserStopped(@NonNull TargetUser user) {
             final int userId = user.getUserIdentifier();
             // Called on ActivityManager thread.
+
+            // Following operations should be trivial and fast enough, so do not dispatch them to
+            // the IO thread.
             SecureSettingsWrapper.onUserStopped(userId);
-            mService.mIoHandler.post(() -> {
-                final var userData = mService.getUserData(userId);
-                final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
-                final var rawMethodMap = userData.mRawInputMethodMap.get();
-                final var methodMap = rawMethodMap.toInputMethodMap(additionalSubtypeMap,
-                        DirectBootAwareness.AUTO, false /* userUnlocked */);
-                InputMethodSettingsRepository.put(userId,
-                        InputMethodSettings.create(methodMap, userId));
-            });
+            final var userData = mService.getUserData(userId);
+            final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
+            final var rawMethodMap = userData.mRawInputMethodMap.get();
+            final boolean userUnlocked = false;  // Stopping a user also locks their storage.
+            userData.mIsUnlockingOrUnlocked.set(userUnlocked);
+            final var methodMap = rawMethodMap.toInputMethodMap(additionalSubtypeMap,
+                    DirectBootAwareness.AUTO, userUnlocked);
+            InputMethodSettingsRepository.put(userId,
+                    InputMethodSettings.create(methodMap, userId));
         }
     }
 
@@ -1194,11 +1199,10 @@
             mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
 
             mSlotIme = mContext.getString(com.android.internal.R.string.status_bar_ime);
-            mNewInputMethodSwitcherMenuEnabled = shouldEnableNewInputMethodSwitcherMenu(mContext);
 
             mShowOngoingImeSwitcherForPhones = false;
 
-            mCurrentUserId = mActivityManagerInternal.getCurrentUserId();
+            mCurrentImeUserId = mActivityManagerInternal.getCurrentUserId();
             final IntFunction<InputMethodBindingController>
                     bindingControllerFactory = userId -> new InputMethodBindingController(userId,
                     InputMethodManagerService.this);
@@ -1210,7 +1214,7 @@
                             : bindingControllerFactory, visibilityStateComputerFactory);
 
             mMenuController = new InputMethodMenuController(this);
-            mMenuControllerNew = mNewInputMethodSwitcherMenuEnabled
+            mMenuControllerNew = Flags.imeSwitcherRevamp()
                     ? new InputMethodMenuControllerNew() : null;
             mVisibilityApplier = new DefaultImeVisibilityApplier(this);
 
@@ -1278,7 +1282,7 @@
         if (DEBUG) {
             Slog.i(TAG, "Default found, using " + defIm.getId());
         }
-        setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false, userId);
+        setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_INDEX, false, userId);
     }
 
     @NonNull
@@ -1293,7 +1297,7 @@
     @GuardedBy("ImfLock.class")
     private void switchUserOnHandlerLocked(@UserIdInt int newUserId,
             IInputMethodClientInvoker clientToBeReset) {
-        final int prevUserId = mCurrentUserId;
+        final int prevUserId = mCurrentImeUserId;
         if (DEBUG) {
             Slog.d(TAG, "Switching user stage 1/3. newUserId=" + newUserId
                     + " prevUserId=" + prevUserId);
@@ -1318,7 +1322,7 @@
         // TODO(b/342027196): Double check if we need to always reset upon user switching.
         newUserData.mLastEnabledInputMethodsStr = "";
 
-        mCurrentUserId = newUserId;
+        mCurrentImeUserId = newUserId;
         final String defaultImiId = SecureSettingsWrapper.getString(
                 Settings.Secure.DEFAULT_INPUT_METHOD, null, newUserId);
 
@@ -1344,6 +1348,17 @@
         }
         updateFromSettingsLocked(true, newUserId);
 
+        // Special workaround for b/356879517.
+        // KeyboardLayoutManager still expects onInputMethodSubtypeChangedForKeyboardLayoutMapping
+        // to be called back upon IME user switching, while we are actively deprecating the concept
+        // of "current IME user" at b/350386877.
+        // TODO(b/356879517): Come up with a way to avoid this special handling.
+        if (newUserData.mSubtypeForKeyboardLayoutMapping != null) {
+            final var subtypeHandleAndSubtype = newUserData.mSubtypeForKeyboardLayoutMapping;
+            mInputManagerInternal.onInputMethodSubtypeChangedForKeyboardLayoutMapping(
+                    newUserId, subtypeHandleAndSubtype.first, subtypeHandleAndSubtype.second);
+        }
+
         if (initialUserSwitch) {
             InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
                     getPackageManagerForUser(mContext, newUserId),
@@ -1407,13 +1422,13 @@
             }
             if (!mSystemReady) {
                 mSystemReady = true;
-                final int currentUserId = mCurrentUserId;
+                final int currentImeUserId = mCurrentImeUserId;
                 mStatusBarManagerInternal =
                         LocalServices.getService(StatusBarManagerInternal.class);
-                hideStatusBarIconLocked(currentUserId);
-                final var bindingController = getInputMethodBindingController(currentUserId);
+                hideStatusBarIconLocked(currentImeUserId);
+                final var bindingController = getInputMethodBindingController(currentImeUserId);
                 updateSystemUiLocked(bindingController.getImeWindowVis(),
-                        bindingController.getBackDisposition(), currentUserId);
+                        bindingController.getBackDisposition(), currentImeUserId);
                 mShowOngoingImeSwitcherForPhones = mRes.getBoolean(
                         com.android.internal.R.bool.show_ongoing_ime_switcher);
                 if (mShowOngoingImeSwitcherForPhones) {
@@ -1483,27 +1498,6 @@
                 });
     }
 
-    /**
-     * Returns true iff the caller is identified to be the current input method with the token.
-     *
-     * @param token the window token given to the input method when it was started
-     * @param userData {@link UserData} of the calling IME process
-     * @return true if and only if non-null valid token is specified
-     */
-    @GuardedBy("ImfLock.class")
-    private boolean calledWithValidTokenLocked(@NonNull IBinder token, @NonNull UserData userData) {
-        if (token == null) {
-            throw new InvalidParameterException("token must not be null.");
-        }
-        final var bindingController = userData.mBindingController;
-        if (token != bindingController.getCurToken()) {
-            Slog.e(TAG, "Ignoring " + Debug.getCaller() + " due to an invalid token."
-                    + " uid:" + Binder.getCallingUid() + " token:" + token);
-            return false;
-        }
-        return true;
-    }
-
     @BinderThread
     @Nullable
     @Override
@@ -1614,7 +1608,7 @@
             }
 
             // Check if selected IME of current user supports handwriting.
-            if (userId == mCurrentUserId) {
+            if (userId == mCurrentImeUserId) {
                 final var bindingController = getInputMethodBindingController(userId);
                 return bindingController.supportsStylusHandwriting()
                         && (!connectionless
@@ -1645,7 +1639,7 @@
         final var userData = getUserData(userId);
         final var methodMap = userData.mRawInputMethodMap.get().toInputMethodMap(
                 AdditionalSubtypeMapRepository.get(userId), directBootAwareness,
-                mUserManagerInternal.isUserUnlockingOrUnlocked(userId));
+                userData.mIsUnlockingOrUnlocked.get());
         final var settings = InputMethodSettings.create(methodMap, userId);
         // Create a copy.
         final ArrayList<InputMethodInfo> methodList = new ArrayList<>(settings.getMethodList());
@@ -1820,7 +1814,7 @@
                     ImeTracker.PHASE_SERVER_WAIT_IME);
             userData.mCurStatsToken = null;
             // TODO: Make mMenuController multi-user aware
-            if (mNewInputMethodSwitcherMenuEnabled) {
+            if (Flags.imeSwitcherRevamp()) {
                 mMenuControllerNew.hide(bindingController.getCurTokenDisplayId(), userId);
             } else {
                 mMenuController.hideInputMethodMenuLocked(userId);
@@ -2028,7 +2022,7 @@
         if (deviceMethodId == null) {
             visibilityStateComputer.getImePolicy().setImeHiddenByDisplayPolicy(true);
         } else if (!Objects.equals(deviceMethodId, selectedMethodId)) {
-            setInputMethodLocked(deviceMethodId, NOT_A_SUBTYPE_ID,
+            setInputMethodLocked(deviceMethodId, NOT_A_SUBTYPE_INDEX,
                     bindingController.getDeviceIdToShowIme(), userId);
             selectedMethodId = deviceMethodId;
         }
@@ -2560,45 +2554,36 @@
     }
 
     @BinderThread
-    private void updateStatusIcon(@NonNull IBinder token, String packageName,
-            @DrawableRes int iconId, @NonNull UserData userData) {
+    @GuardedBy("ImfLock.class")
+    private void updateStatusIconLocked(String packageName, @DrawableRes int iconId,
+            @NonNull UserData userData) {
         final int userId = userData.mUserId;
-        synchronized (ImfLock.class) {
-            // To minimize app compat risk, ignore background users' request for single-user mode.
-            // TODO(b/357178609): generalize the logic and remove this special rule.
-            if (!mConcurrentMultiUserModeEnabled && userId != mCurrentUserId) {
-                return;
-            }
-            if (!calledWithValidTokenLocked(token, userData)) {
-                return;
-            }
-            final long ident = Binder.clearCallingIdentity();
+        // To minimize app compat risk, ignore background users' request for single-user mode.
+        // TODO(b/357178609): generalize the logic and remove this special rule.
+        if (!mConcurrentMultiUserModeEnabled && userId != mCurrentImeUserId) {
+            return;
+        }
+        if (iconId == 0) {
+            if (DEBUG) Slog.d(TAG, "hide the small icon for the input method");
+            hideStatusBarIconLocked(userId);
+        } else if (packageName != null) {
+            if (DEBUG) Slog.d(TAG, "show a small icon for the input method");
+            final PackageManager userAwarePackageManager =
+                    getPackageManagerForUser(mContext, userId);
+            ApplicationInfo applicationInfo = null;
             try {
-                if (iconId == 0) {
-                    if (DEBUG) Slog.d(TAG, "hide the small icon for the input method");
-                    hideStatusBarIconLocked(userId);
-                } else if (packageName != null) {
-                    if (DEBUG) Slog.d(TAG, "show a small icon for the input method");
-                    final PackageManager userAwarePackageManager =
-                            getPackageManagerForUser(mContext, userId);
-                    ApplicationInfo applicationInfo = null;
-                    try {
-                        applicationInfo = userAwarePackageManager.getApplicationInfo(packageName,
-                                PackageManager.ApplicationInfoFlags.of(0));
-                    } catch (PackageManager.NameNotFoundException e) {
-                    }
-                    final CharSequence contentDescription = applicationInfo != null
-                            ? userAwarePackageManager.getApplicationLabel(applicationInfo)
-                            : null;
-                    if (mStatusBarManagerInternal != null) {
-                        mStatusBarManagerInternal.setIcon(mSlotIme, packageName, iconId, 0,
-                                contentDescription != null
-                                        ? contentDescription.toString() : null);
-                        mStatusBarManagerInternal.setIconVisibility(mSlotIme, true);
-                    }
-                }
-            } finally {
-                Binder.restoreCallingIdentity(ident);
+                applicationInfo = userAwarePackageManager.getApplicationInfo(packageName,
+                        PackageManager.ApplicationInfoFlags.of(0));
+            } catch (PackageManager.NameNotFoundException e) {
+            }
+            final CharSequence contentDescription = applicationInfo != null
+                    ? userAwarePackageManager.getApplicationLabel(applicationInfo)
+                    : null;
+            if (mStatusBarManagerInternal != null) {
+                mStatusBarManagerInternal.setIcon(mSlotIme, packageName, iconId, 0,
+                        contentDescription != null
+                                ? contentDescription.toString() : null);
+                mStatusBarManagerInternal.setIconVisibility(mSlotIme, true);
             }
         }
     }
@@ -2607,7 +2592,7 @@
     private void hideStatusBarIconLocked(@UserIdInt int userId) {
         // To minimize app compat risk, ignore background users' request for single-user mode.
         // TODO(b/357178609): generalize the logic and remove this special rule.
-        if (!mConcurrentMultiUserModeEnabled && userId != mCurrentUserId) {
+        if (!mConcurrentMultiUserModeEnabled && userId != mCurrentImeUserId) {
             return;
         }
         if (mStatusBarManagerInternal != null) {
@@ -2635,11 +2620,12 @@
     }
 
     @GuardedBy("ImfLock.class")
-    private boolean shouldShowImeSwitcherLocked(int visibility, @UserIdInt int userId) {
+    private boolean shouldShowImeSwitcherLocked(@ImeWindowVisibility int visibility,
+            @UserIdInt int userId) {
         if (!mShowOngoingImeSwitcherForPhones) return false;
         // When the IME switcher dialog is shown, the IME switcher button should be hidden.
         // TODO(b/305849394): Make mMenuController multi-user aware.
-        final boolean switcherMenuShowing = mNewInputMethodSwitcherMenuEnabled
+        final boolean switcherMenuShowing = Flags.imeSwitcherRevamp()
                 ? mMenuControllerNew.isShowing()
                 : mMenuController.getSwitchingDialogLocked() != null;
         if (switcherMenuShowing) {
@@ -2655,12 +2641,10 @@
                 && mWindowManagerInternal.isKeyguardSecure(userId)) {
             return false;
         }
-        if ((visibility & InputMethodService.IME_ACTIVE) == 0
-                || (visibility & InputMethodService.IME_INVISIBLE) != 0) {
+        if ((visibility & InputMethodService.IME_ACTIVE) == 0) {
             return false;
         }
-        if (mWindowManagerInternal.isHardKeyboardAvailable()
-                && !mNewInputMethodSwitcherMenuEnabled) {
+        if (mWindowManagerInternal.isHardKeyboardAvailable() && !Flags.imeSwitcherRevamp()) {
             // When physical keyboard is attached, we show the ime switcher (or notification if
             // NavBar is not available) because SHOW_IME_WITH_HARD_KEYBOARD settings currently
             // exists in the IME switcher dialog.  Might be OK to remove this condition once
@@ -2671,7 +2655,7 @@
         }
 
         final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
-        if (mNewInputMethodSwitcherMenuEnabled) {
+        if (Flags.imeSwitcherRevamp()) {
             // The IME switcher button should be shown when the current IME specified a
             // language settings activity.
             final var curImi = settings.getMethodMap().get(settings.getSelectedInputMethod());
@@ -2739,29 +2723,25 @@
     }
 
     @BinderThread
+    @GuardedBy("ImfLock.class")
     @SuppressWarnings("deprecation")
-    private void setImeWindowStatus(@NonNull IBinder token, int vis, int backDisposition,
-            @NonNull UserData userData) {
+    private void setImeWindowStatusLocked(@ImeWindowVisibility int vis,
+            @BackDispositionMode int backDisposition, @NonNull UserData userData) {
         final int topFocusedDisplayId = mWindowManagerInternal.getTopFocusedDisplayId();
 
         final int userId = userData.mUserId;
-        synchronized (ImfLock.class) {
-            if (!calledWithValidTokenLocked(token, userData)) {
-                return;
-            }
-            final var bindingController = userData.mBindingController;
-            // Skip update IME status when current token display is not same as focused display.
-            // Note that we still need to update IME status when focusing external display
-            // that does not support system decoration and fallback to show IME on default
-            // display since it is intentional behavior.
-            final int tokenDisplayId = bindingController.getCurTokenDisplayId();
-            if (tokenDisplayId != topFocusedDisplayId && tokenDisplayId != FALLBACK_DISPLAY_ID) {
-                return;
-            }
-            bindingController.setImeWindowVis(vis);
-            bindingController.setBackDisposition(backDisposition);
-            updateSystemUiLocked(vis, backDisposition, userId);
+        final var bindingController = userData.mBindingController;
+        // Skip update IME status when current token display is not same as focused display.
+        // Note that we still need to update IME status when focusing external display
+        // that does not support system decoration and fallback to show IME on default
+        // display since it is intentional behavior.
+        final int tokenDisplayId = bindingController.getCurTokenDisplayId();
+        if (tokenDisplayId != topFocusedDisplayId && tokenDisplayId != FALLBACK_DISPLAY_ID) {
+            return;
         }
+        bindingController.setImeWindowVis(vis);
+        bindingController.setBackDisposition(backDisposition);
+        updateSystemUiLocked(vis, backDisposition, userId);
 
         final boolean dismissImeOnBackKeyPressed;
         switch (backDisposition) {
@@ -2780,19 +2760,14 @@
     }
 
     @BinderThread
-    private void reportStartInput(@NonNull IBinder token, IBinder startInputToken,
-            @NonNull UserData userData) {
-        synchronized (ImfLock.class) {
-            if (!calledWithValidTokenLocked(token, userData)) {
-                return;
-            }
-            final IBinder targetWindow = mImeTargetWindowMap.get(startInputToken);
-            if (targetWindow != null) {
-                mWindowManagerInternal.updateInputMethodTargetWindow(targetWindow);
-            }
-            final var visibilityStateComputer = userData.mVisibilityStateComputer;
-            visibilityStateComputer.setLastImeTargetWindow(targetWindow);
+    @GuardedBy("ImfLock.class")
+    private void reportStartInputLocked(IBinder startInputToken, @NonNull UserData userData) {
+        final IBinder targetWindow = mImeTargetWindowMap.get(startInputToken);
+        if (targetWindow != null) {
+            mWindowManagerInternal.updateInputMethodTargetWindow(targetWindow);
         }
+        final var visibilityStateComputer = userData.mVisibilityStateComputer;
+        visibilityStateComputer.setLastImeTargetWindow(targetWindow);
     }
 
     @GuardedBy("ImfLock.class")
@@ -2800,7 +2775,7 @@
         final int userId = resolveImeUserIdFromDisplayIdLocked(displayId);
         if (disableImeIcon) {
             final var bindingController = getInputMethodBindingController(userId);
-            updateSystemUiLocked(0, bindingController.getBackDisposition(), userId);
+            updateSystemUiLocked(0 /* vis */, bindingController.getBackDisposition(), userId);
         } else {
             updateSystemUiLocked(userId);
         }
@@ -2815,10 +2790,11 @@
     }
 
     @GuardedBy("ImfLock.class")
-    private void updateSystemUiLocked(int vis, int backDisposition, @UserIdInt int userId) {
+    private void updateSystemUiLocked(@ImeWindowVisibility int vis,
+            @BackDispositionMode int backDisposition, @UserIdInt int userId) {
         // To minimize app compat risk, ignore background users' request for single-user mode.
         // TODO(b/357178609): generalize the logic and remove this special rule.
-        if (!mConcurrentMultiUserModeEnabled && userId != mCurrentUserId) {
+        if (!mConcurrentMultiUserModeEnabled && userId != mCurrentImeUserId) {
             return;
         }
         final var userData = getUserData(userId);
@@ -2831,7 +2807,7 @@
         if (DEBUG) {
             Slog.d(TAG, "IME window vis: " + vis
                     + " active: " + (vis & InputMethodService.IME_ACTIVE)
-                    + " inv: " + (vis & InputMethodService.IME_INVISIBLE)
+                    + " visible: " + (vis & InputMethodService.IME_VISIBLE)
                     + " displayId: " + curTokenDisplayId);
         }
         final IBinder focusedWindowToken = userData.mImeBindingState != null
@@ -2839,8 +2815,8 @@
         final Boolean windowPerceptible = focusedWindowToken != null
                 ? mFocusedWindowPerceptible.get(focusedWindowToken) : null;
 
-        // TODO: Move this clearing calling identity block to setImeWindowStatus after making sure
-        // all updateSystemUi happens on system privilege.
+        // TODO: Move this clearing calling identity block to setImeWindowStatusLocked after making
+        //  sure all updateSystemUi happens on system privilege.
         final long ident = Binder.clearCallingIdentity();
         try {
             if (windowPerceptible != null && !windowPerceptible) {
@@ -2853,7 +2829,7 @@
             }
             final var curId = bindingController.getCurId();
             // TODO(b/305849394): Make mMenuController multi-user aware.
-            final boolean switcherMenuShowing = mNewInputMethodSwitcherMenuEnabled
+            final boolean switcherMenuShowing = Flags.imeSwitcherRevamp()
                     ? mMenuControllerNew.isShowing()
                     : mMenuController.getSwitchingDialogLocked() != null;
             if (switcherMenuShowing
@@ -2875,7 +2851,7 @@
     @GuardedBy("ImfLock.class")
     void updateFromSettingsLocked(boolean enabledMayChange, @UserIdInt int userId) {
         updateInputMethodsFromSettingsLocked(enabledMayChange, userId);
-        if (!mNewInputMethodSwitcherMenuEnabled) {
+        if (!Flags.imeSwitcherRevamp()) {
             mMenuController.updateKeyboardFromSettingsLocked(userId);
         }
     }
@@ -2943,7 +2919,7 @@
         }
         if (!TextUtils.isEmpty(id)) {
             try {
-                setInputMethodLocked(id, settings.getSelectedInputMethodSubtypeId(id), userId);
+                setInputMethodLocked(id, settings.getSelectedInputMethodSubtypeIndex(id), userId);
             } catch (IllegalArgumentException e) {
                 Slog.w(TAG, "Unknown input method from prefs: " + id, e);
                 resetCurrentMethodAndClientLocked(UnbindReason.SWITCH_IME_FAILED, userId);
@@ -2966,17 +2942,28 @@
                         ? subtype : null;
         final InputMethodSubtypeHandle newSubtypeHandle = normalizedSubtype != null
                 ? InputMethodSubtypeHandle.of(imi, normalizedSubtype) : null;
+
+        final var userData = getUserData(userId);
+
+        // A workaround for b/356879517. KeyboardLayoutManager has relied on an implementation
+        // detail that IMMS triggers this callback only for the current IME user.
+        // TODO(b/357663774): Figure out how to better handle this scenario.
+        userData.mSubtypeForKeyboardLayoutMapping =
+                Pair.create(newSubtypeHandle, normalizedSubtype);
+        if (userId != mCurrentImeUserId) {
+            return;
+        }
         mInputManagerInternal.onInputMethodSubtypeChangedForKeyboardLayoutMapping(
                 userId, newSubtypeHandle, normalizedSubtype);
     }
 
     @GuardedBy("ImfLock.class")
-    void setInputMethodLocked(String id, int subtypeId, @UserIdInt int userId) {
-        setInputMethodLocked(id, subtypeId, DEVICE_ID_DEFAULT, userId);
+    void setInputMethodLocked(String id, int subtypeIndex, @UserIdInt int userId) {
+        setInputMethodLocked(id, subtypeIndex, DEVICE_ID_DEFAULT, userId);
     }
 
     @GuardedBy("ImfLock.class")
-    void setInputMethodLocked(String id, int subtypeId, int deviceId, @UserIdInt int userId) {
+    void setInputMethodLocked(String id, int subtypeIndex, int deviceId, @UserIdInt int userId) {
         final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
         InputMethodInfo info = settings.getMethodMap().get(id);
         if (info == null) {
@@ -2993,25 +2980,25 @@
             }
             final InputMethodSubtype oldSubtype = bindingController.getCurrentSubtype();
             final InputMethodSubtype newSubtype;
-            if (subtypeId >= 0 && subtypeId < subtypeCount) {
-                newSubtype = info.getSubtypeAt(subtypeId);
+            if (subtypeIndex >= 0 && subtypeIndex < subtypeCount) {
+                newSubtype = info.getSubtypeAt(subtypeIndex);
             } else {
                 // If subtype is null, try to find the most applicable one from
                 // getCurrentInputMethodSubtype.
-                subtypeId = NOT_A_SUBTYPE_ID;
+                subtypeIndex = NOT_A_SUBTYPE_INDEX;
                 // TODO(b/347083680): The method below has questionable behaviors.
                 newSubtype = bindingController.getCurrentInputMethodSubtype();
                 if (newSubtype != null) {
                     for (int i = 0; i < subtypeCount; ++i) {
                         if (Objects.equals(newSubtype, info.getSubtypeAt(i))) {
-                            subtypeId = i;
+                            subtypeIndex = i;
                             break;
                         }
                     }
                 }
             }
             if (!Objects.equals(newSubtype, oldSubtype)) {
-                setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true, userId);
+                setSelectedInputMethodAndSubtypeLocked(info, subtypeIndex, true, userId);
                 IInputMethodInvoker curMethod = bindingController.getCurMethod();
                 if (curMethod != null) {
                     updateSystemUiLocked(bindingController.getImeWindowVis(),
@@ -3038,9 +3025,7 @@
         }
         final long ident = Binder.clearCallingIdentity();
         try {
-            // Set a subtype to this input method.
-            // subtypeId the name of a subtype which will be set.
-            setSelectedInputMethodAndSubtypeLocked(info, subtypeId, false, userId);
+            setSelectedInputMethodAndSubtypeLocked(info, subtypeIndex, false, userId);
             // mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked()
             // because mCurMethodId is stored as a history in
             // setSelectedInputMethodAndSubtypeLocked().
@@ -3735,7 +3720,7 @@
                             return InputBindResult.USER_SWITCHING;
                         }
                         final int[] profileIdsWithDisabled = mUserManagerInternal.getProfileIds(
-                                mCurrentUserId, false /* enabledOnly */);
+                                mCurrentImeUserId, false /* enabledOnly */);
                         for (int profileId : profileIdsWithDisabled) {
                             if (profileId == userId) {
                                 scheduleSwitchUserTaskLocked(userId, cs.mClient);
@@ -3782,9 +3767,9 @@
                     }
 
                     // Verify if caller is a background user.
-                    if (!mConcurrentMultiUserModeEnabled && userId != mCurrentUserId) {
+                    if (!mConcurrentMultiUserModeEnabled && userId != mCurrentImeUserId) {
                         if (ArrayUtils.contains(
-                                mUserManagerInternal.getProfileIds(mCurrentUserId, false),
+                                mUserManagerInternal.getProfileIds(mCurrentImeUserId, false),
                                 userId)) {
                             // cross-profile access is always allowed here to allow
                             // profile-switching.
@@ -4044,7 +4029,7 @@
     @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
     public boolean isInputMethodPickerShownForTest() {
         synchronized (ImfLock.class) {
-            return mNewInputMethodSwitcherMenuEnabled
+            return Flags.imeSwitcherRevamp()
                     ? mMenuControllerNew.isShowing()
                     : mMenuController.isisInputMethodPickerShownForTestLocked();
         }
@@ -4053,11 +4038,12 @@
     /**
      * Gets the list of Input Method Switcher Menu items and the index of the selected item.
      *
-     * @param items             the list of input method and subtype items.
-     * @param selectedImeId     the ID of the selected input method.
-     * @param selectedSubtypeId the ID of the selected input method subtype,
-     *                          or {@link #NOT_A_SUBTYPE_ID} if no subtype is selected.
-     * @param userId            the ID of the user for which to get the menu items.
+     * @param items                the list of input method and subtype items.
+     * @param selectedImeId        the ID of the selected input method.
+     * @param selectedSubtypeIndex the index of the selected subtype in the input method's array of
+     *                             subtypes, or {@link InputMethodUtils#NOT_A_SUBTYPE_INDEX} if no
+     *                             subtype is selected.
+     * @param userId               the ID of the user for which to get the menu items.
      * @return the list of menu items, and the index of the selected item,
      * or {@code -1} if no item is selected.
      */
@@ -4065,17 +4051,17 @@
     @NonNull
     private Pair<List<MenuItem>, Integer> getInputMethodPickerItems(
             @NonNull List<ImeSubtypeListItem> items, @Nullable String selectedImeId,
-            int selectedSubtypeId, @UserIdInt int userId) {
+            int selectedSubtypeIndex, @UserIdInt int userId) {
         final var bindingController = getInputMethodBindingController(userId);
         final var settings = InputMethodSettingsRepository.get(userId);
 
-        if (selectedSubtypeId == NOT_A_SUBTYPE_ID) {
+        if (selectedSubtypeIndex == NOT_A_SUBTYPE_INDEX) {
             // TODO(b/351124299): Check if this fallback logic is still necessary.
             final var curSubtype = bindingController.getCurrentInputMethodSubtype();
             if (curSubtype != null) {
                 final var curMethodId = bindingController.getSelectedMethodId();
                 final var curImi = settings.getMethodMap().get(curMethodId);
-                selectedSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode(
+                selectedSubtypeIndex = SubtypeUtils.getSubtypeIndexFromHashCode(
                         curImi, curSubtype.hashCode());
             }
         }
@@ -4090,35 +4076,24 @@
             final var item = items.get(i);
             final var imeId = item.mImi.getId();
             if (imeId.equals(selectedImeId)) {
-                final int subtypeId = item.mSubtypeId;
+                final int subtypeIndex = item.mSubtypeIndex;
                 // Check if this is the selected IME-subtype pair.
-                if ((subtypeId == 0 && selectedSubtypeId == NOT_A_SUBTYPE_ID)
-                        || subtypeId == NOT_A_SUBTYPE_ID
-                        || subtypeId == selectedSubtypeId) {
+                if ((subtypeIndex == 0 && selectedSubtypeIndex == NOT_A_SUBTYPE_INDEX)
+                        || subtypeIndex == NOT_A_SUBTYPE_INDEX
+                        || subtypeIndex == selectedSubtypeIndex) {
                     selectedIndex = i;
                 }
             }
             final boolean hasHeader = !imeId.equals(prevImeId);
             final boolean hasDivider = hasHeader && prevImeId != null;
             prevImeId = imeId;
-            menuItems.add(new MenuItem(item.mImeName, item.mSubtypeName, item.mImi, item.mSubtypeId,
-                    hasHeader, hasDivider));
+            menuItems.add(new MenuItem(item.mImeName, item.mSubtypeName, item.mImi,
+                    item.mSubtypeIndex, hasHeader, hasDivider));
         }
 
         return new Pair<>(menuItems, selectedIndex);
     }
 
-    @BinderThread
-    private void onImeSwitchButtonClickFromClient(@NonNull IBinder token, int displayId,
-            @NonNull UserData userData) {
-        synchronized (ImfLock.class) {
-            if (!calledWithValidTokenLocked(token, userData)) {
-                return;
-            }
-            onImeSwitchButtonClickLocked(token, displayId, userData);
-        }
-    }
-
     @IInputMethodManagerImpl.PermissionVerified(allOf = {
             Manifest.permission.INTERACT_ACROSS_USERS_FULL,
             Manifest.permission.WRITE_SECURE_SETTINGS})
@@ -4127,13 +4102,8 @@
         synchronized (ImfLock.class) {
             final int userId = resolveImeUserIdFromDisplayIdLocked(displayId);
             final var userData = getUserData(userId);
-            final var bindingController = userData.mBindingController;
-            final var curToken = bindingController.getCurToken();
-            if (curToken == null) {
-                return;
-            }
 
-            onImeSwitchButtonClickLocked(curToken, displayId, userData);
+            onImeSwitchButtonClickLocked(displayId, userData);
         }
     }
 
@@ -4141,17 +4111,16 @@
      * Handles a click on the IME switch button. Depending on the number of enabled IME subtypes,
      * this will either switch to the next IME/subtype, or show the input method picker dialog.
      *
-     * @param token     The token identifying the input method that triggered this.
      * @param displayId The ID of the display where the input method picker dialog should be shown.
      * @param userData  The data of the user for which to switch IMEs or show the picker dialog.
      */
+    @BinderThread
     @GuardedBy("ImfLock.class")
-    private void onImeSwitchButtonClickLocked(@NonNull IBinder token, int displayId,
-            @NonNull UserData userData) {
+    private void onImeSwitchButtonClickLocked(int displayId, @NonNull UserData userData) {
         final int userId = userData.mUserId;
         final var settings = InputMethodSettingsRepository.get(userId);
         if (hasMultipleSubtypesForSwitcher(true /* nonAuxOnly */, settings)) {
-            switchToNextInputMethodLocked(token, false /* onlyCurrentIme */, userData);
+            switchToNextInputMethodLocked(false /* onlyCurrentIme */, userData);
         } else {
             showInputMethodPickerFromSystem(
                     InputMethodManager.SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES, displayId);
@@ -4165,143 +4134,102 @@
     }
 
     @BinderThread
-    private void setInputMethod(@NonNull IBinder token, String id, @NonNull UserData userData) {
-        final int callingUid = Binder.getCallingUid();
-        final int userId = userData.mUserId;
-        synchronized (ImfLock.class) {
-            if (!calledWithValidTokenLocked(token, userData)) {
-                return;
-            }
-            final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
-            final InputMethodInfo imi = settings.getMethodMap().get(id);
-            if (imi == null || !canCallerAccessInputMethod(
-                    imi.getPackageName(), callingUid, userId, settings)) {
-                throw getExceptionForUnknownImeId(id);
-            }
-            setInputMethodWithSubtypeIdLocked(token, id, NOT_A_SUBTYPE_ID, userId);
-        }
-    }
-
-    @BinderThread
-    private void setInputMethodAndSubtype(@NonNull IBinder token, String id,
-            InputMethodSubtype subtype, @NonNull UserData userData) {
-        final int callingUid = Binder.getCallingUid();
-        final int userId = userData.mUserId;
-        synchronized (ImfLock.class) {
-            if (!calledWithValidTokenLocked(token, userData)) {
-                return;
-            }
-            final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
-            final InputMethodInfo imi = settings.getMethodMap().get(id);
-            if (imi == null || !canCallerAccessInputMethod(
-                    imi.getPackageName(), callingUid, userId, settings)) {
-                throw getExceptionForUnknownImeId(id);
-            }
-            if (subtype != null) {
-                setInputMethodWithSubtypeIdLocked(token, id,
-                        SubtypeUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode()), userId);
-            } else {
-                setInputMethod(token, id, userData);
-            }
-        }
-    }
-
-    @BinderThread
-    private boolean switchToPreviousInputMethod(@NonNull IBinder token,
+    @GuardedBy("ImfLock.class")
+    private void setInputMethodAndSubtypeLocked(String id, @Nullable InputMethodSubtype subtype,
             @NonNull UserData userData) {
+        final int callingUid = Binder.getCallingUid();
         final int userId = userData.mUserId;
-        synchronized (ImfLock.class) {
-            if (!calledWithValidTokenLocked(token, userData)) {
-                return false;
-            }
-            final var bindingController = userData.mBindingController;
-            final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
-            final Pair<String, String> lastIme = settings.getLastInputMethodAndSubtype();
-            final InputMethodInfo lastImi;
-            if (lastIme != null) {
-                lastImi = settings.getMethodMap().get(lastIme.first);
-            } else {
-                lastImi = null;
-            }
-            final var currentSubtype = bindingController.getCurrentSubtype();
-            String targetLastImiId = null;
-            int subtypeId = NOT_A_SUBTYPE_ID;
-            if (lastIme != null && lastImi != null) {
-                final boolean imiIdIsSame = lastImi.getId().equals(
-                        bindingController.getSelectedMethodId());
-                final int lastSubtypeHash = Integer.parseInt(lastIme.second);
-                final int currentSubtypeHash = currentSubtype == null ? NOT_A_SUBTYPE_ID
-                        : currentSubtype.hashCode();
-                // If the last IME is the same as the current IME and the last subtype is not
-                // defined, there is no need to switch to the last IME.
-                if (!imiIdIsSame || lastSubtypeHash != currentSubtypeHash) {
-                    targetLastImiId = lastIme.first;
-                    subtypeId = SubtypeUtils.getSubtypeIdFromHashCode(lastImi, lastSubtypeHash);
-                }
-            }
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
+        final InputMethodInfo imi = settings.getMethodMap().get(id);
+        if (imi == null || !canCallerAccessInputMethod(
+                imi.getPackageName(), callingUid, userId, settings)) {
+            throw getExceptionForUnknownImeId(id);
+        }
+        final int subtypeIndex = subtype != null
+                ? SubtypeUtils.getSubtypeIndexFromHashCode(imi, subtype.hashCode())
+                : NOT_A_SUBTYPE_INDEX;
+        setInputMethodWithSubtypeIndexLocked(id, subtypeIndex, userId);
+    }
 
-            if (TextUtils.isEmpty(targetLastImiId)
-                    && !InputMethodUtils.canAddToLastInputMethod(currentSubtype)) {
-                // This is a safety net. If the currentSubtype can't be added to the history
-                // and the framework couldn't find the last ime, we will make the last ime be
-                // the most applicable enabled keyboard subtype of the system imes.
-                final List<InputMethodInfo> enabled = settings.getEnabledInputMethodList();
-                if (enabled != null) {
-                    final int enabledCount = enabled.size();
-                    final String locale;
-                    if (currentSubtype != null
-                            && !TextUtils.isEmpty(currentSubtype.getLocale())) {
-                        locale = currentSubtype.getLocale();
-                    } else {
-                        locale = SystemLocaleWrapper.get(userId).get(0).toString();
-                    }
-                    for (int i = 0; i < enabledCount; ++i) {
-                        final InputMethodInfo imi = enabled.get(i);
-                        if (imi.getSubtypeCount() > 0 && imi.isSystem()) {
-                            InputMethodSubtype keyboardSubtype =
-                                    SubtypeUtils.findLastResortApplicableSubtype(
-                                            SubtypeUtils.getSubtypes(imi),
-                                            SubtypeUtils.SUBTYPE_MODE_KEYBOARD, locale, true);
-                            if (keyboardSubtype != null) {
-                                targetLastImiId = imi.getId();
-                                subtypeId = SubtypeUtils.getSubtypeIdFromHashCode(imi,
-                                        keyboardSubtype.hashCode());
-                                if (keyboardSubtype.getLocale().equals(locale)) {
-                                    break;
-                                }
-                            }
+    @BinderThread
+    @GuardedBy("ImfLock.class")
+    private boolean switchToPreviousInputMethodLocked(@NonNull UserData userData) {
+        final int userId = userData.mUserId;
+        final var bindingController = userData.mBindingController;
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
+        final Pair<String, String> lastIme = settings.getLastInputMethodAndSubtype();
+        final InputMethodInfo lastImi;
+        if (lastIme != null) {
+            lastImi = settings.getMethodMap().get(lastIme.first);
+        } else {
+            lastImi = null;
+        }
+        final var currentSubtype = bindingController.getCurrentSubtype();
+        String targetLastImiId = null;
+        int subtypeIndex = NOT_A_SUBTYPE_INDEX;
+        if (lastIme != null && lastImi != null) {
+            final boolean imiIdIsSame = lastImi.getId().equals(
+                    bindingController.getSelectedMethodId());
+            final int lastSubtypeHash = Integer.parseInt(lastIme.second);
+            final int currentSubtypeHash = currentSubtype == null ? NOT_A_SUBTYPE_INDEX
+                    : currentSubtype.hashCode();
+            // If the last IME is the same as the current IME and the last subtype is not
+            // defined, there is no need to switch to the last IME.
+            if (!imiIdIsSame || lastSubtypeHash != currentSubtypeHash) {
+                targetLastImiId = lastIme.first;
+                subtypeIndex = SubtypeUtils.getSubtypeIndexFromHashCode(lastImi, lastSubtypeHash);
+            }
+        }
+
+        if (TextUtils.isEmpty(targetLastImiId)
+                && !InputMethodUtils.canAddToLastInputMethod(currentSubtype)) {
+            // This is a safety net. If the currentSubtype can't be added to the history
+            // and the framework couldn't find the last ime, we will make the last ime be
+            // the most applicable enabled keyboard subtype of the system imes.
+            final List<InputMethodInfo> enabled = settings.getEnabledInputMethodList();
+            final int enabledCount = enabled.size();
+            final String locale;
+            if (currentSubtype != null
+                    && !TextUtils.isEmpty(currentSubtype.getLocale())) {
+                locale = currentSubtype.getLocale();
+            } else {
+                locale = SystemLocaleWrapper.get(userId).get(0).toString();
+            }
+            for (int i = 0; i < enabledCount; ++i) {
+                final InputMethodInfo imi = enabled.get(i);
+                if (imi.getSubtypeCount() > 0 && imi.isSystem()) {
+                    InputMethodSubtype keyboardSubtype =
+                            SubtypeUtils.findLastResortApplicableSubtype(
+                                    SubtypeUtils.getSubtypes(imi),
+                                    SubtypeUtils.SUBTYPE_MODE_KEYBOARD, locale, true);
+                    if (keyboardSubtype != null) {
+                        targetLastImiId = imi.getId();
+                        subtypeIndex = SubtypeUtils.getSubtypeIndexFromHashCode(imi,
+                                keyboardSubtype.hashCode());
+                        if (keyboardSubtype.getLocale().equals(locale)) {
+                            break;
                         }
                     }
                 }
             }
+        }
 
-            if (!TextUtils.isEmpty(targetLastImiId)) {
-                if (DEBUG) {
-                    Slog.d(TAG, "Switch to: " + lastImi.getId() + ", " + lastIme.second
-                            + ", from: " + bindingController.getSelectedMethodId() + ", "
-                            + subtypeId);
-                }
-                setInputMethodWithSubtypeIdLocked(token, targetLastImiId, subtypeId, userId);
-                return true;
-            } else {
-                return false;
+        if (!TextUtils.isEmpty(targetLastImiId)) {
+            if (DEBUG) {
+                Slog.d(TAG, "Switch to: " + lastImi.getId() + ", " + lastIme.second
+                        + ", from: " + bindingController.getSelectedMethodId() + ", "
+                        + subtypeIndex);
             }
+            setInputMethodWithSubtypeIndexLocked(targetLastImiId, subtypeIndex, userId);
+            return true;
+        } else {
+            return false;
         }
     }
 
     @BinderThread
-    private boolean switchToNextInputMethod(@NonNull IBinder token, boolean onlyCurrentIme,
-            @NonNull UserData userData) {
-        synchronized (ImfLock.class) {
-            if (!calledWithValidTokenLocked(token, userData)) {
-                return false;
-            }
-            return switchToNextInputMethodLocked(token, onlyCurrentIme, userData);
-        }
-    }
-
     @GuardedBy("ImfLock.class")
-    private boolean switchToNextInputMethodLocked(@Nullable IBinder token, boolean onlyCurrentIme,
+    private boolean switchToNextInputMethodLocked(boolean onlyCurrentIme,
             @NonNull UserData userData) {
         final var bindingController = userData.mBindingController;
         final var currentImi = bindingController.getSelectedMethod();
@@ -4312,26 +4240,21 @@
         if (nextSubtype == null) {
             return false;
         }
-        setInputMethodWithSubtypeIdLocked(token, nextSubtype.mImi.getId(),
-                nextSubtype.mSubtypeId, userData.mUserId);
+        setInputMethodWithSubtypeIndexLocked(nextSubtype.mImi.getId(), nextSubtype.mSubtypeIndex,
+                userData.mUserId);
         return true;
     }
 
     @BinderThread
-    private boolean shouldOfferSwitchingToNextInputMethod(@NonNull IBinder token,
-            @NonNull UserData userData) {
-        synchronized (ImfLock.class) {
-            if (!calledWithValidTokenLocked(token, userData)) {
-                return false;
-            }
-            final var bindingController = userData.mBindingController;
-            final var currentImi = bindingController.getSelectedMethod();
-            final ImeSubtypeListItem nextSubtype = userData.mSwitchingController
-                    .getNextInputMethodLocked(false /* onlyCurrentIme */, currentImi,
-                            bindingController.getCurrentSubtype(),
-                            MODE_AUTO, true /* forward */);
-            return nextSubtype != null;
-        }
+    @GuardedBy("ImfLock.class")
+    private boolean shouldOfferSwitchingToNextInputMethodLocked(@NonNull UserData userData) {
+        final var bindingController = userData.mBindingController;
+        final var currentImi = bindingController.getSelectedMethod();
+        final ImeSubtypeListItem nextSubtype = userData.mSwitchingController
+                .getNextInputMethodLocked(false /* onlyCurrentIme */, currentImi,
+                        bindingController.getCurrentSubtype(),
+                        MODE_AUTO, true /* forward */);
+        return nextSubtype != null;
     }
 
     @Override
@@ -4383,7 +4306,7 @@
                 try {
                     final var methodMap = userData.mRawInputMethodMap.get().toInputMethodMap(
                             AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO,
-                            mUserManagerInternal.isUserUnlockingOrUnlocked(userId));
+                            userData.mIsUnlockingOrUnlocked.get());
                     final var newSettings = InputMethodSettings.create(methodMap, userId);
                     InputMethodSettingsRepository.put(userId, newSettings);
                     postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */, userId);
@@ -4534,7 +4457,7 @@
         mStylusIds.add(deviceId);
         // a new Stylus is detected. If IME supports handwriting, and we don't have
         // handwriting initialized, lets do it now.
-        final var bindingController = getInputMethodBindingController(mCurrentUserId);
+        final var bindingController = getInputMethodBindingController(mCurrentImeUserId);
         if (!mHwController.getCurrentRequestId().isPresent()
                 && bindingController.supportsStylusHandwriting()) {
             scheduleResetStylusHandwriting();
@@ -4723,7 +4646,7 @@
 
     private void dumpDebug(ProtoOutputStream proto, long fieldId) {
         synchronized (ImfLock.class) {
-            final int userId = mCurrentUserId;
+            final int userId = mCurrentImeUserId;
             final var userData = getUserData(userId);
             final var bindingController = userData.mBindingController;
             final var visibilityStateComputer = userData.mVisibilityStateComputer;
@@ -4750,7 +4673,7 @@
             proto.write(IS_INTERACTIVE, mIsInteractive);
             proto.write(BACK_DISPOSITION, bindingController.getBackDisposition());
             proto.write(IME_WINDOW_VISIBILITY, bindingController.getImeWindowVis());
-            if (!mNewInputMethodSwitcherMenuEnabled) {
+            if (!Flags.imeSwitcherRevamp()) {
                 proto.write(SHOW_IME_WITH_HARD_KEYBOARD,
                         mMenuController.getShowImeWithHardKeyboard());
             }
@@ -4760,95 +4683,61 @@
     }
 
     @BinderThread
-    private void notifyUserAction(@NonNull IBinder token, @NonNull UserData userData) {
+    @GuardedBy("ImfLock.class")
+    private void notifyUserActionLocked(@NonNull UserData userData) {
         if (DEBUG) {
             Slog.d(TAG, "Got the notification of a user action.");
         }
-        synchronized (ImfLock.class) {
-            final var bindingController = userData.mBindingController;
-            if (bindingController.getCurToken() != token) {
-                if (DEBUG) {
-                    Slog.d(TAG, "Ignoring the user action notification from IMEs that are no longer"
-                            + " active.");
-                }
-                return;
-            }
-            final InputMethodInfo imi = bindingController.getSelectedMethod();
-            if (imi != null) {
-                userData.mSwitchingController.onUserActionLocked(imi,
-                        bindingController.getCurrentSubtype());
-            }
+        final var bindingController = userData.mBindingController;
+        final InputMethodInfo imi = bindingController.getSelectedMethod();
+        if (imi != null) {
+            userData.mSwitchingController.onUserActionLocked(imi,
+                    bindingController.getCurrentSubtype());
         }
     }
 
     @BinderThread
-    private void applyImeVisibility(IBinder token, IBinder windowToken, boolean setVisible,
+    @GuardedBy("ImfLock.class")
+    private void applyImeVisibilityLocked(IBinder windowToken, boolean setVisible,
             @NonNull ImeTracker.Token statsToken, @NonNull UserData userData) {
         try {
-            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.applyImeVisibility");
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.applyImeVisibilityLocked");
             final int userId = userData.mUserId;
-            synchronized (ImfLock.class) {
-                if (!calledWithValidTokenLocked(token, userData)) {
-                    ImeTracker.forLogging().onFailed(statsToken,
-                            ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
-                    return;
-                }
-                ImeTracker.forLogging().onProgress(statsToken,
-                        ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
-                final var visibilityStateComputer = userData.mVisibilityStateComputer;
-                final IBinder requestToken = visibilityStateComputer.getWindowTokenFrom(
-                        windowToken, userId);
-                mVisibilityApplier.applyImeVisibility(requestToken, statsToken,
-                        setVisible ? STATE_SHOW_IME : STATE_HIDE_IME,
-                        SoftInputShowHideReason.NOT_SET /* ignore reason */, userId);
-            }
+            final var visibilityStateComputer = userData.mVisibilityStateComputer;
+            final IBinder requestToken = visibilityStateComputer.getWindowTokenFrom(
+                    windowToken, userId);
+            mVisibilityApplier.applyImeVisibility(requestToken, statsToken,
+                    setVisible ? STATE_SHOW_IME : STATE_HIDE_IME,
+                    SoftInputShowHideReason.NOT_SET /* ignore reason */, userId);
         } finally {
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
     }
 
     @BinderThread
-    private void resetStylusHandwriting(int requestId) {
-        synchronized (ImfLock.class) {
-            final OptionalInt curRequest = mHwController.getCurrentRequestId();
-            if (!curRequest.isPresent() || curRequest.getAsInt() != requestId) {
-                Slog.w(TAG, "IME requested to finish handwriting with a mismatched requestId: "
-                        + requestId);
-            }
-            removeVirtualStylusIdForTestSessionLocked();
-            scheduleResetStylusHandwriting();
+    @GuardedBy("ImfLock.class")
+    private void resetStylusHandwritingLocked(int requestId) {
+        final OptionalInt curRequest = mHwController.getCurrentRequestId();
+        if (!curRequest.isPresent() || curRequest.getAsInt() != requestId) {
+            Slog.w(TAG, "IME requested to finish handwriting with a mismatched requestId: "
+                    + requestId);
         }
+        removeVirtualStylusIdForTestSessionLocked();
+        scheduleResetStylusHandwriting();
     }
 
     @GuardedBy("ImfLock.class")
-    private void setInputMethodWithSubtypeIdLocked(IBinder token, String id, int subtypeId,
+    private void setInputMethodWithSubtypeIndexLocked(String id, int subtypeIndex,
             @UserIdInt int userId) {
-        final var bindingController = getInputMethodBindingController(userId);
-        if (token == null) {
-            if (mContext.checkCallingOrSelfPermission(
-                    android.Manifest.permission.WRITE_SECURE_SETTINGS)
-                    != PackageManager.PERMISSION_GRANTED) {
-                throw new SecurityException(
-                        "Using null token requires permission "
-                                + android.Manifest.permission.WRITE_SECURE_SETTINGS);
-            }
-        } else if (bindingController.getCurToken() != token) {
-            Slog.w(TAG, "Ignoring setInputMethod of uid " + Binder.getCallingUid()
-                    + " token: " + token);
-            return;
-        } else {
-            // Called with current IME's token.
-            final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
-            if (settings.getMethodMap().get(id) != null
-                    && settings.getEnabledInputMethodListWithFilter(
-                            (info) -> info.getId().equals(id)).isEmpty()) {
-                throw new IllegalStateException("Requested IME is not enabled: " + id);
-            }
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
+        if (settings.getMethodMap().get(id) != null
+                && settings.getEnabledInputMethodListWithFilter(
+                        (info) -> info.getId().equals(id)).isEmpty()) {
+            throw new IllegalStateException("Requested IME is not enabled: " + id);
         }
-
         final long ident = Binder.clearCallingIdentity();
         try {
-            setInputMethodLocked(id, subtypeId, userId);
+            setInputMethodLocked(id, subtypeIndex, userId);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -4883,72 +4772,36 @@
     }
 
     @BinderThread
-    private void hideMySoftInput(@NonNull IBinder token, @NonNull ImeTracker.Token statsToken,
+    @GuardedBy("ImfLock.class")
+    private void hideMySoftInputLocked(@NonNull ImeTracker.Token statsToken,
             @InputMethodManager.HideFlags int flags, @SoftInputShowHideReason int reason,
             @NonNull UserData userData) {
-        try {
-            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideMySoftInput");
-            final int userId = userData.mUserId;
-            synchronized (ImfLock.class) {
-                if (!calledWithValidTokenLocked(token, userData)) {
-                    ImeTracker.forLogging().onFailed(statsToken,
-                            ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
-                    return;
-                }
-                ImeTracker.forLogging().onProgress(statsToken,
-                        ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
-                final long ident = Binder.clearCallingIdentity();
-                try {
-                    if (Flags.refactorInsetsController()) {
-                        userData.mCurClient.mClient.setImeVisibility(false, statsToken);
-                        // TODO we will loose the flags here
-                        setImeVisibilityOnFocusedWindowClient(false, userData, statsToken);
-                    } else {
-                        final var visibilityStateComputer = userData.mVisibilityStateComputer;
-                        hideCurrentInputLocked(visibilityStateComputer.getLastImeTargetWindow(),
-                                statsToken, flags, null /* resultReceiver */, reason, userId);
-                    }
-                } finally {
-                    Binder.restoreCallingIdentity(ident);
-                }
-            }
-        } finally {
-            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+        final int userId = userData.mUserId;
+        if (Flags.refactorInsetsController()) {
+            userData.mCurClient.mClient.setImeVisibility(false, statsToken);
+            // TODO we will loose the flags here
+            setImeVisibilityOnFocusedWindowClient(false, userData, statsToken);
+        } else {
+            final var visibilityStateComputer = userData.mVisibilityStateComputer;
+            hideCurrentInputLocked(visibilityStateComputer.getLastImeTargetWindow(),
+                    statsToken, flags, null /* resultReceiver */, reason, userId);
         }
     }
 
     @BinderThread
-    private void showMySoftInput(@NonNull IBinder token, @NonNull ImeTracker.Token statsToken,
+    @GuardedBy("ImfLock.class")
+    private void showMySoftInputLocked(@NonNull ImeTracker.Token statsToken,
             @InputMethodManager.ShowFlags int flags, @SoftInputShowHideReason int reason,
             @NonNull UserData userData) {
-        try {
-            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showMySoftInput");
-            final int userId = userData.mUserId;
-            synchronized (ImfLock.class) {
-                if (!calledWithValidTokenLocked(token, userData)) {
-                    ImeTracker.forLogging().onFailed(statsToken,
-                            ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
-                    return;
-                }
-                ImeTracker.forLogging().onProgress(statsToken,
-                        ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
-                final long ident = Binder.clearCallingIdentity();
-                try {
-                    if (Flags.refactorInsetsController()) {
-                        userData.mCurClient.mClient.setImeVisibility(true, statsToken);
-                        setImeVisibilityOnFocusedWindowClient(true, userData, statsToken);
-                    } else {
-                        final var visibilityStateComputer = userData.mVisibilityStateComputer;
-                        showCurrentInputLocked(visibilityStateComputer.getLastImeTargetWindow(),
-                                statsToken, flags, MotionEvent.TOOL_TYPE_UNKNOWN,
-                                null /* resultReceiver */, reason, userId);
-                    }
-                } finally {
-                    Binder.restoreCallingIdentity(ident);
-                }
-            }
-        } finally {
-            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+        final int userId = userData.mUserId;
+        if (Flags.refactorInsetsController()) {
+            userData.mCurClient.mClient.setImeVisibility(true, statsToken);
+            setImeVisibilityOnFocusedWindowClient(true, userData, statsToken);
+        } else {
+            final var visibilityStateComputer = userData.mVisibilityStateComputer;
+            showCurrentInputLocked(visibilityStateComputer.getLastImeTargetWindow(),
+                    statsToken, flags, MotionEvent.TOOL_TYPE_UNKNOWN,
+                    null /* resultReceiver */, reason, userId);
         }
     }
 
@@ -5045,7 +4898,8 @@
         final boolean isScreenLocked = mWindowManagerInternal.isKeyguardLocked()
                 && mWindowManagerInternal.isKeyguardSecure(userId);
         final String lastInputMethodId = settings.getSelectedInputMethod();
-        int lastInputMethodSubtypeId = settings.getSelectedInputMethodSubtypeId(lastInputMethodId);
+        final int lastInputMethodSubtypeIndex =
+                settings.getSelectedInputMethodSubtypeIndex(lastInputMethodId);
 
         final List<ImeSubtypeListItem> imList = InputMethodSubtypeSwitchingController
                 .getSortedInputMethodAndSubtypeList(
@@ -5059,30 +4913,30 @@
             return;
         }
 
-        if (mNewInputMethodSwitcherMenuEnabled) {
+        if (Flags.imeSwitcherRevamp()) {
             if (DEBUG) {
                 Slog.v(TAG, "Show IME switcher menu,"
                         + " showAuxSubtypes=" + showAuxSubtypes
                         + " displayId=" + displayId
                         + " preferredInputMethodId=" + lastInputMethodId
-                        + " preferredInputMethodSubtypeId=" + lastInputMethodSubtypeId);
+                        + " preferredInputMethodSubtypeIndex=" + lastInputMethodSubtypeIndex);
             }
 
             final var itemsAndIndex = getInputMethodPickerItems(imList,
-                    lastInputMethodId, lastInputMethodSubtypeId, userId);
+                    lastInputMethodId, lastInputMethodSubtypeIndex, userId);
             final var menuItems = itemsAndIndex.first;
             final int selectedIndex = itemsAndIndex.second;
 
             if (selectedIndex == -1) {
                 Slog.w(TAG, "Switching menu shown with no item selected"
                         + ", IME id: " + lastInputMethodId
-                        + ", subtype index: " + lastInputMethodSubtypeId);
+                        + ", subtype index: " + lastInputMethodSubtypeIndex);
             }
 
             mMenuControllerNew.show(menuItems, selectedIndex, displayId, userId);
         } else {
             mMenuController.showInputMethodMenuLocked(showAuxSubtypes, displayId,
-                    lastInputMethodId, lastInputMethodSubtypeId, imList, userId);
+                    lastInputMethodId, lastInputMethodSubtypeIndex, imList, userId);
         }
     }
 
@@ -5111,7 +4965,7 @@
             case MSG_REMOVE_IME_SURFACE: {
                 synchronized (ImfLock.class) {
                     // TODO(b/305849394): Needs to figure out what to do where for background users.
-                    final int userId = mCurrentUserId;
+                    final int userId = mCurrentImeUserId;
                     final var userData = getUserData(userId);
                     try {
                         if (userData.mEnabledSession != null
@@ -5149,7 +5003,7 @@
 
             // --------------------------------------------------------------
             case MSG_HARD_KEYBOARD_SWITCH_CHANGED:
-                if (!mNewInputMethodSwitcherMenuEnabled) {
+                if (!Flags.imeSwitcherRevamp()) {
                     mMenuController.handleHardKeyboardStatusChange(msg.arg1 == 1);
                 }
                 synchronized (ImfLock.class) {
@@ -5177,7 +5031,8 @@
 
             case MSG_RESET_HANDWRITING: {
                 synchronized (ImfLock.class) {
-                    final var bindingController = getInputMethodBindingController(mCurrentUserId);
+                    final var bindingController =
+                            getInputMethodBindingController(mCurrentImeUserId);
                     if (bindingController.supportsStylusHandwriting()
                             && bindingController.getCurMethod() != null
                             && hasSupportedStylusLocked()) {
@@ -5251,7 +5106,9 @@
     private record HandwritingRequest(int requestId, int pid, @NonNull UserData userData) { }
 
     @BinderThread
-    private void onStylusHandwritingReady(int requestId, int pid, @NonNull UserData userData) {
+    @GuardedBy("ImfLock.class")
+    private void onStylusHandwritingReadyLocked(int requestId, int pid,
+            @NonNull UserData userData) {
         mHandler.obtainMessage(MSG_START_HANDWRITING,
                 new HandwritingRequest(requestId, pid, userData)).sendToTarget();
     }
@@ -5259,7 +5116,7 @@
     private void handleSetInteractive(final boolean interactive) {
         synchronized (ImfLock.class) {
             // TODO(b/305849394): Support multiple IMEs.
-            final int userId = mCurrentUserId;
+            final int userId = mCurrentImeUserId;
             final var userData = getUserData(userId);
             final var bindingController = userData.mBindingController;
             mIsInteractive = interactive;
@@ -5599,7 +5456,7 @@
     }
 
     @GuardedBy("ImfLock.class")
-    private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId,
+    private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeIndex,
             boolean setSubtypeOnly, @UserIdInt int userId) {
         final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
         final var bindingController = getInputMethodBindingController(userId);
@@ -5609,12 +5466,12 @@
         // Set Subtype here
         final int newSubtypeHashcode;
         final InputMethodSubtype newSubtype;
-        if (imi == null || subtypeId < 0) {
+        if (imi == null || subtypeIndex < 0) {
             newSubtypeHashcode = INVALID_SUBTYPE_HASHCODE;
             newSubtype = null;
         } else {
-            if (subtypeId < imi.getSubtypeCount()) {
-                InputMethodSubtype subtype = imi.getSubtypeAt(subtypeId);
+            if (subtypeIndex < imi.getSubtypeCount()) {
+                InputMethodSubtype subtype = imi.getSubtypeAt(subtypeIndex);
                 newSubtypeHashcode = subtype.hashCode();
                 newSubtype = subtype;
             } else {
@@ -5650,20 +5507,20 @@
         settings.putSelectedDefaultDeviceInputMethod(null);
 
         InputMethodInfo imi = settings.getMethodMap().get(newDefaultIme);
-        int lastSubtypeId = NOT_A_SUBTYPE_ID;
+        int lastSubtypeIndex = NOT_A_SUBTYPE_INDEX;
         // newDefaultIme is empty when there is no candidate for the selected IME.
         if (imi != null && !TextUtils.isEmpty(newDefaultIme)) {
             String subtypeHashCode = settings.getLastSubtypeForInputMethod(newDefaultIme);
             if (subtypeHashCode != null) {
                 try {
-                    lastSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode(imi,
+                    lastSubtypeIndex = SubtypeUtils.getSubtypeIndexFromHashCode(imi,
                             Integer.parseInt(subtypeHashCode));
                 } catch (NumberFormatException e) {
                     Slog.w(TAG, "HashCode for subtype looks broken: " + subtypeHashCode, e);
                 }
             }
         }
-        setSelectedInputMethodAndSubtypeLocked(imi, lastSubtypeId, false, userId);
+        setSelectedInputMethodAndSubtypeLocked(imi, lastSubtypeIndex, false, userId);
     }
 
     /**
@@ -5687,14 +5544,14 @@
     }
 
     @GuardedBy("ImfLock.class")
-    private boolean switchToInputMethodLocked(@NonNull String imeId, int subtypeId,
+    private boolean switchToInputMethodLocked(@NonNull String imeId, int subtypeIndex,
             @UserIdInt int userId) {
         final var settings = InputMethodSettingsRepository.get(userId);
         final var enabledImes = settings.getEnabledInputMethodList();
         if (!CollectionUtils.any(enabledImes, imi -> imi.getId().equals(imeId))) {
             return false; // IME is not found or not enabled.
         }
-        setInputMethodLocked(imeId, subtypeId, userId);
+        setInputMethodLocked(imeId, subtypeIndex, userId);
         return true;
     }
 
@@ -5750,8 +5607,8 @@
                 return;
             }
 
-            final var nextSubtype = nextItem.mSubtypeId > NOT_A_SUBTYPE_ID
-                    ? nextItem.mImi.getSubtypeAt(nextItem.mSubtypeId) : null;
+            final var nextSubtype = nextItem.mSubtypeIndex > NOT_A_SUBTYPE_INDEX
+                    ? nextItem.mImi.getSubtypeAt(nextItem.mSubtypeIndex) : null;
             nextSubtypeHandle = InputMethodSubtypeHandle.of(nextItem.mImi, nextSubtype);
         } else {
             final InputMethodSubtypeHandle currentSubtypeHandle =
@@ -5770,7 +5627,7 @@
         final int subtypeCount = nextImi.getSubtypeCount();
         if (subtypeCount == 0) {
             if (nextSubtypeHandle.equals(InputMethodSubtypeHandle.of(nextImi, null))) {
-                setInputMethodLocked(nextImi.getId(), NOT_A_SUBTYPE_ID, userId);
+                setInputMethodLocked(nextImi.getId(), NOT_A_SUBTYPE_INDEX, userId);
             }
             return;
         }
@@ -5848,10 +5705,10 @@
         }
 
         @Override
-        public boolean switchToInputMethod(@NonNull String imeId, int subtypeId,
+        public boolean switchToInputMethod(@NonNull String imeId, int subtypeIndex,
                 @UserIdInt int userId) {
             synchronized (ImfLock.class) {
-                return switchToInputMethodLocked(imeId, subtypeId, userId);
+                return switchToInputMethodLocked(imeId, subtypeIndex, userId);
             }
         }
 
@@ -5933,7 +5790,7 @@
                 final var visibilityStateComputer = userData.mVisibilityStateComputer;
                 if (visibilityStateComputer.getLastImeTargetWindow()
                         != userData.mImeBindingState.mFocusedWindow) {
-                    if (mNewInputMethodSwitcherMenuEnabled) {
+                    if (Flags.imeSwitcherRevamp()) {
                         final var bindingController = getInputMethodBindingController(userId);
                         mMenuControllerNew.hide(bindingController.getCurTokenDisplayId(), userId);
                     } else {
@@ -6080,71 +5937,54 @@
     }
 
     @BinderThread
-    private IInputContentUriToken createInputContentUriToken(@Nullable IBinder token,
-            @Nullable Uri contentUri, @Nullable String packageName, @NonNull UserData userData) {
-        if (token == null) {
-            throw new NullPointerException("token");
-        }
-        if (packageName == null) {
-            throw new NullPointerException("packageName");
-        }
-        if (contentUri == null) {
-            throw new NullPointerException("contentUri");
-        }
+    @GuardedBy("ImfLock.class")
+    @Nullable
+    private IInputContentUriToken createInputContentUriTokenLocked(@NonNull Uri contentUri,
+            @NonNull String packageName, @NonNull UserData userData) {
+        Objects.requireNonNull(packageName, "packageName must not be null");
+        Objects.requireNonNull(contentUri, "contentUri must not be null");
         final String contentUriScheme = contentUri.getScheme();
         if (!"content".equals(contentUriScheme)) {
             throw new InvalidParameterException("contentUri must have content scheme");
         }
 
-        synchronized (ImfLock.class) {
-            final int uid = Binder.getCallingUid();
-            final var bindingController = userData.mBindingController;
-            if (bindingController.getSelectedMethodId() == null) {
-                return null;
-            }
-            if (bindingController.getCurToken() != token) {
-                Slog.e(TAG, "Ignoring createInputContentUriToken mCurToken="
-                        + bindingController.getCurToken() + " token=" + token);
-                return null;
-            }
-            // We cannot simply distinguish a bad IME that reports an arbitrary package name from
-            // an unfortunate IME whose internal state is already obsolete due to the asynchronous
-            // nature of our system.  Let's compare it with our internal record.
-            final var curPackageName = userData.mCurEditorInfo != null
-                    ? userData.mCurEditorInfo.packageName : null;
-            if (!TextUtils.equals(curPackageName, packageName)) {
-                Slog.e(TAG, "Ignoring createInputContentUriToken mCurEditorInfo.packageName="
-                        + curPackageName + " packageName=" + packageName);
-                return null;
-            }
-            // This user ID can never be spoofed.
-            final int appUserId = UserHandle.getUserId(userData.mCurClient.mUid);
-            // This user ID may be invalid if "contentUri" embedded an invalid user ID.
-            final int contentUriOwnerUserId = ContentProvider.getUserIdFromUri(contentUri,
-                    userData.mUserId);
-            final Uri contentUriWithoutUserId = ContentProvider.getUriWithoutUserId(contentUri);
-            // Note: InputContentUriTokenHandler.take() checks whether the IME (specified by "uid")
-            // actually has the right to grant a read permission for "contentUriWithoutUserId" that
-            // is claimed to belong to "contentUriOwnerUserId".  For example, specifying random
-            // content URI and/or contentUriOwnerUserId just results in a SecurityException thrown
-            // from InputContentUriTokenHandler.take() and can never be allowed beyond what is
-            // actually allowed to "uid", which is guaranteed to be the IME's one.
-            return new InputContentUriTokenHandler(contentUriWithoutUserId, uid,
-                    packageName, contentUriOwnerUserId, appUserId);
+        final int uid = Binder.getCallingUid();
+        final var bindingController = userData.mBindingController;
+        if (bindingController.getSelectedMethodId() == null) {
+            return null;
         }
+        // We cannot simply distinguish a bad IME that reports an arbitrary package name from
+        // an unfortunate IME whose internal state is already obsolete due to the asynchronous
+        // nature of our system.  Let's compare it with our internal record.
+        final var curPackageName = userData.mCurEditorInfo != null
+                ? userData.mCurEditorInfo.packageName : null;
+        if (!TextUtils.equals(curPackageName, packageName)) {
+            Slog.e(TAG, "Ignoring createInputContentUriTokenLocked mCurEditorInfo.packageName="
+                    + curPackageName + " packageName=" + packageName);
+            return null;
+        }
+        // This user ID can never be spoofed.
+        final int appUserId = UserHandle.getUserId(userData.mCurClient.mUid);
+        // This user ID may be invalid if "contentUri" embedded an invalid user ID.
+        final int contentUriOwnerUserId = ContentProvider.getUserIdFromUri(contentUri,
+                userData.mUserId);
+        final Uri contentUriWithoutUserId = ContentProvider.getUriWithoutUserId(contentUri);
+        // Note: InputContentUriTokenHandler.take() checks whether the IME (specified by "uid")
+        // actually has the right to grant a read permission for "contentUriWithoutUserId" that
+        // is claimed to belong to "contentUriOwnerUserId".  For example, specifying random
+        // content URI and/or contentUriOwnerUserId just results in a SecurityException thrown
+        // from InputContentUriTokenHandler.take() and can never be allowed beyond what is
+        // actually allowed to "uid", which is guaranteed to be the IME's one.
+        return new InputContentUriTokenHandler(contentUriWithoutUserId, uid,
+                packageName, contentUriOwnerUserId, appUserId);
     }
 
     @BinderThread
-    private void reportFullscreenMode(@NonNull IBinder token, boolean fullscreen,
-            @NonNull UserData userData) {
-        synchronized (ImfLock.class) {
-            if (!calledWithValidTokenLocked(token, userData)) {
-                return;
-            }
-            if (userData.mCurClient != null && userData.mCurClient.mClient != null) {
-                userData.mInFullscreenMode = fullscreen;
-                userData.mCurClient.mClient.reportFullscreenMode(fullscreen);
-            }
+    @GuardedBy("ImfLock.class")
+    private void reportFullscreenModeLocked(boolean fullscreen, @NonNull UserData userData) {
+        if (userData.mCurClient != null && userData.mCurClient.mClient != null) {
+            userData.mInFullscreenMode = fullscreen;
+            userData.mCurClient.mClient.reportFullscreenMode(fullscreen);
         }
     }
 
@@ -6235,7 +6075,7 @@
         final Printer p = new PrintWriterPrinter(pw);
 
         synchronized (ImfLock.class) {
-            final int userId = mCurrentUserId;
+            final int userId = mCurrentImeUserId;
             final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
             final var userData = getUserData(userId);
             p.println("Current Input Method Manager state:");
@@ -6267,7 +6107,7 @@
             };
             mClientController.forAllClients(clientControllerDump);
             final var bindingController = userData.mBindingController;
-            p.println("  mCurrentUserId=" + userData.mUserId);
+            p.println("  mCurrentImeUserId=" + userData.mUserId);
             p.println("  mCurMethodId=" + bindingController.getSelectedMethodId());
             client = userData.mCurClient;
             p.println("  mCurClient=" + client + " mCurSeq="
@@ -6285,6 +6125,7 @@
             @SuppressWarnings("GuardedBy") Consumer<UserData> userDataDump =
                     u -> {
                         p.println("    mUserId=" + u.mUserId);
+                        p.println("      unlocked=" + u.mIsUnlockingOrUnlocked.get());
                         p.println("      hasMainConnection="
                                 + u.mBindingController.hasMainConnection());
                         p.println("      isVisibleBound=" + u.mBindingController.isVisibleBound());
@@ -6308,7 +6149,7 @@
                     };
             mUserDataRepository.forAllUserData(userDataDump);
 
-            if (mNewInputMethodSwitcherMenuEnabled) {
+            if (Flags.imeSwitcherRevamp()) {
                 p.println("  menuControllerNew:");
                 mMenuControllerNew.dump(p, "  ");
             } else {
@@ -6359,7 +6200,7 @@
             p.println("No input method client.");
         }
         synchronized (ImfLock.class) {
-            final int userId = mCurrentUserId;
+            final int userId = mCurrentImeUserId;
             final var userData = getUserData(userId);
             if (userData.mImeBindingState.mFocusedWindowClient != null
                     && client != userData.mImeBindingState.mFocusedWindowClient) {
@@ -6590,7 +6431,7 @@
         }
         final int[] userIds;
         synchronized (ImfLock.class) {
-            userIds = InputMethodUtils.resolveUserId(userIdToBeResolved, mCurrentUserId,
+            userIds = InputMethodUtils.resolveUserId(userIdToBeResolved, mCurrentImeUserId,
                     shellCommand.getErrPrintWriter());
         }
         try (PrintWriter pr = shellCommand.getOutPrintWriter()) {
@@ -6636,7 +6477,7 @@
              PrintWriter error = shellCommand.getErrPrintWriter()) {
             synchronized (ImfLock.class) {
                 final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
-                        mCurrentUserId, shellCommand.getErrPrintWriter());
+                        mCurrentImeUserId, shellCommand.getErrPrintWriter());
                 for (int userId : userIds) {
                     if (!userHasDebugPriv(userId, shellCommand)) {
                         continue;
@@ -6731,13 +6572,13 @@
              PrintWriter error = shellCommand.getErrPrintWriter()) {
             synchronized (ImfLock.class) {
                 final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
-                        mCurrentUserId, shellCommand.getErrPrintWriter());
+                        mCurrentImeUserId, shellCommand.getErrPrintWriter());
                 for (int userId : userIds) {
                     if (!userHasDebugPriv(userId, shellCommand)) {
                         continue;
                     }
                     boolean failedToSelectUnknownIme = !switchToInputMethodLocked(imeId,
-                            NOT_A_SUBTYPE_ID, userId);
+                            NOT_A_SUBTYPE_INDEX, userId);
                     if (failedToSelectUnknownIme) {
                         error.print("Unknown input method ");
                         error.print(imeId);
@@ -6751,6 +6592,28 @@
                         out.print(imeId);
                         out.print(" selected for user #");
                         out.println(userId);
+
+                        // Workaround for b/354782333.
+                        final InputMethodSettings settings =
+                                InputMethodSettingsRepository.get(userId);
+                        final var bindingController = getInputMethodBindingController(userId);
+                        final int deviceId = bindingController.getDeviceIdToShowIme();
+                        final String settingsValue;
+                        if (deviceId == DEVICE_ID_DEFAULT) {
+                            settingsValue = settings.getSelectedInputMethod();
+                        } else {
+                            settingsValue = settings.getSelectedDefaultDeviceInputMethod();
+                        }
+                        if (!TextUtils.equals(settingsValue, imeId)) {
+                            Slog.w(TAG, "DEFAULT_INPUT_METHOD=" + settingsValue
+                                    + " is not updated. Fixing it up to " + imeId
+                                    + " See b/354782333.");
+                            if (deviceId == DEVICE_ID_DEFAULT) {
+                                settings.putSelectedInputMethod(imeId);
+                            } else {
+                                settings.putSelectedDefaultDeviceInputMethod(imeId);
+                            }
+                        }
                     }
                     hasFailed |= failedToSelectUnknownIme;
                 }
@@ -6772,7 +6635,7 @@
         synchronized (ImfLock.class) {
             try (PrintWriter out = shellCommand.getOutPrintWriter()) {
                 final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
-                        mCurrentUserId, shellCommand.getErrPrintWriter());
+                        mCurrentImeUserId, shellCommand.getErrPrintWriter());
                 for (int userId : userIds) {
                     if (!userHasDebugPriv(userId, shellCommand)) {
                         continue;
@@ -6937,13 +6800,14 @@
 
     private static final class InputMethodPrivilegedOperationsImpl
             extends IInputMethodPrivilegedOperations.Stub {
+        @NonNull
         private final InputMethodManagerService mImms;
         @NonNull
         private final IBinder mToken;
         @NonNull
         private final UserData mUserData;
 
-        InputMethodPrivilegedOperationsImpl(InputMethodManagerService imms,
+        InputMethodPrivilegedOperationsImpl(@NonNull InputMethodManagerService imms,
                 @NonNull IBinder token, @NonNull UserData userData) {
             mImms = imms;
             mToken = token;
@@ -6952,20 +6816,36 @@
 
         @BinderThread
         @Override
-        public void setImeWindowStatusAsync(int vis, int backDisposition) {
-            mImms.setImeWindowStatus(mToken, vis, backDisposition, mUserData);
+        public void setImeWindowStatusAsync(@ImeWindowVisibility int vis,
+                @BackDispositionMode int backDisposition) {
+            synchronized (ImfLock.class) {
+                if (!calledWithValidTokenLocked(mToken, mUserData)) {
+                    return;
+                }
+                mImms.setImeWindowStatusLocked(vis, backDisposition, mUserData);
+            }
         }
 
         @BinderThread
         @Override
         public void reportStartInputAsync(IBinder startInputToken) {
-            mImms.reportStartInput(mToken, startInputToken, mUserData);
+            synchronized (ImfLock.class) {
+                if (!calledWithValidTokenLocked(mToken, mUserData)) {
+                    return;
+                }
+                mImms.reportStartInputLocked(startInputToken, mUserData);
+            }
         }
 
         @BinderThread
         @Override
         public void setHandwritingSurfaceNotTouchable(boolean notTouchable) {
-            mImms.mHwController.setNotTouchable(notTouchable);
+            synchronized (ImfLock.class) {
+                if (!calledWithValidTokenLocked(mToken, mUserData)) {
+                    return;
+                }
+                mImms.mHwController.setNotTouchable(notTouchable);
+            }
         }
 
         @BinderThread
@@ -6974,8 +6854,14 @@
                 AndroidFuture future /* T=IBinder */) {
             @SuppressWarnings("unchecked") final AndroidFuture<IBinder> typedFuture = future;
             try {
-                typedFuture.complete(mImms.createInputContentUriToken(
-                        mToken, contentUri, packageName, mUserData).asBinder());
+                synchronized (ImfLock.class) {
+                    if (!calledWithValidTokenLocked(mToken, mUserData)) {
+                        typedFuture.complete(null);
+                        return;
+                    }
+                    typedFuture.complete(mImms.createInputContentUriTokenLocked(
+                            contentUri, packageName, mUserData).asBinder());
+                }
             } catch (Throwable e) {
                 typedFuture.completeExceptionally(e);
             }
@@ -6984,7 +6870,12 @@
         @BinderThread
         @Override
         public void reportFullscreenModeAsync(boolean fullscreen) {
-            mImms.reportFullscreenMode(mToken, fullscreen, mUserData);
+            synchronized (ImfLock.class) {
+                if (!calledWithValidTokenLocked(mToken, mUserData)) {
+                    return;
+                }
+                mImms.reportFullscreenModeLocked(fullscreen, mUserData);
+            }
         }
 
         @BinderThread
@@ -6992,8 +6883,14 @@
         public void setInputMethod(String id, AndroidFuture future /* T=Void */) {
             @SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future;
             try {
-                mImms.setInputMethod(mToken, id, mUserData);
-                typedFuture.complete(null);
+                synchronized (ImfLock.class) {
+                    if (!calledWithValidTokenLocked(mToken, mUserData)) {
+                        typedFuture.complete(null);
+                        return;
+                    }
+                    mImms.setInputMethodAndSubtypeLocked(id, null /* subtype */, mUserData);
+                    typedFuture.complete(null);
+                }
             } catch (Throwable e) {
                 typedFuture.completeExceptionally(e);
             }
@@ -7005,8 +6902,14 @@
                 AndroidFuture future /* T=Void */) {
             @SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future;
             try {
-                mImms.setInputMethodAndSubtype(mToken, id, subtype, mUserData);
-                typedFuture.complete(null);
+                synchronized (ImfLock.class) {
+                    if (!calledWithValidTokenLocked(mToken, mUserData)) {
+                        typedFuture.complete(null);
+                        return;
+                    }
+                    mImms.setInputMethodAndSubtypeLocked(id, subtype, mUserData);
+                    typedFuture.complete(null);
+                }
             } catch (Throwable e) {
                 typedFuture.completeExceptionally(e);
             }
@@ -7019,8 +6922,25 @@
                 AndroidFuture future /* T=Void */) {
             @SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future;
             try {
-                mImms.hideMySoftInput(mToken, statsToken, flags, reason, mUserData);
-                typedFuture.complete(null);
+                synchronized (ImfLock.class) {
+                    if (!calledWithValidTokenLocked(mToken, mUserData)) {
+                        ImeTracker.forLogging().onFailed(statsToken,
+                                ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
+                        typedFuture.complete(null);
+                        return;
+                    }
+                    ImeTracker.forLogging().onProgress(statsToken,
+                            ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
+                    Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideMySoftInput");
+                    final long ident = Binder.clearCallingIdentity();
+                    try {
+                        mImms.hideMySoftInputLocked(statsToken, flags, reason, mUserData);
+                        typedFuture.complete(null);
+                    } finally {
+                        Binder.restoreCallingIdentity(ident);
+                        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+                    }
+                }
             } catch (Throwable e) {
                 typedFuture.completeExceptionally(e);
             }
@@ -7033,8 +6953,25 @@
                 AndroidFuture future /* T=Void */) {
             @SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future;
             try {
-                mImms.showMySoftInput(mToken, statsToken, flags, reason, mUserData);
-                typedFuture.complete(null);
+                synchronized (ImfLock.class) {
+                    if (!calledWithValidTokenLocked(mToken, mUserData)) {
+                        ImeTracker.forLogging().onFailed(statsToken,
+                                ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
+                        typedFuture.complete(null);
+                        return;
+                    }
+                    ImeTracker.forLogging().onProgress(statsToken,
+                            ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
+                    Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showMySoftInput");
+                    final long ident = Binder.clearCallingIdentity();
+                    try {
+                        mImms.showMySoftInputLocked(statsToken, flags, reason, mUserData);
+                        typedFuture.complete(null);
+                    } finally {
+                        Binder.restoreCallingIdentity(ident);
+                        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+                    }
+                }
             } catch (Throwable e) {
                 typedFuture.completeExceptionally(e);
             }
@@ -7043,7 +6980,17 @@
         @BinderThread
         @Override
         public void updateStatusIconAsync(String packageName, @DrawableRes int iconId) {
-            mImms.updateStatusIcon(mToken, packageName, iconId, mUserData);
+            synchronized (ImfLock.class) {
+                if (!calledWithValidTokenLocked(mToken, mUserData)) {
+                    return;
+                }
+                final long ident = Binder.clearCallingIdentity();
+                try {
+                    mImms.updateStatusIconLocked(packageName, iconId, mUserData);
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
+                }
+            }
         }
 
         @BinderThread
@@ -7051,7 +6998,13 @@
         public void switchToPreviousInputMethod(AndroidFuture future /* T=Boolean */) {
             @SuppressWarnings("unchecked") final AndroidFuture<Boolean> typedFuture = future;
             try {
-                typedFuture.complete(mImms.switchToPreviousInputMethod(mToken, mUserData));
+                synchronized (ImfLock.class) {
+                    if (!calledWithValidTokenLocked(mToken, mUserData)) {
+                        typedFuture.complete(false);
+                        return;
+                    }
+                    typedFuture.complete(mImms.switchToPreviousInputMethodLocked(mUserData));
+                }
             } catch (Throwable e) {
                 typedFuture.completeExceptionally(e);
             }
@@ -7063,8 +7016,14 @@
                 AndroidFuture future /* T=Boolean */) {
             @SuppressWarnings("unchecked") final AndroidFuture<Boolean> typedFuture = future;
             try {
-                typedFuture.complete(mImms.switchToNextInputMethod(mToken, onlyCurrentIme,
-                        mUserData));
+                synchronized (ImfLock.class) {
+                    if (!calledWithValidTokenLocked(mToken, mUserData)) {
+                        typedFuture.complete(false);
+                        return;
+                    }
+                    typedFuture.complete(mImms.switchToNextInputMethodLocked(onlyCurrentIme,
+                            mUserData));
+                }
             } catch (Throwable e) {
                 typedFuture.completeExceptionally(e);
             }
@@ -7075,8 +7034,14 @@
         public void shouldOfferSwitchingToNextInputMethod(AndroidFuture future /* T=Boolean */) {
             @SuppressWarnings("unchecked") final AndroidFuture<Boolean> typedFuture = future;
             try {
-                typedFuture.complete(mImms.shouldOfferSwitchingToNextInputMethod(mToken,
-                        mUserData));
+                synchronized (ImfLock.class) {
+                    if (!calledWithValidTokenLocked(mToken, mUserData)) {
+                        typedFuture.complete(false);
+                        return;
+                    }
+                    typedFuture.complete(mImms.shouldOfferSwitchingToNextInputMethodLocked(
+                            mUserData));
+                }
             } catch (Throwable e) {
                 typedFuture.completeExceptionally(e);
             }
@@ -7085,39 +7050,68 @@
         @BinderThread
         @Override
         public void onImeSwitchButtonClickFromClient(int displayId) {
-            mImms.onImeSwitchButtonClickFromClient(mToken, displayId, mUserData);
+            synchronized (ImfLock.class) {
+                if (!calledWithValidTokenLocked(mToken, mUserData)) {
+                    return;
+                }
+                mImms.onImeSwitchButtonClickLocked(displayId, mUserData);
+            }
         }
 
         @BinderThread
         @Override
         public void notifyUserActionAsync() {
-            mImms.notifyUserAction(mToken, mUserData);
+            synchronized (ImfLock.class) {
+                if (!calledWithValidTokenLocked(mToken, mUserData)) {
+                    return;
+                }
+                mImms.notifyUserActionLocked(mUserData);
+            }
         }
 
         @BinderThread
         @Override
         public void applyImeVisibilityAsync(IBinder windowToken, boolean setVisible,
                 @NonNull ImeTracker.Token statsToken) {
-            mImms.applyImeVisibility(mToken, windowToken, setVisible, statsToken, mUserData);
+            synchronized (ImfLock.class) {
+                if (!calledWithValidTokenLocked(mToken, mUserData)) {
+                    ImeTracker.forLogging().onFailed(statsToken,
+                            ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
+                    return;
+                }
+                ImeTracker.forLogging().onProgress(statsToken,
+                        ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
+                mImms.applyImeVisibilityLocked(windowToken, setVisible, statsToken, mUserData);
+            }
         }
 
         @BinderThread
         @Override
         public void onStylusHandwritingReady(int requestId, int pid) {
-            mImms.onStylusHandwritingReady(requestId, pid, mUserData);
+            synchronized (ImfLock.class) {
+                if (!calledWithValidTokenLocked(mToken, mUserData)) {
+                    return;
+                }
+                mImms.onStylusHandwritingReadyLocked(requestId, pid, mUserData);
+            }
         }
 
         @BinderThread
         @Override
         public void resetStylusHandwriting(int requestId) {
-            mImms.resetStylusHandwriting(requestId);
+            synchronized (ImfLock.class) {
+                if (!calledWithValidTokenLocked(mToken, mUserData)) {
+                    return;
+                }
+                mImms.resetStylusHandwritingLocked(requestId);
+            }
         }
 
         @BinderThread
         @Override
         public void switchKeyboardLayoutAsync(int direction) {
             synchronized (ImfLock.class) {
-                if (!mImms.calledWithValidTokenLocked(mToken, mUserData)) {
+                if (!calledWithValidTokenLocked(mToken, mUserData)) {
                     return;
                 }
                 final long ident = Binder.clearCallingIdentity();
@@ -7128,5 +7122,25 @@
                 }
             }
         }
+
+        /**
+         * Returns true iff the caller is identified to be the current input method with the token.
+         *
+         * @param token the window token given to the input method when it was started
+         * @param userData {@link UserData} of the calling IME process
+         * @return true if and only if non-null valid token is specified
+         */
+        @GuardedBy("ImfLock.class")
+        private static boolean calledWithValidTokenLocked(@NonNull IBinder token,
+                @NonNull UserData userData) {
+            Objects.requireNonNull(token, "token must not be null");
+            final var bindingController = userData.mBindingController;
+            if (token != bindingController.getCurToken()) {
+                Slog.e(TAG, "Ignoring " + Debug.getCaller() + " due to an invalid token."
+                        + " uid:" + Binder.getCallingUid() + " token:" + token);
+                return false;
+            }
+            return true;
+        }
     }
 }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
index f16a5a0..b5ee068 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
@@ -17,7 +17,7 @@
 package com.android.server.inputmethod;
 
 import static com.android.server.inputmethod.InputMethodManagerService.DEBUG;
-import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_ID;
+import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_INDEX;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -62,7 +62,7 @@
     private View mSwitchingDialogTitleView;
     private List<ImeSubtypeListItem> mImList;
     private InputMethodInfo[] mIms;
-    private int[] mSubtypeIds;
+    private int[] mSubtypeIndices;
 
     private boolean mShowImeWithHardKeyboard;
 
@@ -77,7 +77,7 @@
 
     @GuardedBy("ImfLock.class")
     void showInputMethodMenuLocked(boolean showAuxSubtypes, int displayId,
-            String preferredInputMethodId, int preferredInputMethodSubtypeId,
+            String preferredInputMethodId, int preferredInputMethodSubtypeIndex,
             @NonNull List<ImeSubtypeListItem> imList, @UserIdInt int userId) {
         if (DEBUG) Slog.v(TAG, "Show switching menu. showAuxSubtypes=" + showAuxSubtypes);
 
@@ -85,14 +85,14 @@
 
         hideInputMethodMenuLocked(userId);
 
-        if (preferredInputMethodSubtypeId == NOT_A_SUBTYPE_ID) {
+        if (preferredInputMethodSubtypeIndex == NOT_A_SUBTYPE_INDEX) {
             final InputMethodSubtype currentSubtype =
                     bindingController.getCurrentInputMethodSubtype();
             if (currentSubtype != null) {
                 final String curMethodId = bindingController.getSelectedMethodId();
                 final InputMethodInfo currentImi =
                         InputMethodSettingsRepository.get(userId).getMethodMap().get(curMethodId);
-                preferredInputMethodSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode(
+                preferredInputMethodSubtypeIndex = SubtypeUtils.getSubtypeIndexFromHashCode(
                         currentImi, currentSubtype.hashCode());
             }
         }
@@ -101,7 +101,7 @@
         final int size = imList.size();
         mImList = imList;
         mIms = new InputMethodInfo[size];
-        mSubtypeIds = new int[size];
+        mSubtypeIndices = new int[size];
         // No items are checked by default. When we have a list of explicitly enabled subtypes,
         // the implicit subtype is no longer listed, but if it is still the selected one,
         // no items will be shown as checked.
@@ -109,12 +109,13 @@
         for (int i = 0; i < size; ++i) {
             final ImeSubtypeListItem item = imList.get(i);
             mIms[i] = item.mImi;
-            mSubtypeIds[i] = item.mSubtypeId;
+            mSubtypeIndices[i] = item.mSubtypeIndex;
             if (mIms[i].getId().equals(preferredInputMethodId)) {
-                int subtypeId = mSubtypeIds[i];
-                if ((subtypeId == NOT_A_SUBTYPE_ID)
-                        || (preferredInputMethodSubtypeId == NOT_A_SUBTYPE_ID && subtypeId == 0)
-                        || (subtypeId == preferredInputMethodSubtypeId)) {
+                int subtypeIndex = mSubtypeIndices[i];
+                if ((subtypeIndex == NOT_A_SUBTYPE_INDEX)
+                        || (preferredInputMethodSubtypeIndex == NOT_A_SUBTYPE_INDEX
+                        && subtypeIndex == 0)
+                        || (subtypeIndex == preferredInputMethodSubtypeIndex)) {
                     checkedItem = i;
                 }
             }
@@ -123,7 +124,7 @@
         if (checkedItem == -1) {
             Slog.w(TAG, "Switching menu shown with no item selected"
                     + ", IME id: " + preferredInputMethodId
-                    + ", subtype index: " + preferredInputMethodSubtypeId);
+                    + ", subtype index: " + preferredInputMethodSubtypeIndex);
         }
 
         if (mDialogWindowContext == null) {
@@ -171,19 +172,19 @@
                 com.android.internal.R.layout.input_method_switch_item, imList, checkedItem);
         final DialogInterface.OnClickListener choiceListener = (dialog, which) -> {
             synchronized (ImfLock.class) {
-                if (mIms == null || mIms.length <= which || mSubtypeIds == null
-                        || mSubtypeIds.length <= which) {
+                if (mIms == null || mIms.length <= which || mSubtypeIndices == null
+                        || mSubtypeIndices.length <= which) {
                     return;
                 }
                 final InputMethodInfo im = mIms[which];
-                int subtypeId = mSubtypeIds[which];
+                int subtypeIndex = mSubtypeIndices[which];
                 adapter.mCheckedItem = which;
                 adapter.notifyDataSetChanged();
                 if (im != null) {
-                    if (subtypeId < 0 || subtypeId >= im.getSubtypeCount()) {
-                        subtypeId = NOT_A_SUBTYPE_ID;
+                    if (subtypeIndex < 0 || subtypeIndex >= im.getSubtypeCount()) {
+                        subtypeIndex = NOT_A_SUBTYPE_INDEX;
                     }
-                    mService.setInputMethodLocked(im.getId(), subtypeId, userId);
+                    mService.setInputMethodLocked(im.getId(), subtypeIndex, userId);
                 }
                 hideInputMethodMenuLocked(userId);
             }
@@ -251,7 +252,7 @@
             mDialogBuilder = null;
             mImList = null;
             mIms = null;
-            mSubtypeIds = null;
+            mSubtypeIndices = null;
         }
     }
 
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java
index b72a34d..d9e9e00 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java
@@ -21,7 +21,7 @@
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 
 import static com.android.server.inputmethod.InputMethodManagerService.DEBUG;
-import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_ID;
+import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_INDEX;
 
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -107,7 +107,7 @@
             if (which != selectedIndex) {
                 final var item = items.get(which);
                 InputMethodManagerInternal.get()
-                        .switchToInputMethod(item.mImi.getId(), item.mSubtypeId, userId);
+                        .switchToInputMethod(item.mImi.getId(), item.mSubtypeIndex, userId);
             }
             hide(displayId, userId);
         };
@@ -225,10 +225,10 @@
 
         /**
          * The index of the subtype in the input method's array of subtypes,
-         * or {@link InputMethodUtils#NOT_A_SUBTYPE_ID} if this item doesn't have a subtype.
+         * or {@link InputMethodUtils#NOT_A_SUBTYPE_INDEX} if this item doesn't have a subtype.
          */
-        @IntRange(from = NOT_A_SUBTYPE_ID)
-        private final int mSubtypeId;
+        @IntRange(from = NOT_A_SUBTYPE_INDEX)
+        private final int mSubtypeIndex;
 
         /** Whether this item has a group header (only the first item of each input method). */
         private final boolean mHasHeader;
@@ -240,12 +240,13 @@
         private final boolean mHasDivider;
 
         MenuItem(@NonNull CharSequence imeName, @Nullable CharSequence subtypeName,
-                @NonNull InputMethodInfo imi, @IntRange(from = NOT_A_SUBTYPE_ID) int subtypeId,
-                boolean hasHeader, boolean hasDivider) {
+                @NonNull InputMethodInfo imi,
+                @IntRange(from = NOT_A_SUBTYPE_INDEX) int subtypeIndex, boolean hasHeader,
+                boolean hasDivider) {
             mImeName = imeName;
             mSubtypeName = subtypeName;
             mImi = imi;
-            mSubtypeId = subtypeId;
+            mSubtypeIndex = subtypeIndex;
             mHasHeader = hasHeader;
             mHasDivider = hasDivider;
         }
@@ -255,7 +256,7 @@
             return "MenuItem{"
                     + "mImeName=" + mImeName
                     + " mSubtypeName=" + mSubtypeName
-                    + " mSubtypeId=" + mSubtypeId
+                    + " mSubtypeIndex=" + mSubtypeIndex
                     + " mHasHeader=" + mHasHeader
                     + " mHasDivider=" + mHasDivider
                     + "}";
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
index 0152158..030a5fb 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
@@ -54,7 +54,7 @@
     /**
      * An integer code that represents "no subtype" when a subtype hashcode is used.
      *
-     * <p>Due to historical confusions with {@link InputMethodUtils#NOT_A_SUBTYPE_ID}, we have
+     * <p>Due to historical confusions with {@link InputMethodUtils#NOT_A_SUBTYPE_INDEX}, we have
      * used {@code -1} here. We cannot change this value as it's already saved into secure settings.
      * </p>
      */
@@ -62,7 +62,7 @@
     /**
      * A string code that represents "no subtype" when a subtype hashcode is used.
      *
-     * <p>Due to historical confusions with {@link InputMethodUtils#NOT_A_SUBTYPE_ID}, we have
+     * <p>Due to historical confusions with {@link InputMethodUtils#NOT_A_SUBTYPE_INDEX}, we have
      * used {@code "-1"} here. We cannot change this value as it's already saved into secure
      * settings.</p>
      */
@@ -84,8 +84,8 @@
         // Inputmethod and subtypes are saved in the settings as follows:
         // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
         for (int i = 0; i < ime.second.size(); ++i) {
-            final String subtypeId = ime.second.get(i);
-            builder.append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(subtypeId);
+            final String subtypeHashCode = ime.second.get(i);
+            builder.append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(subtypeHashCode);
         }
     }
 
@@ -350,12 +350,12 @@
         if (lastImi == null) return null;
         try {
             final int lastSubtypeHash = Integer.parseInt(lastIme.second);
-            final int lastSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode(lastImi,
+            final int lastSubtypeIndex = SubtypeUtils.getSubtypeIndexFromHashCode(lastImi,
                     lastSubtypeHash);
-            if (lastSubtypeId < 0 || lastSubtypeId >= lastImi.getSubtypeCount()) {
+            if (lastSubtypeIndex < 0 || lastSubtypeIndex >= lastImi.getSubtypeCount()) {
                 return null;
             }
-            return lastImi.getSubtypeAt(lastSubtypeId);
+            return lastImi.getSubtypeAt(lastSubtypeIndex);
         } catch (NumberFormatException e) {
             return null;
         }
@@ -427,7 +427,7 @@
                     for (int j = 0; j < explicitlyEnabledSubtypes.size(); ++j) {
                         final String s = explicitlyEnabledSubtypes.get(j);
                         if (s.equals(subtypeHashCode)) {
-                            // If both imeId and subtype are enabled, return subtypeId.
+                            // If both imeId and subtype are enabled, return subtypeHashCode.
                             try {
                                 final int hashCode = Integer.parseInt(subtypeHashCode);
                                 // Check whether the subtype is valid or not
@@ -494,11 +494,11 @@
         putString(Settings.Secure.DEFAULT_INPUT_METHOD, imeId);
     }
 
-    void putSelectedSubtype(int subtypeId) {
+    void putSelectedSubtype(int subtypeHashCode) {
         if (DEBUG) {
-            Slog.d(TAG, "putSelectedInputMethodSubtypeStr: " + subtypeId + ", " + mUserId);
+            Slog.d(TAG, "putSelectedInputMethodSubtypeStr: " + subtypeHashCode + ", " + mUserId);
         }
-        putInt(Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, subtypeId);
+        putInt(Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, subtypeHashCode);
     }
 
     @Nullable
@@ -551,13 +551,13 @@
         return mUserId;
     }
 
-    int getSelectedInputMethodSubtypeId(String selectedImiId) {
+    int getSelectedInputMethodSubtypeIndex(String selectedImiId) {
         final InputMethodInfo imi = mMethodMap.get(selectedImiId);
         if (imi == null) {
-            return InputMethodUtils.NOT_A_SUBTYPE_ID;
+            return InputMethodUtils.NOT_A_SUBTYPE_INDEX;
         }
         final int subtypeHashCode = getSelectedInputMethodSubtypeHashCode();
-        return SubtypeUtils.getSubtypeIdFromHashCode(imi, subtypeHashCode);
+        return SubtypeUtils.getSubtypeIndexFromHashCode(imi, subtypeHashCode);
     }
 
     void saveCurrentInputMethodAndSubtypeToHistory(String curMethodId,
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
index 05cc598..202543c 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
@@ -16,6 +16,8 @@
 
 package com.android.server.inputmethod;
 
+import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_INDEX;
+
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -48,7 +50,6 @@
 final class InputMethodSubtypeSwitchingController {
     private static final String TAG = InputMethodSubtypeSwitchingController.class.getSimpleName();
     private static final boolean DEBUG = false;
-    private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID;
 
     @IntDef(prefix = {"MODE_"}, value = {
             MODE_STATIC,
@@ -86,17 +87,21 @@
         public final CharSequence mSubtypeName;
         @NonNull
         public final InputMethodInfo mImi;
-        public final int mSubtypeId;
+        /**
+         * The index of the subtype in the input method's array of subtypes,
+         * or {@link InputMethodUtils#NOT_A_SUBTYPE_INDEX} if this item doesn't have a subtype.
+         */
+        public final int mSubtypeIndex;
         public final boolean mIsSystemLocale;
         public final boolean mIsSystemLanguage;
 
         ImeSubtypeListItem(@NonNull CharSequence imeName, @Nullable CharSequence subtypeName,
-                @NonNull InputMethodInfo imi, int subtypeId, @Nullable String subtypeLocale,
+                @NonNull InputMethodInfo imi, int subtypeIndex, @Nullable String subtypeLocale,
                 @NonNull String systemLocale) {
             mImeName = imeName;
             mSubtypeName = subtypeName;
             mImi = imi;
-            mSubtypeId = subtypeId;
+            mSubtypeIndex = subtypeIndex;
             if (TextUtils.isEmpty(subtypeLocale)) {
                 mIsSystemLocale = false;
                 mIsSystemLanguage = false;
@@ -137,7 +142,7 @@
          *   <li>{@link #mImi} with {@link InputMethodInfo#getId()}</li>
          * </ol>
          * Note: this class has a natural ordering that is inconsistent with
-         * {@link #equals(Object)}. This method doesn't compare {@link #mSubtypeId} but
+         * {@link #equals(Object)}. This method doesn't compare {@link #mSubtypeIndex} but
          * {@link #equals(Object)} does.
          *
          * @param other the object to be compared.
@@ -177,7 +182,7 @@
             return "ImeSubtypeListItem{"
                     + "mImeName=" + mImeName
                     + " mSubtypeName=" + mSubtypeName
-                    + " mSubtypeId=" + mSubtypeId
+                    + " mSubtypeIndex=" + mSubtypeIndex
                     + " mIsSystemLocale=" + mIsSystemLocale
                     + " mIsSystemLanguage=" + mIsSystemLanguage
                     + "}";
@@ -190,7 +195,8 @@
             }
             if (o instanceof ImeSubtypeListItem) {
                 final ImeSubtypeListItem that = (ImeSubtypeListItem) o;
-                return Objects.equals(this.mImi, that.mImi) && this.mSubtypeId == that.mSubtypeId;
+                return Objects.equals(this.mImi, that.mImi)
+                        && this.mSubtypeIndex == that.mSubtypeIndex;
             }
             return false;
         }
@@ -256,7 +262,7 @@
                     }
                 }
             } else {
-                imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_ID, null,
+                imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_INDEX, null,
                         mSystemLocaleStr));
             }
         }
@@ -310,17 +316,17 @@
                     }
                 }
             } else {
-                imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_ID, null,
+                imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_INDEX, null,
                         mSystemLocaleStr));
             }
         }
         return imList;
     }
 
-    private static int calculateSubtypeId(@NonNull InputMethodInfo imi,
+    private static int calculateSubtypeIndex(@NonNull InputMethodInfo imi,
             @Nullable InputMethodSubtype subtype) {
-        return subtype != null ? SubtypeUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode())
-                : NOT_A_SUBTYPE_ID;
+        return subtype != null ? SubtypeUtils.getSubtypeIndexFromHashCode(imi, subtype.hashCode())
+                : NOT_A_SUBTYPE_INDEX;
     }
 
     private static class StaticRotationList {
@@ -341,12 +347,12 @@
          * @return The index in the given list. -1 if not found.
          */
         private int getIndex(@NonNull InputMethodInfo imi, @Nullable InputMethodSubtype subtype) {
-            final int currentSubtypeId = calculateSubtypeId(imi, subtype);
+            final int currentSubtypeIndex = calculateSubtypeIndex(imi, subtype);
             final int numSubtypes = mImeSubtypeList.size();
             for (int i = 0; i < numSubtypes; ++i) {
                 final ImeSubtypeListItem item = mImeSubtypeList.get(i);
                 // Skip until the current IME/subtype is found.
-                if (imi.equals(item.mImi) && item.mSubtypeId == currentSubtypeId) {
+                if (imi.equals(item.mImi) && item.mSubtypeIndex == currentSubtypeIndex) {
                     return i;
                 }
             }
@@ -414,14 +420,14 @@
          */
         private int getUsageRank(@NonNull InputMethodInfo imi,
                 @Nullable InputMethodSubtype subtype) {
-            final int currentSubtypeId = calculateSubtypeId(imi, subtype);
+            final int currentSubtypeIndex = calculateSubtypeIndex(imi, subtype);
             final int numItems = mUsageHistoryOfSubtypeListItemIndex.length;
             for (int usageRank = 0; usageRank < numItems; usageRank++) {
                 final int subtypeListItemIndex = mUsageHistoryOfSubtypeListItemIndex[usageRank];
                 final ImeSubtypeListItem subtypeListItem =
                         mImeSubtypeList.get(subtypeListItemIndex);
                 if (subtypeListItem.mImi.equals(imi)
-                        && subtypeListItem.mSubtypeId == currentSubtypeId) {
+                        && subtypeListItem.mSubtypeIndex == currentSubtypeIndex) {
                     return usageRank;
                 }
             }
@@ -506,6 +512,9 @@
         /**
          * Gets the next input method and subtype from the given ones.
          *
+         * <p>If the given input method and subtype are not found, this returns the most recent
+         * input method and subtype.</p>
+         *
          * @param imi            the input method to find the next value from.
          * @param subtype        the input method subtype to find the next value from, if any.
          * @param onlyCurrentIme whether to consider only subtypes of the current input method.
@@ -517,17 +526,20 @@
         public ImeSubtypeListItem next(@NonNull InputMethodInfo imi,
                 @Nullable InputMethodSubtype subtype, boolean onlyCurrentIme,
                 boolean useRecency, boolean forward) {
-            final int size = mItems.size();
-            if (size <= 1) {
+            if (mItems.isEmpty()) {
                 return null;
             }
             final int index = getIndex(imi, subtype, useRecency);
             if (index < 0) {
-                return null;
+                Slog.w(TAG, "Trying to switch away from input method: " + imi
+                        + " and subtype " + subtype + " which are not in the list,"
+                        + " falling back to most recent item in list.");
+                return mItems.get(mRecencyMap[0]);
             }
 
             final int incrementSign = (forward ? 1 : -1);
 
+            final int size = mItems.size();
             for (int i = 1; i < size; i++) {
                 final int nextIndex = (index + i * incrementSign + size) % size;
                 final int mappedIndex = useRecency ? mRecencyMap[nextIndex] : nextIndex;
@@ -548,7 +560,7 @@
          */
         public boolean setMostRecent(@NonNull InputMethodInfo imi,
                 @Nullable InputMethodSubtype subtype) {
-            if (mItems.size() <= 1) {
+            if (mItems.isEmpty()) {
                 return false;
             }
 
@@ -575,11 +587,11 @@
         @IntRange(from = -1)
         private int getIndex(@NonNull InputMethodInfo imi, @Nullable InputMethodSubtype subtype,
                 boolean useRecency) {
-            final int subtypeIndex = calculateSubtypeId(imi, subtype);
+            final int subtypeIndex = calculateSubtypeIndex(imi, subtype);
             for (int i = 0; i < mItems.size(); i++) {
                 final int mappedIndex = useRecency ? mRecencyMap[i] : i;
                 final var item = mItems.get(mappedIndex);
-                if (item.mImi.equals(imi) && item.mSubtypeId == subtypeIndex) {
+                if (item.mImi.equals(imi) && item.mSubtypeIndex == subtypeIndex) {
                     return i;
                 }
             }
@@ -591,258 +603,80 @@
             pw.println(prefix + "Static order:");
             for (int i = 0; i < mItems.size(); ++i) {
                 final var item = mItems.get(i);
-                pw.println(prefix + "i=" + i + " item=" + item);
+                pw.println(prefix + "  i=" + i + " item=" + item);
             }
             pw.println(prefix + "Recency order:");
             for (int i = 0; i < mRecencyMap.length; ++i) {
                 final int index = mRecencyMap[i];
                 final var item = mItems.get(index);
-                pw.println(prefix + "i=" + i + " item=" + item);
-            }
-        }
-    }
-
-    @VisibleForTesting
-    public static class ControllerImpl {
-
-        @NonNull
-        private final DynamicRotationList mSwitchingAwareRotationList;
-        @NonNull
-        private final StaticRotationList mSwitchingUnawareRotationList;
-        /** List of input methods and subtypes. */
-        @Nullable
-        private final RotationList mRotationList;
-        /** List of input methods and subtypes suitable for hardware keyboards. */
-        @Nullable
-        private final RotationList mHardwareRotationList;
-
-        /**
-         * Whether there was a user action since the last input method and subtype switch.
-         * Used to determine the switching behaviour for {@link #MODE_AUTO}.
-         */
-        private boolean mUserActionSinceSwitch;
-
-        @NonNull
-        public static ControllerImpl createFrom(@Nullable ControllerImpl currentInstance,
-                @NonNull List<ImeSubtypeListItem> sortedEnabledItems,
-                @NonNull List<ImeSubtypeListItem> hardwareKeyboardItems) {
-            final var switchingAwareImeSubtypes = filterImeSubtypeList(sortedEnabledItems,
-                    true /* supportsSwitchingToNextInputMethod */);
-            final var switchingUnawareImeSubtypes = filterImeSubtypeList(sortedEnabledItems,
-                    false /* supportsSwitchingToNextInputMethod */);
-
-            final DynamicRotationList switchingAwareRotationList;
-            if (currentInstance != null && Objects.equals(
-                    currentInstance.mSwitchingAwareRotationList.mImeSubtypeList,
-                    switchingAwareImeSubtypes)) {
-                // Can reuse the current instance.
-                switchingAwareRotationList = currentInstance.mSwitchingAwareRotationList;
-            } else {
-                switchingAwareRotationList = new DynamicRotationList(switchingAwareImeSubtypes);
-            }
-
-            final StaticRotationList switchingUnawareRotationList;
-            if (currentInstance != null && Objects.equals(
-                    currentInstance.mSwitchingUnawareRotationList.mImeSubtypeList,
-                    switchingUnawareImeSubtypes)) {
-                // Can reuse the current instance.
-                switchingUnawareRotationList = currentInstance.mSwitchingUnawareRotationList;
-            } else {
-                switchingUnawareRotationList = new StaticRotationList(switchingUnawareImeSubtypes);
-            }
-
-            final RotationList rotationList;
-            if (!Flags.imeSwitcherRevamp()) {
-                rotationList = null;
-            } else if (currentInstance != null && currentInstance.mRotationList != null
-                    && Objects.equals(
-                            currentInstance.mRotationList.mItems, sortedEnabledItems)) {
-                // Can reuse the current instance.
-                rotationList = currentInstance.mRotationList;
-            } else {
-                rotationList = new RotationList(sortedEnabledItems);
-            }
-
-            final RotationList hardwareRotationList;
-            if (!Flags.imeSwitcherRevamp()) {
-                hardwareRotationList = null;
-            } else if (currentInstance != null && currentInstance.mHardwareRotationList != null
-                    && Objects.equals(
-                            currentInstance.mHardwareRotationList.mItems, hardwareKeyboardItems)) {
-                // Can reuse the current instance.
-                hardwareRotationList = currentInstance.mHardwareRotationList;
-            } else {
-                hardwareRotationList = new RotationList(hardwareKeyboardItems);
-            }
-
-            return new ControllerImpl(switchingAwareRotationList, switchingUnawareRotationList,
-                    rotationList, hardwareRotationList);
-        }
-
-        private ControllerImpl(@NonNull DynamicRotationList switchingAwareRotationList,
-                @NonNull StaticRotationList switchingUnawareRotationList,
-                @Nullable RotationList rotationList,
-                @Nullable RotationList hardwareRotationList) {
-            mSwitchingAwareRotationList = switchingAwareRotationList;
-            mSwitchingUnawareRotationList = switchingUnawareRotationList;
-            mRotationList = rotationList;
-            mHardwareRotationList = hardwareRotationList;
-        }
-
-        @Nullable
-        public ImeSubtypeListItem getNextInputMethod(boolean onlyCurrentIme,
-                @Nullable InputMethodInfo imi, @Nullable InputMethodSubtype subtype,
-                @SwitchMode int mode, boolean forward) {
-            if (imi == null) {
-                return null;
-            }
-            if (Flags.imeSwitcherRevamp() && mRotationList != null) {
-                return mRotationList.next(imi, subtype, onlyCurrentIme,
-                        isRecency(mode, forward), forward);
-            } else if (imi.supportsSwitchingToNextInputMethod()) {
-                return mSwitchingAwareRotationList.getNextInputMethodLocked(onlyCurrentIme, imi,
-                        subtype);
-            } else {
-                return mSwitchingUnawareRotationList.getNextInputMethodLocked(onlyCurrentIme, imi,
-                        subtype);
-            }
-        }
-
-        @Nullable
-        public ImeSubtypeListItem getNextInputMethodForHardware(boolean onlyCurrentIme,
-                @NonNull InputMethodInfo imi, @Nullable InputMethodSubtype subtype,
-                @SwitchMode int mode, boolean forward) {
-            if (Flags.imeSwitcherRevamp() && mHardwareRotationList != null) {
-                return mHardwareRotationList.next(imi, subtype, onlyCurrentIme,
-                        isRecency(mode, forward), forward);
-            }
-            return null;
-        }
-
-        /**
-         * Called when the user took an action that should update the recency of the current
-         * input method and subtype in the switching list.
-         *
-         * @param imi     the currently selected input method.
-         * @param subtype the currently selected input method subtype, if any.
-         * @return {@code true} if the recency was updated, otherwise {@code false}.
-         * @see android.inputmethodservice.InputMethodServiceInternal#notifyUserActionIfNecessary()
-         */
-        public boolean onUserActionLocked(@NonNull InputMethodInfo imi,
-                @Nullable InputMethodSubtype subtype) {
-            boolean recencyUpdated = false;
-            if (Flags.imeSwitcherRevamp()) {
-                if (mRotationList != null) {
-                    recencyUpdated |= mRotationList.setMostRecent(imi, subtype);
-                }
-                if (mHardwareRotationList != null) {
-                    recencyUpdated |= mHardwareRotationList.setMostRecent(imi, subtype);
-                }
-                if (recencyUpdated) {
-                    mUserActionSinceSwitch = true;
-                }
-            } else if (imi.supportsSwitchingToNextInputMethod()) {
-                mSwitchingAwareRotationList.onUserAction(imi, subtype);
-            }
-            return recencyUpdated;
-        }
-
-        /** Called when the input method and subtype was changed. */
-        public void onInputMethodSubtypeChanged() {
-            mUserActionSinceSwitch = false;
-        }
-
-        /**
-         * Whether the given mode and direction result in recency or static order.
-         *
-         * <p>{@link #MODE_AUTO} resolves to the recency order for the first forwards switch
-         * after an {@link #onUserActionLocked user action}, and otherwise to the static order.</p>
-         *
-         * @param mode    the switching mode.
-         * @param forward the switching direction.
-         * @return {@code true} for the recency order, otherwise {@code false}.
-         */
-        private boolean isRecency(@SwitchMode int mode, boolean forward) {
-            if (mode == MODE_AUTO && mUserActionSinceSwitch && forward) {
-                return true;
-            } else {
-                return mode == MODE_RECENT;
-            }
-        }
-
-        @NonNull
-        private static List<ImeSubtypeListItem> filterImeSubtypeList(
-                @NonNull List<ImeSubtypeListItem> items,
-                boolean supportsSwitchingToNextInputMethod) {
-            final ArrayList<ImeSubtypeListItem> result = new ArrayList<>();
-            final int numItems = items.size();
-            for (int i = 0; i < numItems; i++) {
-                final ImeSubtypeListItem item = items.get(i);
-                if (item.mImi.supportsSwitchingToNextInputMethod()
-                        == supportsSwitchingToNextInputMethod) {
-                    result.add(item);
-                }
-            }
-            return result;
-        }
-
-        protected void dump(@NonNull Printer pw, @NonNull String prefix) {
-            pw.println(prefix + "mSwitchingAwareRotationList:");
-            mSwitchingAwareRotationList.dump(pw, prefix + "  ");
-            pw.println(prefix + "mSwitchingUnawareRotationList:");
-            mSwitchingUnawareRotationList.dump(pw, prefix + "  ");
-            if (Flags.imeSwitcherRevamp()) {
-                if (mRotationList != null) {
-                    pw.println(prefix + "mRotationList:");
-                    mRotationList.dump(pw, prefix + "  ");
-                }
-                if (mHardwareRotationList != null) {
-                    pw.println(prefix + "mHardwareRotationList:");
-                    mHardwareRotationList.dump(pw, prefix + "  ");
-                }
-                pw.println("User action since last switch: " + mUserActionSinceSwitch);
+                pw.println(prefix + "  i=" + i + " item=" + item);
             }
         }
     }
 
     @NonNull
-    private ControllerImpl mController;
-
-    InputMethodSubtypeSwitchingController() {
-        mController = ControllerImpl.createFrom(null, Collections.emptyList(),
-                Collections.emptyList());
-    }
+    private DynamicRotationList mSwitchingAwareRotationList =
+            new DynamicRotationList(Collections.emptyList());
+    @NonNull
+    private StaticRotationList mSwitchingUnawareRotationList =
+            new StaticRotationList(Collections.emptyList());
+    /** List of input methods and subtypes. */
+    @NonNull
+    private RotationList mRotationList = new RotationList(Collections.emptyList());
+    /** List of input methods and subtypes suitable for hardware keyboards. */
+    @NonNull
+    private RotationList mHardwareRotationList = new RotationList(Collections.emptyList());
 
     /**
-     * Called when the user took an action that should update the recency of the current
-     * input method and subtype in the switching list.
-     *
-     * @param imi     the currently selected input method.
-     * @param subtype the currently selected input method subtype, if any.
-     * @see android.inputmethodservice.InputMethodServiceInternal#notifyUserActionIfNecessary()
+     * Whether there was a user action since the last input method and subtype switch.
+     * Used to determine the switching behaviour for {@link #MODE_AUTO}.
      */
-    public void onUserActionLocked(@NonNull InputMethodInfo imi,
-            @Nullable InputMethodSubtype subtype) {
-        mController.onUserActionLocked(imi, subtype);
-    }
+    private boolean mUserActionSinceSwitch;
 
-    /** Called when the input method and subtype was changed. */
-    public void onInputMethodSubtypeChanged() {
-        mController.onInputMethodSubtypeChanged();
-    }
+    /**
+     * Updates the list of input methods and subtypes used for switching. If the given items are
+     * equal to the existing ones (regardless of recency order), the update is skipped and the
+     * current recency order is kept. Otherwise, the recency order is reset.
+     *
+     * @param sortedEnabledItems    the sorted list of enabled input methods and subtypes.
+     * @param hardwareKeyboardItems the unsorted list of enabled input method and subtypes
+     *                              suitable for hardware keyboards.
+     */
+    @VisibleForTesting
+    void update(@NonNull List<ImeSubtypeListItem> sortedEnabledItems,
+            @NonNull List<ImeSubtypeListItem> hardwareKeyboardItems) {
+        final var switchingAwareImeSubtypes = filterImeSubtypeList(sortedEnabledItems,
+                true /* supportsSwitchingToNextInputMethod */);
+        final var switchingUnawareImeSubtypes = filterImeSubtypeList(sortedEnabledItems,
+                false /* supportsSwitchingToNextInputMethod */);
 
-    public void resetCircularListLocked(@NonNull Context context,
-            @NonNull InputMethodSettings settings) {
-        mController = ControllerImpl.createFrom(mController,
-                getSortedInputMethodAndSubtypeList(
-                        false /* includeAuxiliarySubtypes */, false /* isScreenLocked */,
-                        false /* forImeMenu */, context, settings),
-                getInputMethodAndSubtypeListForHardwareKeyboard(context, settings));
+        if (!Objects.equals(mSwitchingAwareRotationList.mImeSubtypeList,
+                switchingAwareImeSubtypes)) {
+            mSwitchingAwareRotationList = new DynamicRotationList(switchingAwareImeSubtypes);
+        }
+
+        if (!Objects.equals(mSwitchingUnawareRotationList.mImeSubtypeList,
+                switchingUnawareImeSubtypes)) {
+            mSwitchingUnawareRotationList = new StaticRotationList(switchingUnawareImeSubtypes);
+        }
+
+        if (Flags.imeSwitcherRevamp()
+                && !Objects.equals(mRotationList.mItems, sortedEnabledItems)) {
+            mRotationList = new RotationList(sortedEnabledItems);
+        }
+
+        if (Flags.imeSwitcherRevamp()
+                && !Objects.equals(mHardwareRotationList.mItems, hardwareKeyboardItems)) {
+            mHardwareRotationList = new RotationList(hardwareKeyboardItems);
+        }
     }
 
     /**
      * Gets the next input method and subtype, starting from the given ones, in the given direction.
      *
+     * <p>If the given input method and subtype are not found, this returns the most recent
+     * input method and subtype.</p>
+     *
      * @param onlyCurrentIme whether to consider only subtypes of the current input method.
      * @param imi            the input method to find the next value from.
      * @param subtype        the input method subtype to find the next value from, if any.
@@ -854,13 +688,28 @@
     public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme,
             @Nullable InputMethodInfo imi, @Nullable InputMethodSubtype subtype,
             @SwitchMode int mode, boolean forward) {
-        return mController.getNextInputMethod(onlyCurrentIme, imi, subtype, mode, forward);
+        if (imi == null) {
+            return null;
+        }
+        if (Flags.imeSwitcherRevamp()) {
+            return mRotationList.next(imi, subtype, onlyCurrentIme,
+                    isRecency(mode, forward), forward);
+        } else if (imi.supportsSwitchingToNextInputMethod()) {
+            return mSwitchingAwareRotationList.getNextInputMethodLocked(onlyCurrentIme, imi,
+                    subtype);
+        } else {
+            return mSwitchingUnawareRotationList.getNextInputMethodLocked(onlyCurrentIme, imi,
+                    subtype);
+        }
     }
 
     /**
      * Gets the next input method and subtype suitable for hardware keyboards, starting from the
      * given ones, in the given direction.
      *
+     * <p>If the given input method and subtype are not found, this returns the most recent
+     * input method and subtype.</p>
+     *
      * @param onlyCurrentIme whether to consider only subtypes of the current input method.
      * @param imi            the input method to find the next value from.
      * @param subtype        the input method subtype to find the next value from, if any.
@@ -872,11 +721,98 @@
     public ImeSubtypeListItem getNextInputMethodForHardware(boolean onlyCurrentIme,
             @NonNull InputMethodInfo imi, @Nullable InputMethodSubtype subtype,
             @SwitchMode int mode, boolean forward) {
-        return mController.getNextInputMethodForHardware(onlyCurrentIme, imi, subtype, mode,
-                forward);
+        if (Flags.imeSwitcherRevamp()) {
+            return mHardwareRotationList.next(imi, subtype, onlyCurrentIme,
+                    isRecency(mode, forward), forward);
+        }
+        return null;
     }
 
-    public void dump(@NonNull Printer pw, @NonNull String prefix) {
-        mController.dump(pw, prefix);
+    /**
+     * Called when the user took an action that should update the recency of the current
+     * input method and subtype in the switching list.
+     *
+     * @param imi     the currently selected input method.
+     * @param subtype the currently selected input method subtype, if any.
+     * @return {@code true} if the recency was updated, otherwise {@code false}.
+     * @see android.inputmethodservice.InputMethodServiceInternal#notifyUserActionIfNecessary()
+     */
+    public boolean onUserActionLocked(@NonNull InputMethodInfo imi,
+            @Nullable InputMethodSubtype subtype) {
+        boolean recencyUpdated = false;
+        if (Flags.imeSwitcherRevamp()) {
+            recencyUpdated |= mRotationList.setMostRecent(imi, subtype);
+            recencyUpdated |= mHardwareRotationList.setMostRecent(imi, subtype);
+            if (recencyUpdated) {
+                mUserActionSinceSwitch = true;
+            }
+        } else if (imi.supportsSwitchingToNextInputMethod()) {
+            mSwitchingAwareRotationList.onUserAction(imi, subtype);
+        }
+        return recencyUpdated;
+    }
+
+    /** Called when the input method and subtype was changed. */
+    public void onInputMethodSubtypeChanged() {
+        mUserActionSinceSwitch = false;
+    }
+
+    /**
+     * Whether the given mode and direction result in recency or static order.
+     *
+     * <p>{@link #MODE_AUTO} resolves to the recency order for the first forwards switch
+     * after an {@link #onUserActionLocked user action}, and otherwise to the static order.</p>
+     *
+     * @param mode    the switching mode.
+     * @param forward the switching direction.
+     * @return {@code true} for the recency order, otherwise {@code false}.
+     */
+    private boolean isRecency(@SwitchMode int mode, boolean forward) {
+        if (mode == MODE_AUTO && mUserActionSinceSwitch && forward) {
+            return true;
+        } else {
+            return mode == MODE_RECENT;
+        }
+    }
+
+    @NonNull
+    private static List<ImeSubtypeListItem> filterImeSubtypeList(
+            @NonNull List<ImeSubtypeListItem> items,
+            boolean supportsSwitchingToNextInputMethod) {
+        final ArrayList<ImeSubtypeListItem> result = new ArrayList<>();
+        final int numItems = items.size();
+        for (int i = 0; i < numItems; i++) {
+            final ImeSubtypeListItem item = items.get(i);
+            if (item.mImi.supportsSwitchingToNextInputMethod()
+                    == supportsSwitchingToNextInputMethod) {
+                result.add(item);
+            }
+        }
+        return result;
+    }
+
+    void dump(@NonNull Printer pw, @NonNull String prefix) {
+        pw.println(prefix + "mSwitchingAwareRotationList:");
+        mSwitchingAwareRotationList.dump(pw, prefix + "  ");
+        pw.println(prefix + "mSwitchingUnawareRotationList:");
+        mSwitchingUnawareRotationList.dump(pw, prefix + "  ");
+        if (Flags.imeSwitcherRevamp()) {
+            pw.println(prefix + "mRotationList:");
+            mRotationList.dump(pw, prefix + "  ");
+            pw.println(prefix + "mHardwareRotationList:");
+            mHardwareRotationList.dump(pw, prefix + "  ");
+            pw.println(prefix + "User action since last switch: " + mUserActionSinceSwitch);
+        }
+    }
+
+    InputMethodSubtypeSwitchingController() {
+    }
+
+    public void resetCircularListLocked(@NonNull Context context,
+            @NonNull InputMethodSettings settings) {
+        update(getSortedInputMethodAndSubtypeList(
+                        false /* includeAuxiliarySubtypes */, false /* isScreenLocked */,
+                        false /* forImeMenu */, context, settings),
+                getInputMethodAndSubtypeListForHardwareKeyboard(context, settings));
     }
 }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index 361cdbb..da35fe7 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -59,7 +59,7 @@
  */
 final class InputMethodUtils {
     public static final boolean DEBUG = false;
-    static final int NOT_A_SUBTYPE_ID = -1;
+    static final int NOT_A_SUBTYPE_INDEX = -1;
     private static final String TAG = "InputMethodUtils";
 
     // The string for enabled input method is saved as follows:
diff --git a/services/core/java/com/android/server/inputmethod/SubtypeUtils.java b/services/core/java/com/android/server/inputmethod/SubtypeUtils.java
index 1b4c0d6..f615b52 100644
--- a/services/core/java/com/android/server/inputmethod/SubtypeUtils.java
+++ b/services/core/java/com/android/server/inputmethod/SubtypeUtils.java
@@ -16,6 +16,9 @@
 
 package com.android.server.inputmethod;
 
+import static com.android.server.inputmethod.InputMethodSettings.INVALID_SUBTYPE_HASHCODE;
+import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_INDEX;
+
 import android.annotation.AnyThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -48,7 +51,6 @@
     static final String SUBTYPE_MODE_ANY = null;
     static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
 
-    static final int NOT_A_SUBTYPE_ID = -1;
     private static final String TAG_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE =
             "EnabledWhenDefaultIsNotAsciiCapable";
 
@@ -103,10 +105,19 @@
     }
 
     static boolean isValidSubtypeHashCode(InputMethodInfo imi, int subtypeHashCode) {
-        return getSubtypeIdFromHashCode(imi, subtypeHashCode) != NOT_A_SUBTYPE_ID;
+        return getSubtypeIndexFromHashCode(imi, subtypeHashCode) != NOT_A_SUBTYPE_INDEX;
     }
 
-    static int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) {
+    /**
+     * Returns the index to be specified to {@link InputMethodInfo#getSubtypeAt(int)}.
+     *
+     * @param imi             {@link InputMethodInfo} to be queried about
+     * @param subtypeHashCode {@link InputMethodSubtype#hashCode()} to be queried about
+     *
+     * @return The index to be specified to {@link InputMethodInfo#getSubtypeAt(int)}.
+     *         {@link InputMethodUtils#NOT_A_SUBTYPE_INDEX} if not found
+     */
+    static int getSubtypeIndexFromHashCode(InputMethodInfo imi, int subtypeHashCode) {
         if (imi != null) {
             final int subtypeCount = imi.getSubtypeCount();
             for (int i = 0; i < subtypeCount; ++i) {
@@ -116,7 +127,7 @@
                 }
             }
         }
-        return NOT_A_SUBTYPE_ID;
+        return NOT_A_SUBTYPE_INDEX;
     }
 
     private static final LocaleUtils.LocaleExtractor<InputMethodSubtype> sSubtypeToLocale =
@@ -242,7 +253,7 @@
      *                                    most applicable subtype, it will return the first subtype
      *                                    matched with mode
      *
-     * @return the most applicable subtypeId
+     * @return the most applicable {@link InputMethodSubtype}
      */
     static InputMethodSubtype findLastResortApplicableSubtype(
             List<InputMethodSubtype> subtypes, String mode, @NonNull String locale,
@@ -310,15 +321,15 @@
             @Nullable InputMethodSubtype currentSubtype) {
         final int userId = settings.getUserId();
         final int selectedSubtypeHashCode = SecureSettingsWrapper.getInt(
-                Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, NOT_A_SUBTYPE_ID, userId);
-        if (selectedSubtypeHashCode != NOT_A_SUBTYPE_ID && currentSubtype != null
+                Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, INVALID_SUBTYPE_HASHCODE, userId);
+        if (selectedSubtypeHashCode != INVALID_SUBTYPE_HASHCODE && currentSubtype != null
                 && isValidSubtypeHashCode(imi, currentSubtype.hashCode())) {
             return currentSubtype;
         }
 
-        final int subtypeId = settings.getSelectedInputMethodSubtypeId(imi.getId());
-        if (subtypeId != NOT_A_SUBTYPE_ID) {
-            return imi.getSubtypeAt(subtypeId);
+        final int subtypeIndex = settings.getSelectedInputMethodSubtypeIndex(imi.getId());
+        if (subtypeIndex != NOT_A_SUBTYPE_INDEX) {
+            return imi.getSubtypeAt(subtypeIndex);
         }
 
         // If there are no selected subtypes, the framework will try to find the most applicable
diff --git a/services/core/java/com/android/server/inputmethod/UserData.java b/services/core/java/com/android/server/inputmethod/UserData.java
index 28394c6a..96da17e 100644
--- a/services/core/java/com/android/server/inputmethod/UserData.java
+++ b/services/core/java/com/android/server/inputmethod/UserData.java
@@ -19,14 +19,17 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.util.Pair;
 import android.util.SparseArray;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.ImeTracker;
+import android.view.inputmethod.InputMethodSubtype;
 import android.window.ImeOnBackInvokedDispatcher;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
 import com.android.internal.inputmethod.IRemoteInputConnection;
+import com.android.internal.inputmethod.InputMethodSubtypeHandle;
 
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -149,11 +152,30 @@
     String mLastEnabledInputMethodsStr = "";
 
     /**
+     * A temporary solution to Bug 356879517, where we need to emulate the previous single-user mode
+     * behavior for KeyboardLayoutManager.
+     *
+     * <p>TODO(b/357663774): Remove this workaround</p>
+     */
+    @GuardedBy("ImfLock.class")
+    @Nullable
+    Pair<InputMethodSubtypeHandle, InputMethodSubtype> mSubtypeForKeyboardLayoutMapping;
+
+    /**
      * {@code true} when the IME is responsible for drawing the navigation bar and its buttons.
      */
     @NonNull
     final AtomicBoolean mImeDrawsNavBar = new AtomicBoolean();
 
+
+    /**
+     * {@code true} if the user storage is considered to be unlocked.
+     *
+     * @see com.android.server.pm.UserManagerInternal#isUserUnlockingOrUnlocked(int)
+     */
+    @NonNull
+    final AtomicBoolean mIsUnlockingOrUnlocked = new AtomicBoolean(false);
+
     /**
      * Intended to be instantiated only from this file.
      */
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubTestModeManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubTestModeManager.java
index f2714db..2bb3be6 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubTestModeManager.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubTestModeManager.java
@@ -17,10 +17,12 @@
 package com.android.server.location.contexthub;
 
 import android.chre.flags.Flags;
+import android.hardware.location.ContextHubTransaction;
 import android.hardware.location.NanoAppMessage;
 import android.util.Log;
 
-import java.util.Random;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.Callable;
 
 /**
  * A class to manage behaviors during test mode. This is used for testing.
@@ -29,32 +31,31 @@
 public class ContextHubTestModeManager {
     private static final String TAG = "ContextHubTestModeManager";
 
-    /** Probability of duplicating a message. */
-    private static final double MESSAGE_DROP_PROBABILITY = 0.05;
-
-    /** Probability of duplicating a message. */
-    private static final double MESSAGE_DUPLICATION_PROBABILITY = 0.05;
+    private static final int DROP_MESSAGE_TO_HOST_EVENT = 0;
+    private static final int DROP_MESSAGE_TO_CONTEXT_HUB_EVENT = 1;
+    private static final int DUPLICATE_MESSAGE_TO_HOST_EVENT = 2;
+    private static final int DUPLICATE_MESSAGE_TO_CONTEXT_HUB_EVENT = 3;
+    private static final int NUMBER_OF_EVENTS = 4;
 
     /** The number of total messages to send when the duplication event happens. */
     private static final int NUM_MESSAGES_TO_DUPLICATE = 3;
 
-    /**
-     * The seed for the random number generator. This is used to make the
-     * test more deterministic.
-     */
-    private static final long SEED = 0xDEADBEEF;
-
-    private final Random mRandom = new Random(SEED);
+    /** The counter to track the number of interactions with the test mode manager. */
+    private final AtomicLong mCounter = new AtomicLong(0);
 
     /**
      * @return whether the message was handled
      * @see ContextHubServiceCallback#handleNanoappMessage
      */
     public boolean handleNanoappMessage(Runnable handleMessage, NanoAppMessage message) {
+        if (!message.isReliable()) {
+            return false;
+        }
+
+        long counterValue = mCounter.getAndIncrement();
         if (Flags.reliableMessageDuplicateDetectionService()
-                && message.isReliable()
-                && mRandom.nextDouble() < MESSAGE_DUPLICATION_PROBABILITY) {
-            Log.i(TAG, "[TEST MODE] Duplicating message ("
+                && counterValue % NUMBER_OF_EVENTS == DUPLICATE_MESSAGE_TO_HOST_EVENT) {
+            Log.i(TAG, "[TEST MODE] Duplicating message to host ("
                     + NUM_MESSAGES_TO_DUPLICATE
                     + " sends) with message sequence number: "
                     + message.getMessageSequenceNumber());
@@ -63,6 +64,14 @@
             }
             return true;
         }
+
+        if (counterValue % NUMBER_OF_EVENTS == DROP_MESSAGE_TO_HOST_EVENT) {
+            Log.i(TAG, "[TEST MODE] Dropping message to host with "
+                    + "message sequence number: "
+                    + message.getMessageSequenceNumber());
+            return true;
+        }
+
         return false;
     }
 
@@ -70,14 +79,39 @@
      * @return whether the message was handled
      * @see IContextHubWrapper#sendMessageToContextHub
      */
-    public boolean sendMessageToContextHub(NanoAppMessage message) {
+    public boolean sendMessageToContextHub(Callable<Integer> sendMessage, NanoAppMessage message) {
+        if (!message.isReliable()) {
+            return false;
+        }
+
+        long counterValue = mCounter.getAndIncrement();
+        if (counterValue % NUMBER_OF_EVENTS == DUPLICATE_MESSAGE_TO_CONTEXT_HUB_EVENT) {
+            Log.i(TAG, "[TEST MODE] Duplicating message to the Context Hub ("
+                    + NUM_MESSAGES_TO_DUPLICATE
+                    + " sends) with message sequence number: "
+                    + message.getMessageSequenceNumber());
+            for (int i = 0; i < NUM_MESSAGES_TO_DUPLICATE; ++i) {
+                try {
+                    int result = sendMessage.call();
+                    if (result != ContextHubTransaction.RESULT_SUCCESS) {
+                        Log.e(TAG, "sendMessage returned an error: " + result);
+                    }
+                } catch (Exception e) {
+                    Log.e(TAG, "Exception in sendMessageToContextHub: "
+                            + e.getMessage());
+                }
+            }
+            return true;
+        }
+
         if (Flags.reliableMessageRetrySupportService()
-                && message.isReliable()
-                && mRandom.nextDouble() < MESSAGE_DROP_PROBABILITY) {
-            Log.i(TAG, "[TEST MODE] Dropping message with message sequence number: "
+                && counterValue % NUMBER_OF_EVENTS == DROP_MESSAGE_TO_CONTEXT_HUB_EVENT) {
+            Log.i(TAG, "[TEST MODE] Dropping message to the Context Hub with "
+                    + "message sequence number: "
                     + message.getMessageSequenceNumber());
             return true;
         }
+
         return false;
     }
 }
diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
index 4fc3d87..a8ad418 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -53,6 +53,7 @@
 import java.util.NoSuchElementException;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.Callable;
 
 /**
  * @hide
@@ -659,32 +660,40 @@
 
         @ContextHubTransaction.Result
         public int sendMessageToContextHub(short hostEndpointId, int contextHubId,
-                NanoAppMessage message) throws RemoteException {
+                NanoAppMessage message) {
             android.hardware.contexthub.IContextHub hub = getHub();
             if (hub == null) {
                 return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
             }
 
-            try {
-                var msg = ContextHubServiceUtil.createAidlContextHubMessage(
-                        hostEndpointId, message);
-
-                // Only process the message normally if not using test mode manager or if
-                // the test mode manager call returned false as this indicates it did not
-                // process the message.
-                boolean useTestModeManager = Flags.reliableMessageImplementation()
-                        && Flags.reliableMessageTestModeBehavior()
-                        && mIsTestModeEnabled.get();
-                if (!useTestModeManager || !mTestModeManager.sendMessageToContextHub(message)) {
+            Callable<Integer> sendMessage = () -> {
+                try {
+                    var msg = ContextHubServiceUtil.createAidlContextHubMessage(
+                            hostEndpointId, message);
                     hub.sendMessageToHub(contextHubId, msg);
+                    return ContextHubTransaction.RESULT_SUCCESS;
+                } catch (RemoteException | ServiceSpecificException e) {
+                    return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
+                } catch (IllegalArgumentException e) {
+                    return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
                 }
+            };
 
-                return ContextHubTransaction.RESULT_SUCCESS;
-            } catch (RemoteException | ServiceSpecificException e) {
-                return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
-            } catch (IllegalArgumentException e) {
-                return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
+            // Only process the message normally if not using test mode manager or if
+            // the test mode manager call returned false as this indicates it did not
+            // process the message.
+            boolean useTestModeManager = Flags.reliableMessageImplementation()
+                    && Flags.reliableMessageTestModeBehavior()
+                    && mIsTestModeEnabled.get();
+            if (!useTestModeManager || !mTestModeManager.sendMessageToContextHub(
+                    sendMessage, message)) {
+                try {
+                    return sendMessage.call();
+                } catch (Exception e) {
+                    return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
+                }
             }
+            return ContextHubTransaction.RESULT_SUCCESS;
         }
 
         @ContextHubTransaction.Result
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 53b6796..a6f4c0e 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -530,6 +530,12 @@
      */
     private boolean mUseDifferentDelaysForBackgroundChain;
 
+    /**
+     * Core uids and apps without the internet permission will not have any firewall rules applied
+     * to them.
+     */
+    private boolean mNeverApplyRulesToCoreUids;
+
     // See main javadoc for instructions on how to use these locks.
     final Object mUidRulesFirstLock = new Object();
     final Object mNetworkPoliciesSecondLock = new Object();
@@ -622,16 +628,6 @@
 
     @GuardedBy("mUidRulesFirstLock")
     final SparseIntArray mUidFirewallStandbyRules = new SparseIntArray();
-    @GuardedBy("mUidRulesFirstLock")
-    final SparseIntArray mUidFirewallDozableRules = new SparseIntArray();
-    @GuardedBy("mUidRulesFirstLock")
-    final SparseIntArray mUidFirewallPowerSaveRules = new SparseIntArray();
-    @GuardedBy("mUidRulesFirstLock")
-    final SparseIntArray mUidFirewallBackgroundRules = new SparseIntArray();
-    @GuardedBy("mUidRulesFirstLock")
-    final SparseIntArray mUidFirewallRestrictedModeRules = new SparseIntArray();
-    @GuardedBy("mUidRulesFirstLock")
-    final SparseIntArray mUidFirewallLowPowerStandbyModeRules = new SparseIntArray();
 
     /** Set of states for the child firewall chains. True if the chain is active. */
     @GuardedBy("mUidRulesFirstLock")
@@ -770,7 +766,8 @@
 
     /** List of apps indexed by uid and whether they have the internet permission */
     @GuardedBy("mUidRulesFirstLock")
-    private final SparseBooleanArray mInternetPermissionMap = new SparseBooleanArray();
+    @VisibleForTesting
+    final SparseBooleanArray mInternetPermissionMap = new SparseBooleanArray();
 
     /**
      * Map of uid -> UidStateCallbackInfo objects holding the data received from
@@ -1048,6 +1045,7 @@
 
             mUseMeteredFirewallChains = Flags.useMeteredFirewallChains();
             mUseDifferentDelaysForBackgroundChain = Flags.useDifferentDelaysForBackgroundChain();
+            mNeverApplyRulesToCoreUids = Flags.neverApplyRulesToCoreUids();
 
             synchronized (mUidRulesFirstLock) {
                 synchronized (mNetworkPoliciesSecondLock) {
@@ -4098,6 +4096,8 @@
                         + mUseMeteredFirewallChains);
                 fout.println(Flags.FLAG_USE_DIFFERENT_DELAYS_FOR_BACKGROUND_CHAIN + ": "
                         + mUseDifferentDelaysForBackgroundChain);
+                fout.println(Flags.FLAG_NEVER_APPLY_RULES_TO_CORE_UIDS + ": "
+                        + mNeverApplyRulesToCoreUids);
 
                 fout.println();
                 fout.println("mRestrictBackgroundLowPowerMode: " + mRestrictBackgroundLowPowerMode);
@@ -4589,7 +4589,7 @@
     @VisibleForTesting
     @GuardedBy("mUidRulesFirstLock")
     void updateRestrictedModeAllowlistUL() {
-        mUidFirewallRestrictedModeRules.clear();
+        final SparseIntArray uidRules = new SparseIntArray();
         forEachUid("updateRestrictedModeAllowlist", uid -> {
             synchronized (mUidRulesFirstLock) {
                 final int effectiveBlockedReasons = updateBlockedReasonsForRestrictedModeUL(
@@ -4599,13 +4599,13 @@
                 // setUidFirewallRulesUL will allowlist all uids that are passed to it, so only add
                 // non-default rules.
                 if (newFirewallRule != FIREWALL_RULE_DEFAULT) {
-                    mUidFirewallRestrictedModeRules.append(uid, newFirewallRule);
+                    uidRules.append(uid, newFirewallRule);
                 }
             }
         });
         if (mRestrictedNetworkingMode) {
             // firewall rules only need to be set when this mode is being enabled.
-            setUidFirewallRulesUL(FIREWALL_CHAIN_RESTRICTED, mUidFirewallRestrictedModeRules);
+            setUidFirewallRulesUL(FIREWALL_CHAIN_RESTRICTED, uidRules);
         }
         enableFirewallChainUL(FIREWALL_CHAIN_RESTRICTED, mRestrictedNetworkingMode);
     }
@@ -4689,8 +4689,7 @@
     void updateRulesForPowerSaveUL() {
         Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRulesForPowerSaveUL");
         try {
-            updateRulesForAllowlistedPowerSaveUL(mRestrictPower, FIREWALL_CHAIN_POWERSAVE,
-                    mUidFirewallPowerSaveRules);
+            updateRulesForAllowlistedPowerSaveUL(mRestrictPower, FIREWALL_CHAIN_POWERSAVE);
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
         }
@@ -4705,8 +4704,7 @@
     void updateRulesForDeviceIdleUL() {
         Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRulesForDeviceIdleUL");
         try {
-            updateRulesForAllowlistedPowerSaveUL(mDeviceIdleMode, FIREWALL_CHAIN_DOZABLE,
-                    mUidFirewallDozableRules);
+            updateRulesForAllowlistedPowerSaveUL(mDeviceIdleMode, FIREWALL_CHAIN_DOZABLE);
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
         }
@@ -4720,13 +4718,11 @@
     // NOTE: since both fw_dozable and fw_powersave uses the same map
     // (mPowerSaveTempWhitelistAppIds) for allowlisting, we can reuse their logic in this method.
     @GuardedBy("mUidRulesFirstLock")
-    private void updateRulesForAllowlistedPowerSaveUL(boolean enabled, int chain,
-            SparseIntArray rules) {
+    private void updateRulesForAllowlistedPowerSaveUL(boolean enabled, int chain) {
         if (enabled) {
             // Sync the allowlists before enabling the chain.  We don't care about the rules if
             // we are disabling the chain.
-            final SparseIntArray uidRules = rules;
-            uidRules.clear();
+            final SparseIntArray uidRules = new SparseIntArray();
             final List<UserInfo> users = mUserManager.getUsers();
             for (int ui = users.size() - 1; ui >= 0; ui--) {
                 UserInfo user = users.get(ui);
@@ -4755,9 +4751,7 @@
     private void updateRulesForBackgroundChainUL() {
         Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRulesForBackgroundChainUL");
         try {
-            final SparseIntArray uidRules = mUidFirewallBackgroundRules;
-            uidRules.clear();
-
+            final SparseIntArray uidRules = new SparseIntArray();
             final List<UserInfo> users = mUserManager.getUsers();
             for (int ui = users.size() - 1; ui >= 0; ui--) {
                 final UserInfo user = users.get(ui);
@@ -4794,17 +4788,17 @@
         Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRulesForLowPowerStandbyUL");
         try {
             if (mLowPowerStandbyActive) {
-                mUidFirewallLowPowerStandbyModeRules.clear();
+                final SparseIntArray uidRules = new SparseIntArray();
                 for (int i = mUidState.size() - 1; i >= 0; i--) {
                     final int uid = mUidState.keyAt(i);
                     final int effectiveBlockedReasons = getEffectiveBlockedReasons(uid);
                     if (hasInternetPermissionUL(uid) && (effectiveBlockedReasons
                                     & BLOCKED_REASON_LOW_POWER_STANDBY) == 0) {
-                        mUidFirewallLowPowerStandbyModeRules.put(uid, FIREWALL_RULE_ALLOW);
+                        uidRules.put(uid, FIREWALL_RULE_ALLOW);
                     }
                 }
                 setUidFirewallRulesUL(FIREWALL_CHAIN_LOW_POWER_STANDBY,
-                        mUidFirewallLowPowerStandbyModeRules, CHAIN_TOGGLE_ENABLE);
+                        uidRules, CHAIN_TOGGLE_ENABLE);
             } else {
                 setUidFirewallRulesUL(FIREWALL_CHAIN_LOW_POWER_STANDBY, null, CHAIN_TOGGLE_DISABLE);
             }
@@ -4822,10 +4816,8 @@
         final int effectiveBlockedReasons = getEffectiveBlockedReasons(uid);
         if (mUidState.contains(uid)
                 && (effectiveBlockedReasons & BLOCKED_REASON_LOW_POWER_STANDBY) == 0) {
-            mUidFirewallLowPowerStandbyModeRules.put(uid, FIREWALL_RULE_ALLOW);
             setUidFirewallRuleUL(FIREWALL_CHAIN_LOW_POWER_STANDBY, uid, FIREWALL_RULE_ALLOW);
         } else {
-            mUidFirewallLowPowerStandbyModeRules.delete(uid);
             setUidFirewallRuleUL(FIREWALL_CHAIN_LOW_POWER_STANDBY, uid, FIREWALL_RULE_DEFAULT);
         }
     }
@@ -4896,6 +4888,12 @@
                 int[] idleUids = mUsageStats.getIdleUidsForUser(user.id);
                 for (int uid : idleUids) {
                     if (!mPowerSaveTempWhitelistAppIds.get(UserHandle.getAppId(uid), false)) {
+                        if (mNeverApplyRulesToCoreUids && !isUidValidForRulesUL(uid)) {
+                            // This check is needed to keep mUidFirewallStandbyRules free of any
+                            // such uids. Doing this keeps it in sync with the actual rules applied
+                            // in the underlying connectivity stack.
+                            continue;
+                        }
                         // quick check: if this uid doesn't have INTERNET permission, it
                         // doesn't have network access anyway, so it is a waste to mess
                         // with it here.
@@ -5198,6 +5196,11 @@
 
     @GuardedBy("mUidRulesFirstLock")
     private boolean isUidValidForAllowlistRulesUL(int uid) {
+        return isUidValidForRulesUL(uid);
+    }
+
+    @GuardedBy("mUidRulesFirstLock")
+    private boolean isUidValidForRulesUL(int uid) {
         return UserHandle.isApp(uid) && hasInternetPermissionUL(uid);
     }
 
@@ -5313,16 +5316,11 @@
         mActivityManagerInternal.onUidBlockedReasonsChanged(uid, BLOCKED_REASON_NONE);
         mUidPolicy.delete(uid);
         mUidFirewallStandbyRules.delete(uid);
-        mUidFirewallDozableRules.delete(uid);
-        mUidFirewallPowerSaveRules.delete(uid);
-        mUidFirewallBackgroundRules.delete(uid);
         mBackgroundTransitioningUids.delete(uid);
         mPowerSaveWhitelistExceptIdleAppIds.delete(uid);
         mPowerSaveWhitelistAppIds.delete(uid);
         mPowerSaveTempWhitelistAppIds.delete(uid);
         mAppIdleTempWhitelistAppIds.delete(uid);
-        mUidFirewallRestrictedModeRules.delete(uid);
-        mUidFirewallLowPowerStandbyModeRules.delete(uid);
         synchronized (mUidStateCallbackInfos) {
             mUidStateCallbackInfos.remove(uid);
         }
@@ -6217,41 +6215,33 @@
         }
     }
 
-    private void addSdkSandboxUidsIfNeeded(SparseIntArray uidRules) {
-        final int size = uidRules.size();
-        final SparseIntArray sdkSandboxUids = new SparseIntArray();
-        for (int index = 0; index < size; index++) {
-            final int uid = uidRules.keyAt(index);
-            final int rule = uidRules.valueAt(index);
-            if (Process.isApplicationUid(uid)) {
-                sdkSandboxUids.put(Process.toSdkSandboxUid(uid), rule);
-            }
-        }
-
-        for (int index = 0; index < sdkSandboxUids.size(); index++) {
-            final int uid = sdkSandboxUids.keyAt(index);
-            final int rule = sdkSandboxUids.valueAt(index);
-            uidRules.put(uid, rule);
-        }
-    }
-
     /**
      * Set uid rules on a particular firewall chain. This is going to synchronize the rules given
      * here to netd.  It will clean up dead rules and make sure the target chain only contains rules
      * specified here.
      */
+    @GuardedBy("mUidRulesFirstLock")
     private void setUidFirewallRulesUL(int chain, SparseIntArray uidRules) {
-        addSdkSandboxUidsIfNeeded(uidRules);
         try {
             int size = uidRules.size();
-            int[] uids = new int[size];
-            int[] rules = new int[size];
+            final IntArray uids = new IntArray(size);
+            final IntArray rules = new IntArray(size);
             for(int index = size - 1; index >= 0; --index) {
-                uids[index] = uidRules.keyAt(index);
-                rules[index] = uidRules.valueAt(index);
+                final int uid = uidRules.keyAt(index);
+                if (mNeverApplyRulesToCoreUids && !isUidValidForRulesUL(uid)) {
+                    continue;
+                }
+                uids.add(uid);
+                rules.add(uidRules.valueAt(index));
+                if (Process.isApplicationUid(uid)) {
+                    uids.add(Process.toSdkSandboxUid(uid));
+                    rules.add(uidRules.valueAt(index));
+                }
             }
-            mNetworkManager.setFirewallUidRules(chain, uids, rules);
-            mLogger.firewallRulesChanged(chain, uids, rules);
+            final int[] uidArray = uids.toArray();
+            final int[] ruleArray = rules.toArray();
+            mNetworkManager.setFirewallUidRules(chain, uidArray, ruleArray);
+            mLogger.firewallRulesChanged(chain, uidArray, ruleArray);
         } catch (IllegalStateException e) {
             Log.wtf(TAG, "problem setting firewall uid rules", e);
         } catch (RemoteException e) {
@@ -6264,26 +6254,17 @@
      */
     @GuardedBy("mUidRulesFirstLock")
     private void setUidFirewallRuleUL(int chain, int uid, int rule) {
+        if (mNeverApplyRulesToCoreUids && !isUidValidForRulesUL(uid)) {
+            return;
+        }
         if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) {
             Trace.traceBegin(Trace.TRACE_TAG_NETWORK,
                     "setUidFirewallRuleUL: " + chain + "/" + uid + "/" + rule);
         }
         try {
-            if (chain == FIREWALL_CHAIN_DOZABLE) {
-                mUidFirewallDozableRules.put(uid, rule);
-            } else if (chain == FIREWALL_CHAIN_STANDBY) {
+            if (chain == FIREWALL_CHAIN_STANDBY) {
                 mUidFirewallStandbyRules.put(uid, rule);
-            } else if (chain == FIREWALL_CHAIN_POWERSAVE) {
-                mUidFirewallPowerSaveRules.put(uid, rule);
-            } else if (chain == FIREWALL_CHAIN_RESTRICTED) {
-                mUidFirewallRestrictedModeRules.put(uid, rule);
-            } else if (chain == FIREWALL_CHAIN_LOW_POWER_STANDBY) {
-                mUidFirewallLowPowerStandbyModeRules.put(uid, rule);
-            } else if (chain == FIREWALL_CHAIN_BACKGROUND) {
-                mUidFirewallBackgroundRules.put(uid, rule);
             }
-            // Note that we do not need keep a separate cache of uid rules for chains that we do
-            // not call #setUidFirewallRulesUL for.
 
             try {
                 mNetworkManager.setFirewallUidRule(chain, uid, rule);
@@ -6328,6 +6309,8 @@
      * Resets all firewall rules associated with an UID.
      */
     private void resetUidFirewallRules(int uid) {
+        // Resetting rules for uids with isUidValidForRulesUL = false should be OK as no rules
+        // should be previously set and the downstream code will skip no-op changes.
         try {
             mNetworkManager.setFirewallUidRule(FIREWALL_CHAIN_DOZABLE, uid,
                     FIREWALL_RULE_DEFAULT);
diff --git a/services/core/java/com/android/server/net/flags.aconfig b/services/core/java/com/android/server/net/flags.aconfig
index 586baf0..7f04e66 100644
--- a/services/core/java/com/android/server/net/flags.aconfig
+++ b/services/core/java/com/android/server/net/flags.aconfig
@@ -27,3 +27,13 @@
       purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "never_apply_rules_to_core_uids"
+    namespace: "backstage_power"
+    description: "Removes all rule bookkeeping and evaluation logic for core uids and uids without the internet permission"
+    bug: "356956588"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index 3cc0457..66e61c0 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -16,9 +16,6 @@
 
 package com.android.server.notification;
 
-import static android.service.notification.Condition.STATE_TRUE;
-import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER;
-
 import android.app.INotificationManager;
 import android.app.NotificationManager;
 import android.content.ComponentName;
@@ -322,20 +319,7 @@
                 final Condition c = conditions[i];
                 final ConditionRecord r = getRecordLocked(c.id, info.component, true /*create*/);
                 r.info = info;
-                if (android.app.Flags.modesUi()) {
-                    // if user turned on the mode, ignore the update unless the app also wants the
-                    // mode on. this will update the origin of the mode and let the owner turn it
-                    // off when the context ends
-                    if (r.condition != null && r.condition.source == UPDATE_ORIGIN_USER) {
-                        if (r.condition.state == STATE_TRUE && c.state == STATE_TRUE) {
-                            r.condition = c;
-                        }
-                    } else {
-                        r.condition = c;
-                    }
-                } else {
-                    r.condition = c;
-                }
+                r.condition = c;
             }
         }
         final int N = conditions.length;
diff --git a/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
index d060f8f2..c8cb54f 100644
--- a/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
+++ b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
@@ -31,7 +31,7 @@
 import android.service.notification.DeviceEffectsApplier;
 import android.service.notification.ZenDeviceEffects;
 import android.service.notification.ZenModeConfig;
-import android.service.notification.ZenModeConfig.ConfigChangeOrigin;
+import android.service.notification.ZenModeConfig.ConfigOrigin;
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
@@ -72,7 +72,7 @@
     }
 
     @Override
-    public void apply(ZenDeviceEffects effects, @ConfigChangeOrigin int origin) {
+    public void apply(ZenDeviceEffects effects, @ConfigOrigin int origin) {
         Binder.withCleanCallingIdentity(() -> {
             if (mLastAppliedEffects.shouldSuppressAmbientDisplay()
                     != effects.shouldSuppressAmbientDisplay()) {
@@ -120,15 +120,16 @@
         mLastAppliedEffects = effects;
     }
 
-    private void updateOrScheduleNightMode(boolean useNightMode, @ConfigChangeOrigin int origin) {
+    private void updateOrScheduleNightMode(boolean useNightMode, @ConfigOrigin int origin) {
         mPendingNightMode = useNightMode;
 
         // Changing the theme can be disruptive for the user (Activities are likely recreated, may
         // lose some state). Therefore we only apply the change immediately if the rule was
         // activated manually, or we are initializing, or the screen is currently off/dreaming.
-        if (origin == ZenModeConfig.UPDATE_ORIGIN_INIT
-                || origin == ZenModeConfig.UPDATE_ORIGIN_INIT_USER
-                || origin == ZenModeConfig.UPDATE_ORIGIN_USER
+        if (origin == ZenModeConfig.ORIGIN_INIT
+                || origin == ZenModeConfig.ORIGIN_INIT_USER
+                || origin == ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI
+                || origin == ZenModeConfig.ORIGIN_USER_IN_APP
                 || !mPowerManager.isInteractive()) {
             unregisterScreenOffReceiver();
             updateNightModeImmediately(useNightMode);
diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
index bd551fb..9818916 100644
--- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
@@ -1194,9 +1194,9 @@
         }
 
         boolean shouldIgnoreNotification(final NotificationRecord record) {
-            // Ignore group summaries
-            return (record.getSbn().isGroup() && record.getSbn().getNotification()
-                    .isGroupSummary());
+            // Ignore auto-group summaries => don't count them as app-posted notifications
+            // for the cooldown budget
+            return (record.getSbn().isGroup() && GroupHelper.isAggregatedGroup(record));
         }
 
         /**
@@ -1519,7 +1519,14 @@
         @Override
         public void setLastNotificationUpdateTimeMs(NotificationRecord record,
                 long timestampMillis) {
-            super.setLastNotificationUpdateTimeMs(record, timestampMillis);
+            if (Flags.politeNotificationsAttnUpdate()) {
+                // Set last update per package/channel only for exempt notifications
+                if (isAvalancheExempted(record)) {
+                    super.setLastNotificationUpdateTimeMs(record, timestampMillis);
+                }
+            } else {
+                super.setLastNotificationUpdateTimeMs(record, timestampMillis);
+            }
             mLastNotificationTimestamp = timestampMillis;
             mAppStrategy.setLastNotificationUpdateTimeMs(record, timestampMillis);
         }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 4179edd..c7c984b 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2963,8 +2963,9 @@
                     };
                     cancelGroupChildrenLocked(userId, pkg, Binder.getCallingUid(),
                             Binder.getCallingPid(), null,
-                            false, childrenFlagChecker, groupKey,
-                            REASON_APP_CANCEL, SystemClock.elapsedRealtime());
+                            false, childrenFlagChecker,
+                            NotificationManagerService::wasChildOfForceRegroupedGroupChecker,
+                            groupKey, REASON_APP_CANCEL, SystemClock.elapsedRealtime());
                 }
             }
         });
@@ -5771,16 +5772,20 @@
                     Binder.getCallingUid());
         }
 
-        @ZenModeConfig.ConfigChangeOrigin
+        @ZenModeConfig.ConfigOrigin
         private int computeZenOrigin(boolean fromUser) {
             // "fromUser" is introduced with MODES_API, so only consider it in that case.
-            // (Non-MODES_API behavior should also not depend at all on UPDATE_ORIGIN_USER).
+            // (Non-MODES_API behavior should also not depend at all on ORIGIN_USER_IN_X).
             if (android.app.Flags.modesApi() && fromUser) {
-                return ZenModeConfig.UPDATE_ORIGIN_USER;
+                if (isCallerSystemOrSystemUi()) {
+                    return ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI;
+                } else {
+                    return ZenModeConfig.ORIGIN_USER_IN_APP;
+                }
             } else if (isCallerSystemOrSystemUi()) {
-                return ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI;
+                return ZenModeConfig.ORIGIN_SYSTEM;
             } else {
-                return ZenModeConfig.UPDATE_ORIGIN_APP;
+                return ZenModeConfig.ORIGIN_APP;
             }
         }
 
@@ -6137,7 +6142,7 @@
             enforcePolicyAccess(pkg, "setNotificationPolicy");
             enforceUserOriginOnlyFromSystem(fromUser, "setNotificationPolicy");
             int callingUid = Binder.getCallingUid();
-            @ZenModeConfig.ConfigChangeOrigin int origin = computeZenOrigin(fromUser);
+            @ZenModeConfig.ConfigOrigin int origin = computeZenOrigin(fromUser);
 
             boolean shouldApplyAsImplicitRule = android.app.Flags.modesApi()
                     && !canManageGlobalZenPolicy(pkg, callingUid);
@@ -8663,8 +8668,8 @@
                     if (r.getNotification().isGroupSummary()) {
                         cancelGroupChildrenLocked(mUserId, mPkg, mCallingUid, mCallingPid,
                                 listenerName, mSendDelete, childrenFlagChecker,
-                                r.getNotification().getGroup(), mReason,
-                                mCancellationElapsedTimeMs);
+                                NotificationManagerService::isChildOfCurrentGroupChecker,
+                                r.getGroupKey(), mReason, mCancellationElapsedTimeMs);
                     }
                     mAttentionHelper.updateLightsLocked();
                     if (mShortcutHelper != null) {
@@ -9386,8 +9391,8 @@
         if (oldIsSummary && (!isSummary || !oldGroup.equals(group))) {
             cancelGroupChildrenLocked(old.getUserId(), old.getSbn().getPackageName(), callingUid,
                     callingPid, null, false /* sendDelete */, childrenFlagChecker,
-                    old.getNotification().getGroup(), REASON_APP_CANCEL,
-                    SystemClock.elapsedRealtime());
+                    NotificationManagerService::isChildOfCurrentGroupChecker, old.getGroupKey(),
+                    REASON_APP_CANCEL, SystemClock.elapsedRealtime());
         }
     }
 
@@ -10368,13 +10373,45 @@
         public boolean apply(int flags);
     }
 
-    private static boolean isChildOfGroup(final NotificationRecord childRecord, int userId,
+    @FunctionalInterface
+    private interface GroupChildChecker {
+        // Returns true if the childRecord is a child of the group defined
+        // by the rest of the parameters
+        boolean apply(NotificationRecord childRecord, int userId, String pkg, String groupKey);
+    }
+
+    /**
+     * Checks that the notification is currently a child of the group
+     * @param childRecord the notification to check
+     * @param userId userId of the group
+     * @param pkg package name of the group
+     * @param groupKey group key for a current group
+     * @return true if the childRecord is currently a child of the group
+     */
+    private static boolean isChildOfCurrentGroupChecker(NotificationRecord childRecord, int userId,
             String pkg, String groupKey) {
         return (childRecord.getUser().getIdentifier() == userId
             && childRecord.getSbn().getPackageName().equals(pkg)
             && childRecord.getSbn().isGroup()
             && !childRecord.getNotification().isGroupSummary()
-            && TextUtils.equals(groupKey, childRecord.getNotification().getGroup()));
+            && TextUtils.equals(groupKey, childRecord.getGroupKey()));
+    }
+
+    /**
+     * Checks that the notification was originally a child of the group
+     * @param childRecord the notification to check
+     * @param userId userId of the group
+     * @param pkg package name of the group
+     * @param groupKey original/initial group key for a group that was force grouped
+     * @return true if the childRecord was originally a child of the group
+     */
+    private static boolean wasChildOfForceRegroupedGroupChecker(NotificationRecord childRecord,
+            int userId, String pkg, String groupKey) {
+        return (childRecord.getUser().getIdentifier() == userId
+            && childRecord.getSbn().getPackageName().equals(pkg)
+            && childRecord.getSbn().isGroup()
+            && !childRecord.getNotification().isGroupSummary()
+            && TextUtils.equals(groupKey, childRecord.getOriginalGroupKey()));
     }
 
     @GuardedBy("mNotificationLock")
@@ -10535,18 +10572,19 @@
     // Warning: The caller is responsible for invoking updateLightsLocked().
     @GuardedBy("mNotificationLock")
     private void cancelGroupChildrenLocked(int userId, String pkg, int callingUid, int callingPid,
-            String listenerName, boolean sendDelete, FlagChecker flagChecker, String groupKey,
-            int reason, @ElapsedRealtimeLong long cancellationElapsedTimeMs) {
+            String listenerName, boolean sendDelete, FlagChecker flagChecker,
+            GroupChildChecker groupChildChecker, String groupKey, int reason,
+            @ElapsedRealtimeLong long cancellationElapsedTimeMs) {
         if (pkg == null) {
             if (DBG) Slog.e(TAG, "No package for group summary");
             return;
         }
 
         cancelGroupChildrenByListLocked(mNotificationList, userId, pkg, callingUid, callingPid,
-                listenerName, sendDelete, true, flagChecker, groupKey,
+                listenerName, sendDelete, true, flagChecker, groupChildChecker, groupKey,
                 reason, cancellationElapsedTimeMs);
         cancelGroupChildrenByListLocked(mEnqueuedNotifications, userId, pkg, callingUid, callingPid,
-                listenerName, sendDelete, false, flagChecker, groupKey,
+                listenerName, sendDelete, false, flagChecker, groupChildChecker, groupKey,
                 reason, cancellationElapsedTimeMs);
     }
 
@@ -10554,12 +10592,13 @@
     private void cancelGroupChildrenByListLocked(ArrayList<NotificationRecord> notificationList,
             int userId, String pkg, int callingUid, int callingPid,
             String listenerName, boolean sendDelete, boolean wasPosted, FlagChecker flagChecker,
-            String groupKey, int reason, @ElapsedRealtimeLong long cancellationElapsedTimeMs) {
+            GroupChildChecker grouChildChecker, String groupKey, int reason,
+            @ElapsedRealtimeLong long cancellationElapsedTimeMs) {
         final int childReason = REASON_GROUP_SUMMARY_CANCELED;
         for (int i = notificationList.size() - 1; i >= 0; i--) {
             final NotificationRecord childR = notificationList.get(i);
             final StatusBarNotification childSbn = childR.getSbn();
-            if (isChildOfGroup(childR, userId, pkg, groupKey)
+            if (grouChildChecker.apply(childR, userId, pkg, groupKey)
                 && (flagChecker == null || flagChecker.apply(childR.getFlags()))
                 && (!childR.getChannel().isImportantConversation() || reason != REASON_CANCEL)) {
                 EventLogTags.writeNotificationCancel(callingUid, callingPid, pkg, childSbn.getId(),
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index bd00901..1392003 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -1163,6 +1163,21 @@
         getSbn().setOverrideGroupKey(overrideGroupKey);
     }
 
+    /**
+     * Get the original group key that was set via {@link Notification.Builder#setGroup}
+     *
+     * This value is different than the value returned by {@link #getGroupKey()} as it does
+     * not contain any userId or package name.
+     *
+     * This value is different than the value returned
+     * by {@link StatusBarNotification#getGroup()} if the notification group
+     * was overridden: by NotificationAssistantService or by autogrouping.
+     */
+    @Nullable
+    public String getOriginalGroupKey() {
+        return getSbn().getNotification().getGroup();
+    }
+
     public NotificationChannel getChannel() {
         return mChannel;
     }
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index c09504f..821722b 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -30,7 +30,6 @@
 import static android.app.NotificationManager.IMPORTANCE_MAX;
 import static android.app.NotificationManager.IMPORTANCE_NONE;
 import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
-
 import static android.os.UserHandle.USER_SYSTEM;
 import static android.service.notification.Flags.notificationClassification;
 
@@ -1979,8 +1978,8 @@
                         (areChannelsBypassingDnd
                                 ? NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND : 0),
                         policy.priorityConversationSenders),
-                fromSystemOrSystemUi ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
-                        : ZenModeConfig.UPDATE_ORIGIN_APP,
+                fromSystemOrSystemUi ? ZenModeConfig.ORIGIN_SYSTEM
+                        : ZenModeConfig.ORIGIN_APP,
                 callingUid);
     }
 
diff --git a/services/core/java/com/android/server/notification/ZenModeConditions.java b/services/core/java/com/android/server/notification/ZenModeConditions.java
index 3650536..d495ef5 100644
--- a/services/core/java/com/android/server/notification/ZenModeConditions.java
+++ b/services/core/java/com/android/server/notification/ZenModeConditions.java
@@ -82,7 +82,7 @@
         for (ZenRule automaticRule : config.automaticRules.values()) {
             if (automaticRule.component != null) {
                 evaluateRule(automaticRule, current, trigger, processSubscriptions, false);
-                updateSnoozing(automaticRule);
+                automaticRule.reconsiderConditionOverride();
             }
         }
 
@@ -116,8 +116,8 @@
         if (DEBUG) Log.d(TAG, "onServiceAdded " + component);
         final int callingUid = Binder.getCallingUid();
         mHelper.setConfig(mHelper.getConfig(), component,
-                callingUid == Process.SYSTEM_UID ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
-                        : ZenModeConfig.UPDATE_ORIGIN_APP,
+                callingUid == Process.SYSTEM_UID ? ZenModeConfig.ORIGIN_SYSTEM
+                        : ZenModeConfig.ORIGIN_APP,
                 "zmc.onServiceAdded:" + component, callingUid);
     }
 
@@ -128,8 +128,8 @@
         if (config == null) return;
         final int callingUid = Binder.getCallingUid();
         mHelper.setAutomaticZenRuleState(id, condition,
-                callingUid == Process.SYSTEM_UID ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
-                        : ZenModeConfig.UPDATE_ORIGIN_APP,
+                callingUid == Process.SYSTEM_UID ? ZenModeConfig.ORIGIN_SYSTEM
+                        : ZenModeConfig.ORIGIN_APP,
                 callingUid);
     }
 
@@ -187,13 +187,4 @@
                     + rule.conditionId);
         }
     }
-
-    private boolean updateSnoozing(ZenRule rule) {
-        if (rule != null && rule.snoozing && !rule.isTrueOrUnknown()) {
-            rule.snoozing = false;
-            if (DEBUG) Log.d(TAG, "Snoozing reset for " + rule.conditionId);
-            return true;
-        }
-        return false;
-    }
 }
diff --git a/services/core/java/com/android/server/notification/ZenModeEventLogger.java b/services/core/java/com/android/server/notification/ZenModeEventLogger.java
index 4a82057..b03a54e 100644
--- a/services/core/java/com/android/server/notification/ZenModeEventLogger.java
+++ b/services/core/java/com/android/server/notification/ZenModeEventLogger.java
@@ -34,7 +34,7 @@
 import android.service.notification.DNDPolicyProto;
 import android.service.notification.ZenAdapters;
 import android.service.notification.ZenModeConfig;
-import android.service.notification.ZenModeConfig.ConfigChangeOrigin;
+import android.service.notification.ZenModeConfig.ConfigOrigin;
 import android.service.notification.ZenModeConfig.ZenRule;
 import android.service.notification.ZenModeDiff;
 import android.service.notification.ZenPolicy;
@@ -113,7 +113,7 @@
      * @param origin     The origin of the Zen change.
      */
     public final void maybeLogZenChange(ZenModeInfo prevInfo, ZenModeInfo newInfo, int callingUid,
-            @ConfigChangeOrigin int origin) {
+            @ConfigOrigin int origin) {
         mChangeState.init(prevInfo, newInfo, callingUid, origin);
         if (mChangeState.shouldLogChanges()) {
             maybeReassignCallingUid();
@@ -250,11 +250,11 @@
         ZenModeConfig mPrevConfig, mNewConfig;
         NotificationManager.Policy mPrevPolicy, mNewPolicy;
         int mCallingUid = Process.INVALID_UID;
-        @ConfigChangeOrigin
-        int mOrigin = ZenModeConfig.UPDATE_ORIGIN_UNKNOWN;
+        @ConfigOrigin
+        int mOrigin = ZenModeConfig.ORIGIN_UNKNOWN;
 
         private void init(ZenModeInfo prevInfo, ZenModeInfo newInfo, int callingUid,
-                @ConfigChangeOrigin int origin) {
+                @ConfigOrigin int origin) {
             // previous & new may be the same -- that would indicate that zen mode hasn't changed.
             mPrevZenMode = prevInfo.mZenMode;
             mNewZenMode = newInfo.mZenMode;
@@ -484,7 +484,8 @@
          */
         boolean getIsUserAction() {
             if (Flags.modesApi()) {
-                return mOrigin == ZenModeConfig.UPDATE_ORIGIN_USER;
+                return mOrigin == ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI
+                        || mOrigin == ZenModeConfig.ORIGIN_USER_IN_APP;
             }
 
             // Approach for pre-MODES_API:
@@ -549,10 +550,10 @@
         }
 
         boolean isFromSystemOrSystemUi() {
-            return mOrigin == ZenModeConfig.UPDATE_ORIGIN_INIT
-                    || mOrigin == ZenModeConfig.UPDATE_ORIGIN_INIT_USER
-                    || mOrigin == ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
-                    || mOrigin == ZenModeConfig.UPDATE_ORIGIN_RESTORE_BACKUP;
+            return mOrigin == ZenModeConfig.ORIGIN_INIT
+                    || mOrigin == ZenModeConfig.ORIGIN_INIT_USER
+                    || mOrigin == ZenModeConfig.ORIGIN_SYSTEM
+                    || mOrigin == ZenModeConfig.ORIGIN_RESTORE_BACKUP;
         }
 
         /**
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 95d8bb9..0f50260 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -29,13 +29,16 @@
 import static android.service.notification.Condition.STATE_FALSE;
 import static android.service.notification.Condition.STATE_TRUE;
 import static android.service.notification.NotificationServiceProto.ROOT_CONFIG;
-import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_APP;
-import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_INIT;
-import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_INIT_USER;
-import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_RESTORE_BACKUP;
-import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI;
-import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_UNKNOWN;
-import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER;
+import static android.service.notification.ZenModeConfig.ORIGIN_APP;
+import static android.service.notification.ZenModeConfig.ORIGIN_INIT;
+import static android.service.notification.ZenModeConfig.ORIGIN_INIT_USER;
+import static android.service.notification.ZenModeConfig.ORIGIN_RESTORE_BACKUP;
+import static android.service.notification.ZenModeConfig.ORIGIN_SYSTEM;
+import static android.service.notification.ZenModeConfig.ORIGIN_UNKNOWN;
+import static android.service.notification.ZenModeConfig.ORIGIN_USER_IN_APP;
+import static android.service.notification.ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI;
+import static android.service.notification.ZenModeConfig.ZenRule.OVERRIDE_ACTIVATE;
+import static android.service.notification.ZenModeConfig.ZenRule.OVERRIDE_DEACTIVATE;
 
 import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
 import static com.android.internal.util.Preconditions.checkArgument;
@@ -95,7 +98,7 @@
 import android.service.notification.ZenAdapters;
 import android.service.notification.ZenDeviceEffects;
 import android.service.notification.ZenModeConfig;
-import android.service.notification.ZenModeConfig.ConfigChangeOrigin;
+import android.service.notification.ZenModeConfig.ConfigOrigin;
 import android.service.notification.ZenModeConfig.ZenRule;
 import android.service.notification.ZenModeProto;
 import android.service.notification.ZenPolicy;
@@ -294,7 +297,7 @@
             // "update" config to itself, which will have no effect in the case where a config
             // was read in via XML, but will initialize zen mode if nothing was read in and the
             // config remains the default.
-            updateConfigAndZenModeLocked(mConfig, UPDATE_ORIGIN_INIT, "init",
+            updateConfigAndZenModeLocked(mConfig, ORIGIN_INIT, "init",
                     true /*setRingerMode*/, Process.SYSTEM_UID /* callingUid */);
         }
     }
@@ -328,7 +331,7 @@
             }
             mDeviceEffectsApplier = deviceEffectsApplier;
         }
-        applyConsolidatedDeviceEffects(UPDATE_ORIGIN_INIT);
+        applyConsolidatedDeviceEffects(ORIGIN_INIT);
     }
 
     public void onUserSwitched(int user) {
@@ -368,7 +371,7 @@
             config.user = user;
         }
         synchronized (mConfigLock) {
-            setConfigLocked(config, null, UPDATE_ORIGIN_INIT_USER, reason,
+            setConfigLocked(config, null, ORIGIN_INIT_USER, reason,
                     Process.SYSTEM_UID);
         }
         cleanUpZenRules();
@@ -384,7 +387,7 @@
         final int newZen = NotificationManager.zenModeFromInterruptionFilter(filter, -1);
         if (newZen != -1) {
             setManualZenMode(newZen, null,
-                    fromSystemOrSystemUi ? UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI : UPDATE_ORIGIN_APP,
+                    fromSystemOrSystemUi ? ORIGIN_SYSTEM : ORIGIN_APP,
                     /* reason= */ "listener:" + (name != null ? name.flattenToShortString() : null),
                     /* caller= */ name != null ? name.getPackageName() : null,
                     callingUid);
@@ -447,8 +450,8 @@
     }
 
     public String addAutomaticZenRule(String pkg, AutomaticZenRule automaticZenRule,
-            @ConfigChangeOrigin int origin, String reason, int callingUid) {
-        requirePublicOrigin("addAutomaticZenRule", origin);
+            @ConfigOrigin int origin, String reason, int callingUid) {
+        checkManageRuleOrigin("addAutomaticZenRule", origin);
         if (!ZenModeConfig.SYSTEM_AUTHORITY.equals(pkg)) {
             PackageItemInfo component = getServiceInfo(automaticZenRule.getOwner());
             if (component == null) {
@@ -497,7 +500,7 @@
 
     @GuardedBy("mConfigLock")
     private ZenRule maybeRestoreRemovedRule(ZenModeConfig config, ZenRule ruleToAdd,
-            AutomaticZenRule azrToAdd, @ConfigChangeOrigin int origin) {
+            AutomaticZenRule azrToAdd, @ConfigOrigin int origin) {
         if (!Flags.modesApi()) {
             return ruleToAdd;
         }
@@ -517,7 +520,7 @@
         config.deletedRules.remove(deletedKey);
         ruleToRestore.deletionInstant = null;
 
-        if (origin != UPDATE_ORIGIN_APP) {
+        if (origin != ORIGIN_APP) {
             return ruleToAdd; // Okay to create anew.
         }
 
@@ -547,8 +550,8 @@
     }
 
     public boolean updateAutomaticZenRule(String ruleId, AutomaticZenRule automaticZenRule,
-            @ConfigChangeOrigin int origin, String reason, int callingUid) {
-        requirePublicOrigin("updateAutomaticZenRule", origin);
+            @ConfigOrigin int origin, String reason, int callingUid) {
+        checkManageRuleOrigin("updateAutomaticZenRule", origin);
         if (ruleId == null) {
             throw new IllegalArgumentException("ruleId cannot be null");
         }
@@ -621,7 +624,7 @@
                             mContext.getString(R.string.zen_mode_implicit_deactivated),
                             STATE_FALSE);
                     setAutomaticZenRuleStateLocked(newConfig, Collections.singletonList(rule),
-                            deactivated, UPDATE_ORIGIN_APP, callingUid);
+                            deactivated, ORIGIN_APP, callingUid);
                 }
             } else {
                 // Either create a new rule with a default ZenPolicy, or update an existing rule's
@@ -642,12 +645,12 @@
                 if ((rule.userModifiedFields & AutomaticZenRule.FIELD_INTERRUPTION_FILTER) == 0) {
                     rule.zenMode = zenMode;
                 }
-                rule.snoozing = false;
                 rule.condition = new Condition(rule.conditionId,
                         mContext.getString(R.string.zen_mode_implicit_activated),
                         STATE_TRUE);
+                rule.resetConditionOverride();
 
-                setConfigLocked(newConfig, /* triggeringComponent= */ null, UPDATE_ORIGIN_APP,
+                setConfigLocked(newConfig, /* triggeringComponent= */ null, ORIGIN_APP,
                         "applyGlobalZenModeAsImplicitZenRule", callingUid);
             }
         }
@@ -701,7 +704,7 @@
                         /* updateBitmask= */ false,
                         isNew);
 
-                setConfigLocked(newConfig, /* triggeringComponent= */ null, UPDATE_ORIGIN_APP,
+                setConfigLocked(newConfig, /* triggeringComponent= */ null, ORIGIN_APP,
                         "applyGlobalPolicyAsImplicitZenRule", callingUid);
             }
         }
@@ -788,9 +791,9 @@
         return ruleId.startsWith(IMPLICIT_RULE_ID_PREFIX);
     }
 
-    boolean removeAutomaticZenRule(String id, @ConfigChangeOrigin int origin, String reason,
+    boolean removeAutomaticZenRule(String id, @ConfigOrigin int origin, String reason,
             int callingUid) {
-        requirePublicOrigin("removeAutomaticZenRule", origin);
+        checkManageRuleOrigin("removeAutomaticZenRule", origin);
         ZenModeConfig newConfig;
         synchronized (mConfigLock) {
             if (mConfig == null) return false;
@@ -821,9 +824,9 @@
         }
     }
 
-    boolean removeAutomaticZenRules(String packageName, @ConfigChangeOrigin int origin,
+    boolean removeAutomaticZenRules(String packageName, @ConfigOrigin int origin,
             String reason, int callingUid) {
-        requirePublicOrigin("removeAutomaticZenRules", origin);
+        checkManageRuleOrigin("removeAutomaticZenRules", origin);
         ZenModeConfig newConfig;
         synchronized (mConfigLock) {
             if (mConfig == null) return false;
@@ -837,7 +840,7 @@
             }
             // If the system is clearing all rules this means DND access is revoked or the package
             // was uninstalled, so also clear the preserved-deleted rules.
-            if (origin == UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI) {
+            if (origin == ORIGIN_SYSTEM) {
                 for (int i = newConfig.deletedRules.size() - 1; i >= 0; i--) {
                     ZenRule rule = newConfig.deletedRules.get(newConfig.deletedRules.keyAt(i));
                     if (Objects.equals(rule.getPkg(), packageName)) {
@@ -850,7 +853,7 @@
     }
 
     private void maybePreserveRemovedRule(ZenModeConfig config, ZenRule ruleToRemove,
-            @ConfigChangeOrigin int origin) {
+            @ConfigOrigin int origin) {
         if (!Flags.modesApi()) {
             return;
         }
@@ -859,15 +862,15 @@
         // We don't try to preserve system-owned rules because their conditionIds (used as
         // deletedRuleKey) are not stable. This is almost moot anyway because an app cannot
         // delete a system-owned rule.
-        if (origin == UPDATE_ORIGIN_APP && !ruleToRemove.canBeUpdatedByApp()
+        if (origin == ORIGIN_APP && !ruleToRemove.canBeUpdatedByApp()
                 && !PACKAGE_ANDROID.equals(ruleToRemove.pkg)) {
             String deletedKey = ZenModeConfig.deletedRuleKey(ruleToRemove);
             if (deletedKey != null) {
                 ZenRule deletedRule = ruleToRemove.copy();
                 deletedRule.deletionInstant = Instant.now(mClock);
                 // If the rule is restored it shouldn't be active (or snoozed).
-                deletedRule.snoozing = false;
                 deletedRule.condition = null;
+                deletedRule.resetConditionOverride();
                 // Overwrites a previously-deleted rule with the same conditionId, but that's okay.
                 config.deletedRules.put(deletedKey, deletedRule);
             }
@@ -884,13 +887,18 @@
             if (rule == null || !canManageAutomaticZenRule(rule)) {
                 return Condition.STATE_UNKNOWN;
             }
-            return rule.condition != null ? rule.condition.state : STATE_FALSE;
+            if (Flags.modesApi() && Flags.modesUi()) {
+                return rule.isAutomaticActive() ? STATE_TRUE : STATE_FALSE;
+            } else {
+                // Buggy, does not consider snoozing!
+                return rule.condition != null ? rule.condition.state : STATE_FALSE;
+            }
         }
     }
 
-    void setAutomaticZenRuleState(String id, Condition condition, @ConfigChangeOrigin int origin,
+    void setAutomaticZenRuleState(String id, Condition condition, @ConfigOrigin int origin,
             int callingUid) {
-        requirePublicOrigin("setAutomaticZenRuleState", origin);
+        checkSetRuleStateOrigin("setAutomaticZenRuleState(String id)", origin);
         ZenModeConfig newConfig;
         synchronized (mConfigLock) {
             if (mConfig == null) return;
@@ -911,8 +919,8 @@
     }
 
     void setAutomaticZenRuleState(Uri ruleDefinition, Condition condition,
-            @ConfigChangeOrigin int origin, int callingUid) {
-        requirePublicOrigin("setAutomaticZenRuleState", origin);
+            @ConfigOrigin int origin, int callingUid) {
+        checkSetRuleStateOrigin("setAutomaticZenRuleState(Uri ruleDefinition)", origin);
         ZenModeConfig newConfig;
         synchronized (mConfigLock) {
             if (mConfig == null) return;
@@ -932,20 +940,53 @@
 
     @GuardedBy("mConfigLock")
     private void setAutomaticZenRuleStateLocked(ZenModeConfig config, List<ZenRule> rules,
-            Condition condition, @ConfigChangeOrigin int origin, int callingUid) {
+            Condition condition, @ConfigOrigin int origin, int callingUid) {
         if (rules == null || rules.isEmpty()) return;
 
-        if (Flags.modesApi() && condition.source == SOURCE_USER_ACTION) {
-            origin = UPDATE_ORIGIN_USER; // Although coming from app, it's actually a user action.
+        if (!Flags.modesUi()) {
+            if (Flags.modesApi() && condition.source == SOURCE_USER_ACTION) {
+                origin = ORIGIN_USER_IN_APP; // Although coming from app, it's actually from user.
+            }
         }
 
         for (ZenRule rule : rules) {
-            rule.condition = condition;
-            updateSnoozing(rule);
+            applyConditionAndReconsiderOverride(rule, condition, origin);
             setConfigLocked(config, rule.component, origin, "conditionChanged", callingUid);
         }
     }
 
+    private static void applyConditionAndReconsiderOverride(ZenRule rule, Condition condition,
+            int origin) {
+        if (Flags.modesApi() && Flags.modesUi()) {
+            if (origin == ORIGIN_USER_IN_SYSTEMUI && condition != null
+                    && condition.source == SOURCE_USER_ACTION) {
+                // Apply as override, instead of actual condition.
+                // If the new override is the reverse of a previous (still active) override, try
+                // removing the previous override, as long as the resulting state, based on the
+                // previous owner-provided condition, is the desired one (active or inactive).
+                // This allows the rule owner to resume controlling the rule after
+                // snoozing-unsnoozing or activating-stopping.
+                if (condition.state == STATE_TRUE) {
+                    rule.resetConditionOverride();
+                    if (!rule.isAutomaticActive()) {
+                        rule.setConditionOverride(OVERRIDE_ACTIVATE);
+                    }
+                } else if (condition.state == STATE_FALSE) {
+                    rule.resetConditionOverride();
+                    if (rule.isAutomaticActive()) {
+                        rule.setConditionOverride(OVERRIDE_DEACTIVATE);
+                    }
+                }
+            } else {
+                rule.condition = condition;
+                rule.reconsiderConditionOverride();
+            }
+        } else {
+            rule.condition = condition;
+            rule.reconsiderConditionOverride();
+        }
+    }
+
     private static List<ZenRule> findMatchingRules(ZenModeConfig config, Uri id,
             Condition condition) {
         List<ZenRule> matchingRules = new ArrayList<>();
@@ -968,15 +1009,6 @@
         return true;
     }
 
-    private boolean updateSnoozing(ZenRule rule) {
-        if (rule != null && rule.snoozing && !rule.isTrueOrUnknown()) {
-            rule.snoozing = false;
-            if (DEBUG) Log.d(TAG, "Snoozing reset for " + rule.conditionId);
-            return true;
-        }
-        return false;
-    }
-
     public int getCurrentInstanceCount(ComponentName cn) {
         if (cn == null) {
             return 0;
@@ -1062,7 +1094,7 @@
                 }
             }
             if (updated) {
-                setConfigLocked(config, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+                setConfigLocked(config, null, ORIGIN_SYSTEM,
                         "updateZenRulesOnLocaleChange", Process.SYSTEM_UID);
             }
         }
@@ -1119,11 +1151,11 @@
      * </ul>
      *
      * <p>The rule's {@link ZenRule#condition} is cleared (meaning that an active rule will be
-     * deactivated) unless the update has origin == {@link ZenModeConfig#UPDATE_ORIGIN_USER}.
+     * deactivated) unless the update has origin == {@link ZenModeConfig#ORIGIN_USER_IN_SYSTEMUI}.
      */
     @GuardedBy("mConfigLock")
     private boolean populateZenRule(String pkg, AutomaticZenRule azr, ZenRule rule,
-                         @ConfigChangeOrigin int origin, boolean isNew) {
+                         @ConfigOrigin int origin, boolean isNew) {
         if (Flags.modesApi()) {
             boolean modified = false;
             // These values can always be edited by the app, so we apply changes immediately.
@@ -1137,7 +1169,7 @@
 
             // Allow updating the CPS backing system rules (e.g. for custom manual -> schedule)
             if (Flags.modesUi()
-                    && (origin == UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI || origin == UPDATE_ORIGIN_USER)
+                    && (origin == ORIGIN_SYSTEM || origin == ORIGIN_USER_IN_SYSTEMUI)
                     && Objects.equals(rule.pkg, SystemZenRules.PACKAGE_ANDROID)
                     && !Objects.equals(rule.component, azr.getOwner())) {
                 rule.component = azr.getOwner();
@@ -1151,7 +1183,7 @@
                     rule.disabledOrigin = origin;
                 } else if (azr.isEnabled()) {
                     // Enabling or previously enabled. Clear disabler.
-                    rule.disabledOrigin = UPDATE_ORIGIN_UNKNOWN;
+                    rule.disabledOrigin = ORIGIN_UNKNOWN;
                 }
             }
 
@@ -1166,7 +1198,7 @@
                     Flags.modesApi()
                             && (Flags.modesUi() || isWatch)
                             && !isNew
-                            && origin == UPDATE_ORIGIN_USER
+                            && origin == ORIGIN_USER_IN_SYSTEMUI
                             && rule.enabled == azr.isEnabled()
                             && rule.conditionId != null
                             && rule.condition != null
@@ -1178,7 +1210,7 @@
 
             if (rule.enabled != azr.isEnabled()) {
                 rule.enabled = azr.isEnabled();
-                rule.snoozing = false;
+                rule.resetConditionOverride();
                 modified = true;
             }
             if (!Objects.equals(rule.configurationActivity, azr.getConfigurationActivity())) {
@@ -1232,7 +1264,7 @@
             }
 
             // Updates the bitmasks if the origin of the change is the user.
-            boolean updateBitmask = (origin == UPDATE_ORIGIN_USER);
+            boolean updateBitmask = (origin == ORIGIN_USER_IN_SYSTEMUI);
 
             if (updateBitmask && !TextUtils.equals(previousName, azr.getName())) {
                 rule.userModifiedFields |= AutomaticZenRule.FIELD_NAME;
@@ -1263,12 +1295,12 @@
 
             // Updates the bitmask and values for all device effect fields, based on the origin.
             modified |= updateZenDeviceEffects(rule, azr.getDeviceEffects(),
-                    origin == UPDATE_ORIGIN_APP, updateBitmask);
+                    origin == ORIGIN_APP, updateBitmask);
 
             return modified;
         } else {
             if (rule.enabled != azr.isEnabled()) {
-                rule.snoozing = false;
+                rule.resetConditionOverride();
             }
             rule.name = azr.getName();
             rule.condition = null;
@@ -1297,8 +1329,8 @@
      * change. (Note that regardless of origin, fields can always be updated if they're not already
      * user modified.)
      */
-    private static boolean doesOriginAlwaysUpdateValues(@ConfigChangeOrigin int origin) {
-        return origin == UPDATE_ORIGIN_USER || origin == UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI;
+    private static boolean doesOriginAlwaysUpdateValues(@ConfigOrigin int origin) {
+        return origin == ORIGIN_USER_IN_SYSTEMUI || origin == ORIGIN_SYSTEM;
     }
 
     /**
@@ -1539,7 +1571,7 @@
                 : AUTOMATIC_RULE_STATUS_DISABLED);
     }
 
-    void setManualZenMode(int zenMode, Uri conditionId, @ConfigChangeOrigin int origin,
+    void setManualZenMode(int zenMode, Uri conditionId, @ConfigOrigin int origin,
             String reason, @Nullable String caller, int callingUid) {
         setManualZenMode(zenMode, conditionId, origin, reason, caller, true /*setRingerMode*/,
                 callingUid);
@@ -1547,7 +1579,7 @@
                 Settings.Secure.SHOW_ZEN_SETTINGS_SUGGESTION, 0);
     }
 
-    private void setManualZenMode(int zenMode, Uri conditionId, @ConfigChangeOrigin int origin,
+    private void setManualZenMode(int zenMode, Uri conditionId, @ConfigOrigin int origin,
             String reason, @Nullable String caller, boolean setRingerMode, int callingUid) {
         ZenModeConfig newConfig;
         synchronized (mConfigLock) {
@@ -1564,24 +1596,22 @@
                 newConfig.manualRule.zenMode = zenMode;
                 newConfig.manualRule.condition = new Condition(newConfig.manualRule.conditionId, "",
                         zenMode == Global.ZEN_MODE_OFF ? STATE_FALSE : STATE_TRUE,
-                        origin == UPDATE_ORIGIN_USER ? SOURCE_USER_ACTION : SOURCE_UNKNOWN);
-                if (zenMode == Global.ZEN_MODE_OFF && origin != UPDATE_ORIGIN_USER) {
+                        origin == ORIGIN_USER_IN_SYSTEMUI ? SOURCE_USER_ACTION : SOURCE_UNKNOWN);
+                if (zenMode == Global.ZEN_MODE_OFF && origin != ORIGIN_USER_IN_SYSTEMUI) {
                     // User deactivation of DND means just turning off the manual DND rule.
                     // For API calls (different origin) keep old behavior of snoozing all rules.
                     for (ZenRule automaticRule : newConfig.automaticRules.values()) {
                         if (automaticRule.isAutomaticActive()) {
-                            automaticRule.snoozing = true;
+                            automaticRule.setConditionOverride(OVERRIDE_DEACTIVATE);
                         }
                     }
                 }
             } else {
                 if (zenMode == Global.ZEN_MODE_OFF) {
                     newConfig.manualRule = null;
-                    // User deactivation of DND means just turning off the manual DND rule.
-                    // For API calls (different origin) keep old behavior of snoozing all rules.
                     for (ZenRule automaticRule : newConfig.automaticRules.values()) {
                         if (automaticRule.isAutomaticActive()) {
-                            automaticRule.snoozing = true;
+                            automaticRule.setConditionOverride(OVERRIDE_DEACTIVATE);
                         }
                     }
 
@@ -1602,7 +1632,7 @@
     }
 
     public void setManualZenRuleDeviceEffects(ZenDeviceEffects deviceEffects,
-            @ConfigChangeOrigin int origin, String reason, int callingUid) {
+            @ConfigOrigin int origin, String reason, int callingUid) {
         if (!Flags.modesUi()) {
             return;
         }
@@ -1623,13 +1653,11 @@
     void dump(ProtoOutputStream proto) {
         proto.write(ZenModeProto.ZEN_MODE, mZenMode);
         synchronized (mConfigLock) {
-            if (mConfig.manualRule != null) {
+            if (mConfig.isManualActive()) {
                 mConfig.manualRule.dumpDebug(proto, ZenModeProto.ENABLED_ACTIVE_CONDITIONS);
             }
             for (ZenRule rule : mConfig.automaticRules.values()) {
-                if (rule.enabled && rule.condition != null
-                        && rule.condition.state == STATE_TRUE
-                        && !rule.snoozing) {
+                if (rule.isAutomaticActive()) {
                     rule.dumpDebug(proto, ZenModeProto.ENABLED_ACTIVE_CONDITIONS);
                 }
             }
@@ -1673,7 +1701,11 @@
         if (config != null) {
             if (forRestore) {
                 config.user = userId;
-                if (!Flags.modesUi()) {
+                if (Flags.modesUi()) {
+                    if (config.manualRule != null) {
+                        config.manualRule.condition = null; // don't restore transient state
+                    }
+                } else {
                     config.manualRule = null;  // don't restore the manual rule
                 }
             }
@@ -1688,8 +1720,8 @@
                 for (ZenRule automaticRule : config.automaticRules.values()) {
                     if (forRestore) {
                         // don't restore transient state from restored automatic rules
-                        automaticRule.snoozing = false;
                         automaticRule.condition = null;
+                        automaticRule.resetConditionOverride();
                         automaticRule.creationTime = time;
                     }
 
@@ -1752,7 +1784,7 @@
             if (DEBUG) Log.d(TAG, reason);
             synchronized (mConfigLock) {
                 setConfigLocked(config, null,
-                        forRestore ? UPDATE_ORIGIN_RESTORE_BACKUP : UPDATE_ORIGIN_INIT, reason,
+                        forRestore ? ORIGIN_RESTORE_BACKUP : ORIGIN_INIT, reason,
                         Process.SYSTEM_UID);
             }
         }
@@ -1787,7 +1819,7 @@
     /**
      * Sets the global notification policy used for priority only do not disturb
      */
-    public void setNotificationPolicy(Policy policy, @ConfigChangeOrigin int origin,
+    public void setNotificationPolicy(Policy policy, @ConfigOrigin int origin,
             int callingUid) {
         synchronized (mConfigLock) {
             if (policy == null || mConfig == null) return;
@@ -1843,7 +1875,7 @@
             }
 
             if (!newConfig.equals(mConfig)) {
-                setConfigLocked(newConfig, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+                setConfigLocked(newConfig, null, ORIGIN_SYSTEM,
                         "cleanUpZenRules", Process.SYSTEM_UID);
             }
         }
@@ -1893,20 +1925,20 @@
 
     @GuardedBy("mConfigLock")
     private boolean setConfigLocked(ZenModeConfig config, ComponentName triggeringComponent,
-            @ConfigChangeOrigin int origin, String reason, int callingUid) {
+            @ConfigOrigin int origin, String reason, int callingUid) {
         return setConfigLocked(config, origin, reason, triggeringComponent, true /*setRingerMode*/,
                 callingUid);
     }
 
     void setConfig(ZenModeConfig config, ComponentName triggeringComponent,
-            @ConfigChangeOrigin int origin, String reason, int callingUid) {
+            @ConfigOrigin int origin, String reason, int callingUid) {
         synchronized (mConfigLock) {
             setConfigLocked(config, triggeringComponent, origin, reason, callingUid);
         }
     }
 
     @GuardedBy("mConfigLock")
-    private boolean setConfigLocked(ZenModeConfig config, @ConfigChangeOrigin int origin,
+    private boolean setConfigLocked(ZenModeConfig config, @ConfigOrigin int origin,
             String reason, ComponentName triggeringComponent, boolean setRingerMode,
             int callingUid) {
         final long identity = Binder.clearCallingIdentity();
@@ -1954,7 +1986,7 @@
      * If logging is enabled, will also request logging of the outcome of this change if needed.
      */
     @GuardedBy("mConfigLock")
-    private void updateConfigAndZenModeLocked(ZenModeConfig config, @ConfigChangeOrigin int origin,
+    private void updateConfigAndZenModeLocked(ZenModeConfig config, @ConfigOrigin int origin,
             String reason, boolean setRingerMode, int callingUid) {
         final boolean logZenModeEvents = mFlagResolver.isEnabled(
                 SystemUiSystemPropertiesFlags.NotificationFlags.LOG_DND_STATE_EVENTS);
@@ -1963,7 +1995,7 @@
                 mZenMode, mConfig, mConsolidatedPolicy);
         if (!config.equals(mConfig)) {
             // Schedule broadcasts. Cannot be sent during boot, though.
-            if (Flags.modesApi() && origin != UPDATE_ORIGIN_INIT) {
+            if (Flags.modesApi() && origin != ORIGIN_INIT) {
                 for (ZenRule rule : config.automaticRules.values()) {
                     ZenRule original = mConfig.automaticRules.get(rule.id);
                     if (original != null) {
@@ -2020,7 +2052,7 @@
 
     @VisibleForTesting
     @GuardedBy("mConfigLock")
-    protected void evaluateZenModeLocked(@ConfigChangeOrigin int origin, String reason,
+    protected void evaluateZenModeLocked(@ConfigOrigin int origin, String reason,
             boolean setRingerMode) {
         if (DEBUG) Log.d(TAG, "evaluateZenMode");
         if (mConfig == null) return;
@@ -2111,7 +2143,7 @@
     }
 
     @GuardedBy("mConfigLock")
-    private void updateAndApplyConsolidatedPolicyAndDeviceEffects(@ConfigChangeOrigin int origin,
+    private void updateAndApplyConsolidatedPolicyAndDeviceEffects(@ConfigOrigin int origin,
             String reason) {
         synchronized (mConfigLock) {
             if (mConfig == null) return;
@@ -2163,7 +2195,7 @@
         }
     }
 
-    private void applyConsolidatedDeviceEffects(@ConfigChangeOrigin int source) {
+    private void applyConsolidatedDeviceEffects(@ConfigOrigin int source) {
         if (!Flags.modesApi()) {
             return;
         }
@@ -2506,7 +2538,7 @@
             }
 
             if (newZen != -1) {
-                setManualZenMode(newZen, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+                setManualZenMode(newZen, null, ORIGIN_SYSTEM,
                         "ringerModeInternal", /* caller= */ null, /* setRingerMode= */ false,
                         Process.SYSTEM_UID);
             }
@@ -2551,7 +2583,7 @@
                     break;
             }
             if (newZen != -1) {
-                setManualZenMode(newZen, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+                setManualZenMode(newZen, null, ORIGIN_SYSTEM,
                         "ringerModeExternal", caller, false /*setRingerMode*/, Process.SYSTEM_UID);
             }
 
@@ -2708,15 +2740,33 @@
         }
     }
 
-    /** Checks that the {@code origin} supplied to a ZenModeHelper "API" method makes sense. */
-    private static void requirePublicOrigin(String method, @ConfigChangeOrigin int origin) {
+    /**
+     * Checks that the {@code origin} supplied to ZenModeHelper rule-management API methods
+     * ({@link #addAutomaticZenRule}, {@link #removeAutomaticZenRule}, etc, makes sense.
+     */
+    private static void checkManageRuleOrigin(String method, @ConfigOrigin int origin) {
         if (!Flags.modesApi()) {
             return;
         }
-        checkArgument(origin == UPDATE_ORIGIN_APP || origin == UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
-                        || origin == UPDATE_ORIGIN_USER,
-                "Expected one of UPDATE_ORIGIN_APP, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, or "
-                        + "UPDATE_ORIGIN_USER for %s, but received '%s'.",
+        checkArgument(origin == ORIGIN_APP || origin == ORIGIN_SYSTEM
+                        || origin == ORIGIN_USER_IN_SYSTEMUI,
+                "Expected one of ORIGIN_APP, ORIGIN_SYSTEM, or "
+                        + "ORIGIN_USER_IN_SYSTEMUI for %s, but received '%s'.",
+                method, origin);
+    }
+
+    /**
+     * Checks that the {@code origin} supplied to {@link #setAutomaticZenRuleState} overloads makes
+     * sense.
+     */
+    private static void checkSetRuleStateOrigin(String method, @ConfigOrigin int origin) {
+        if (!Flags.modesApi()) {
+            return;
+        }
+        checkArgument(origin == ORIGIN_APP || origin == ORIGIN_USER_IN_APP
+                        || origin == ORIGIN_SYSTEM || origin == ORIGIN_USER_IN_SYSTEMUI,
+                "Expected one of ORIGIN_APP, ORIGIN_USER_IN_APP, ORIGIN_SYSTEM, or "
+                        + "ORIGIN_USER_IN_SYSTEMUI for %s, but received '%s'.",
                 method, origin);
     }
 
@@ -2841,7 +2891,7 @@
             }
         }
 
-        private void postApplyDeviceEffects(@ConfigChangeOrigin int origin) {
+        private void postApplyDeviceEffects(@ConfigOrigin int origin) {
             removeMessages(MSG_APPLY_EFFECTS);
             sendMessage(obtainMessage(MSG_APPLY_EFFECTS, origin, 0));
         }
@@ -2862,7 +2912,7 @@
                     updateRingerAndAudio(/* shouldApplyToRinger= */ false);
                     break;
                 case MSG_APPLY_EFFECTS:
-                    @ConfigChangeOrigin int origin = msg.arg1;
+                    @ConfigOrigin int origin = msg.arg1;
                     applyConsolidatedDeviceEffects(origin);
                     break;
             }
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 46585a5..6303ecd 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -80,6 +80,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.KeepForWeakReference;
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.content.om.OverlayConfig;
@@ -1180,6 +1181,7 @@
         // intent, querying the PackageManagerService for the actual current
         // state may lead to contradictions within OMS. Better then to lag
         // behind until all pending intents have been processed.
+        @GuardedBy("itself")
         private final ArrayMap<String, PackageStateUsers> mCache = new ArrayMap<>();
         private final ArraySet<Integer> mInitializedUsers = new ArraySet<>();
 
@@ -1207,10 +1209,12 @@
             }
 
             final ArrayMap<String, PackageState> userPackages = new ArrayMap<>();
-            for (int i = 0, n = mCache.size(); i < n; i++) {
-                final PackageStateUsers pkg = mCache.valueAt(i);
-                if (pkg.mInstalledUsers.contains(userId)) {
-                    userPackages.put(mCache.keyAt(i), pkg.mPackageState);
+            synchronized (mCache) {
+                for (int i = 0, n = mCache.size(); i < n; i++) {
+                    final PackageStateUsers pkg = mCache.valueAt(i);
+                    if (pkg.mInstalledUsers.contains(userId)) {
+                        userPackages.put(mCache.keyAt(i), pkg.mPackageState);
+                    }
                 }
             }
             return userPackages;
@@ -1220,7 +1224,11 @@
         @Nullable
         public PackageState getPackageStateForUser(@NonNull final String packageName,
                 final int userId) {
-            final PackageStateUsers pkg = mCache.get(packageName);
+            final PackageStateUsers pkg;
+
+            synchronized (mCache) {
+                pkg = mCache.get(packageName);
+            }
             if (pkg != null && pkg.mInstalledUsers.contains(userId)) {
                 return pkg.mPackageState;
             }
@@ -1251,12 +1259,15 @@
         @NonNull
         private PackageState addPackageUser(@NonNull final PackageState pkg,
                 final int user) {
-            PackageStateUsers pkgUsers = mCache.get(pkg.getPackageName());
-            if (pkgUsers == null) {
-                pkgUsers = new PackageStateUsers(pkg);
-                mCache.put(pkg.getPackageName(), pkgUsers);
-            } else {
-                pkgUsers.mPackageState = pkg;
+            PackageStateUsers pkgUsers;
+            synchronized (mCache) {
+                pkgUsers = mCache.get(pkg.getPackageName());
+                if (pkgUsers == null) {
+                    pkgUsers = new PackageStateUsers(pkg);
+                    mCache.put(pkg.getPackageName(), pkgUsers);
+                } else {
+                    pkgUsers.mPackageState = pkg;
+                }
             }
             pkgUsers.mInstalledUsers.add(user);
             return pkgUsers.mPackageState;
@@ -1265,18 +1276,24 @@
 
         @NonNull
         private void removePackageUser(@NonNull final String packageName, final int user) {
-            final PackageStateUsers pkgUsers = mCache.get(packageName);
-            if (pkgUsers == null) {
-                return;
+            // synchronize should include the call to the other removePackageUser() method so that
+            // the access and modification happen under the same lock.
+            synchronized (mCache) {
+                final PackageStateUsers pkgUsers = mCache.get(packageName);
+                if (pkgUsers == null) {
+                    return;
+                }
+                removePackageUser(pkgUsers, user);
             }
-            removePackageUser(pkgUsers, user);
         }
 
         @NonNull
         private void removePackageUser(@NonNull final PackageStateUsers pkg, final int user) {
             pkg.mInstalledUsers.remove(user);
             if (pkg.mInstalledUsers.isEmpty()) {
-                mCache.remove(pkg.mPackageState.getPackageName());
+                synchronized (mCache) {
+                    mCache.remove(pkg.mPackageState.getPackageName());
+                }
             }
         }
 
@@ -1386,8 +1403,10 @@
         public void forgetAllPackageInfos(final int userId) {
             // Iterate in reverse order since removing the package in all users will remove the
             // package from the cache.
-            for (int i = mCache.size() - 1; i >= 0; i--) {
-                removePackageUser(mCache.valueAt(i), userId);
+            synchronized (mCache) {
+                for (int i = mCache.size() - 1; i >= 0; i--) {
+                    removePackageUser(mCache.valueAt(i), userId);
+                }
             }
         }
 
@@ -1405,22 +1424,23 @@
 
         public void dump(@NonNull final PrintWriter pw, @NonNull DumpState dumpState) {
             pw.println("AndroidPackage cache");
+            synchronized (mCache) {
+                if (!dumpState.isVerbose()) {
+                    pw.println(TAB1 + mCache.size() + " package(s)");
+                    return;
+                }
 
-            if (!dumpState.isVerbose()) {
-                pw.println(TAB1 + mCache.size() + " package(s)");
-                return;
-            }
+                if (mCache.size() == 0) {
+                    pw.println(TAB1 + "<empty>");
+                    return;
+                }
 
-            if (mCache.size() == 0) {
-                pw.println(TAB1 + "<empty>");
-                return;
-            }
-
-            for (int i = 0, n = mCache.size(); i < n; i++) {
-                final String packageName = mCache.keyAt(i);
-                final PackageStateUsers pkg = mCache.valueAt(i);
-                pw.print(TAB1 + packageName + ": " + pkg.mPackageState + " users=");
-                pw.println(TextUtils.join(", ", pkg.mInstalledUsers));
+                for (int i = 0, n = mCache.size(); i < n; i++) {
+                    final String packageName = mCache.keyAt(i);
+                    final PackageStateUsers pkg = mCache.valueAt(i);
+                    pw.print(TAB1 + packageName + ": " + pkg.mPackageState + " users=");
+                    pw.println(TextUtils.join(", ", pkg.mInstalledUsers));
+                }
             }
         }
     }
diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java
index 8b72138..29f8243 100644
--- a/services/core/java/com/android/server/pm/AppDataHelper.java
+++ b/services/core/java/com/android/server/pm/AppDataHelper.java
@@ -127,7 +127,7 @@
         }
 
         // TODO(b/211761016): should we still create the profile dirs?
-        if (ps.getPkg() != null && !shouldHaveAppStorage(ps.getPkg())) {
+        if (!shouldHaveAppStorage(ps)) {
             Slog.w(TAG, "Skipping preparing app data for " + ps.getPackageName());
             return;
         }
@@ -172,19 +172,19 @@
     }
 
     private void prepareAppDataAndMigrate(@NonNull Installer.Batch batch,
-            @NonNull AndroidPackage pkg, @UserIdInt int userId,
+            @NonNull PackageStateInternal psi, @UserIdInt int userId,
             @StorageManager.StorageFlags int flags, boolean maybeMigrateAppData) {
-        if (pkg == null) {
+        if (psi == null || psi.getPkg() == null) {
             Slog.wtf(TAG, "Package was null!", new Throwable());
             return;
         }
-        if (!shouldHaveAppStorage(pkg)) {
-            Slog.w(TAG, "Skipping preparing app data for " + pkg.getPackageName());
+        if (!shouldHaveAppStorage(psi)) {
+            Slog.w(TAG, "Skipping preparing app data for " + psi.getPackageName());
             return;
         }
         final PackageSetting ps;
         synchronized (mPm.mLock) {
-            ps = mPm.mSettings.getPackageLPr(pkg.getPackageName());
+            ps = mPm.mSettings.getPackageLPr(psi.getPackageName());
         }
         prepareAppData(batch, ps, Process.INVALID_UID, userId, flags).thenRun(() -> {
             // Note: this code block is executed with the Installer lock
@@ -449,7 +449,7 @@
             }
 
             if (ps.getUserStateOrDefault(userId).isInstalled()) {
-                prepareAppDataAndMigrate(batch, ps.getPkg(), userId, flags, migrateAppData);
+                prepareAppDataAndMigrate(batch, ps, userId, flags, migrateAppData);
                 preparedCount++;
             }
         }
@@ -484,8 +484,7 @@
                             + " or was deleted without DELETE_KEEP_DATA",
                     PackageManagerException.INTERNAL_ERROR_STORAGE_INVALID_NOT_INSTALLED_FOR_USER);
         }
-        if (packageState.getPkg() != null
-                && !shouldHaveAppStorage(packageState.getPkg())) {
+        if (!shouldHaveAppStorage(packageState)) {
             throw PackageManagerException.ofInternalError(
                     "Package " + packageName + " shouldn't have storage",
                     PackageManagerException.INTERNAL_ERROR_STORAGE_INVALID_SHOULD_NOT_HAVE_STORAGE);
@@ -535,8 +534,7 @@
                 if (packageStateInternal != null
                         && packageStateInternal.getUserStateOrDefault(
                                 UserHandle.USER_SYSTEM).isInstalled()) {
-                    AndroidPackage pkg = packageStateInternal.getPkg();
-                    prepareAppDataAndMigrate(batch, pkg,
+                    prepareAppDataAndMigrate(batch, packageStateInternal,
                             UserHandle.USER_SYSTEM, storageFlags, true /* maybeMigrateAppData */);
                     count++;
                 }
@@ -637,12 +635,16 @@
     }
 
     /**
-     * Returns {@code true} if app's internal storage should be created for this {@code pkg}.
+     * Returns {@code true} if app's internal storage should be created for this {@code ps}.
      */
-    private boolean shouldHaveAppStorage(AndroidPackage pkg) {
+    private boolean shouldHaveAppStorage(PackageStateInternal ps) {
+        if (ps.getPkg() == null) {
+            // Keeps the legacy behavior
+            return true;
+        }
         PackageManager.Property noAppDataProp =
-                pkg.getProperties().get(PackageManager.PROPERTY_NO_APP_DATA_STORAGE);
-        return (noAppDataProp == null || !noAppDataProp.getBoolean()) && pkg.getUid() >= 0;
+                ps.getPkg().getProperties().get(PackageManager.PROPERTY_NO_APP_DATA_STORAGE);
+        return (noAppDataProp == null || !noAppDataProp.getBoolean()) && ps.getAppId() >= 0;
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index b56e119..8398ffc 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -690,7 +690,13 @@
     public void deletePackageVersionedInternal(VersionedPackage versionedPackage,
             final IPackageDeleteObserver2 observer, final int userId, final int deleteFlags,
             final boolean allowSilentUninstall) {
-        final int callingUid = Binder.getCallingUid();
+        deletePackageVersionedInternal(versionedPackage, observer, userId, deleteFlags,
+                Binder.getCallingUid(), allowSilentUninstall);
+    }
+
+    public void deletePackageVersionedInternal(VersionedPackage versionedPackage,
+            final IPackageDeleteObserver2 observer, final int userId, final int deleteFlags,
+            final int callingUid, final boolean allowSilentUninstall) {
         mPm.mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.DELETE_PACKAGES, null);
         final Computer snapshot = mPm.snapshotComputer();
@@ -720,16 +726,22 @@
         final String internalPackageName =
                 snapshot.resolveInternalPackageName(packageName, versionCode);
 
-        final int uid = Binder.getCallingUid();
         if (!isOrphaned(snapshot, internalPackageName)
                 && !allowSilentUninstall
                 && !isCallerAllowedToSilentlyUninstall(
-                        snapshot, uid, internalPackageName, userId)) {
+                        snapshot, callingUid, internalPackageName, userId)) {
             mPm.mHandler.post(() -> {
                 try {
                     final Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE);
                     intent.setData(Uri.fromParts(PACKAGE_SCHEME, packageName, null));
-                    intent.putExtra(PackageInstaller.EXTRA_CALLBACK, observer.asBinder());
+                    intent.putExtra(PackageInstaller.EXTRA_CALLBACK,
+                            new PackageManager.UninstallCompleteCallback(observer.asBinder()));
+                    if ((deleteFlags & PackageManager.DELETE_ARCHIVE) != 0) {
+                        // Delete flags are passed to the uninstaller activity so it can be
+                        // preserved in the follow-up uninstall operation after the user
+                        // confirmation
+                        intent.putExtra(PackageInstaller.EXTRA_DELETE_FLAGS, deleteFlags);
+                    }
                     observer.onUserActionRequired(intent);
                 } catch (RemoteException re) {
                 }
@@ -738,7 +750,7 @@
         }
         final boolean deleteAllUsers = (deleteFlags & PackageManager.DELETE_ALL_USERS) != 0;
         final int[] users = deleteAllUsers ? mUserManagerInternal.getUserIds() : new int[]{userId};
-        if (UserHandle.getUserId(uid) != userId || (deleteAllUsers && users.length > 1)) {
+        if (UserHandle.getUserId(callingUid) != userId || (deleteAllUsers && users.length > 1)) {
             mPm.mContext.enforceCallingOrSelfPermission(
                     android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
                     "deletePackage for user " + userId);
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index a0d5ea8..22b4d5d 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -686,6 +686,9 @@
                     (installFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
             final boolean fullApp =
                     (installFlags & PackageManager.INSTALL_FULL_APP) != 0;
+            final boolean isPackageDeviceAdmin = mPm.isPackageDeviceAdmin(packageName, userId);
+            final boolean isProtectedPackage = mPm.mProtectedPackages != null
+                    && mPm.mProtectedPackages.isPackageStateProtected(userId, packageName);
 
             // writer
             synchronized (mPm.mLock) {
@@ -694,7 +697,8 @@
                 if (pkgSetting == null || pkgSetting.getPkg() == null) {
                     return Pair.create(PackageManager.INSTALL_FAILED_INVALID_URI, intentSender);
                 }
-                if (instantApp && (pkgSetting.isSystem() || pkgSetting.isUpdatedSystemApp())) {
+                if (instantApp && (pkgSetting.isSystem() || pkgSetting.isUpdatedSystemApp()
+                        || isPackageDeviceAdmin || isProtectedPackage)) {
                     return Pair.create(PackageManager.INSTALL_FAILED_INVALID_URI, intentSender);
                 }
                 if (!snapshot.canViewInstantApps(callingUid, UserHandle.getUserId(callingUid))) {
@@ -1801,26 +1805,37 @@
                                 oldPackageState.getRestrictUpdateHash());
                     }
 
-                    if (oldPackage != null) {
-                        // APK should not change its sharedUserId declarations
-                        final var oldSharedUid = oldPackage.getSharedUserId() != null
-                                ? oldPackage.getSharedUserId() : "<nothing>";
-                        final var newSharedUid = parsedPackage.getSharedUserId() != null
-                                ? parsedPackage.getSharedUserId() : "<nothing>";
-                        if (!oldSharedUid.equals(newSharedUid)) {
-                            throw new PrepareFailure(INSTALL_FAILED_UID_CHANGED,
-                                    "Package " + parsedPackage.getPackageName()
-                                            + " shared user changed from "
-                                            + oldSharedUid + " to " + newSharedUid);
+                    // APK should not change its sharedUserId declarations
+                    final String oldSharedUid;
+                    if (mPm.mSettings.getSharedUserSettingLPr(oldPackageState) != null) {
+                        oldSharedUid = mPm.mSettings.getSharedUserSettingLPr(oldPackageState).name;
+                    } else {
+                        oldSharedUid = "<nothing>";
+                    }
+                    String newSharedUid = parsedPackage.getSharedUserId() != null
+                            ? parsedPackage.getSharedUserId() : "<nothing>";
+                    // If the previously installed app version doesn't have sharedUserSetting,
+                    // check that the new apk either doesn't have sharedUserId or it is leaving one.
+                    // If it contains sharedUserId but it is also leaving it, it's ok to proceed.
+                    if (oldSharedUid.equals("<nothing>")) {
+                        if (parsedPackage.isLeavingSharedUser()) {
+                            newSharedUid = "<nothing>";
                         }
+                    }
 
-                        // APK should not re-join shared UID
-                        if (oldPackage.isLeavingSharedUser()
-                                && !parsedPackage.isLeavingSharedUser()) {
-                            throw new PrepareFailure(INSTALL_FAILED_UID_CHANGED,
-                                    "Package " + parsedPackage.getPackageName()
-                                            + " attempting to rejoin " + newSharedUid);
-                        }
+                    if (!oldSharedUid.equals(newSharedUid)) {
+                        throw new PrepareFailure(INSTALL_FAILED_UID_CHANGED,
+                                "Package " + parsedPackage.getPackageName()
+                                        + " shared user changed from "
+                                        + oldSharedUid + " to " + newSharedUid);
+                    }
+
+                    // APK should not re-join shared UID
+                    if (oldPackageState.isLeavingSharedUser()
+                            && !parsedPackage.isLeavingSharedUser()) {
+                        throw new PrepareFailure(INSTALL_FAILED_UID_CHANGED,
+                                "Package " + parsedPackage.getPackageName()
+                                        + " attempting to rejoin " + newSharedUid);
                     }
 
                     // In case of rollback, remember per-user/profile install state
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 7156795..be6fa14 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -1438,7 +1438,8 @@
         if (mContext.checkPermission(Manifest.permission.DELETE_PACKAGES, callingPid, callingUid)
                 == PackageManager.PERMISSION_GRANTED) {
             // Sweet, call straight through!
-            mPm.deletePackageVersioned(versionedPackage, adapter.getBinder(), userId, flags);
+            mPm.deletePackageVersioned(versionedPackage, adapter.getBinder(), userId, flags,
+                    callingUid);
         } else if (canSilentlyInstallPackage) {
             // Allow the device owner and affiliated profile owner to silently delete packages
             // Need to clear the calling identity to get DELETE_PACKAGES permission
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 33ca8a8..20859da 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -3323,9 +3323,17 @@
     }
 
     public void deletePackageVersioned(VersionedPackage versionedPackage,
+            final IPackageDeleteObserver2 observer, final int userId, final int deleteFlags,
+            final int callingUid) {
+        mDeletePackageHelper.deletePackageVersionedInternal(
+                versionedPackage, observer, userId, deleteFlags, callingUid,
+                /* allowSilentUninstall= */ false);
+    }
+
+    public void deletePackageVersioned(VersionedPackage versionedPackage,
             final IPackageDeleteObserver2 observer, final int userId, final int deleteFlags) {
         mDeletePackageHelper.deletePackageVersionedInternal(
-                versionedPackage, observer, userId, deleteFlags, false);
+                versionedPackage, observer, userId, deleteFlags, /* allowSilentUninstall= */ false);
     }
 
     boolean isCallerVerifier(@NonNull Computer snapshot, int callingUid) {
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 9f10e01..d374142 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -98,6 +98,7 @@
                 SCANNED_AS_STOPPED_SYSTEM_APP,
                 PENDING_RESTORE,
                 DEBUGGABLE,
+                IS_LEAVING_SHARED_USER,
         })
         public @interface Flags {
         }
@@ -107,6 +108,7 @@
         private static final int SCANNED_AS_STOPPED_SYSTEM_APP = 1 << 3;
         private static final int PENDING_RESTORE = 1 << 4;
         private static final int DEBUGGABLE = 1 << 5;
+        private static final int IS_LEAVING_SHARED_USER = 1 << 6;
     }
     private int mBooleans;
 
@@ -595,6 +597,20 @@
     }
 
     /**
+     * @see PackageState#isLeavingSharedUser
+     */
+    public PackageSetting setLeavingSharedUser(boolean value) {
+        setBoolean(Booleans.IS_LEAVING_SHARED_USER, value);
+        onChanged();
+        return this;
+    }
+
+    @Override
+    public boolean isLeavingSharedUser() {
+        return getBoolean(Booleans.IS_LEAVING_SHARED_USER);
+    }
+
+    /**
      * @see AndroidPackage#getBaseRevisionCode
      */
     public PackageSetting setBaseRevisionCode(int value) {
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index 7afc358..26da84f 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -435,7 +435,7 @@
 
             // Preserve split apk information for downgrade check with DELETE_KEEP_DATA and archived
             // app cases
-            if (deletedPkg.getSplitNames() != null) {
+            if (deletedPkg != null && deletedPkg.getSplitNames() != null) {
                 deletedPs.setSplitNames(deletedPkg.getSplitNames());
                 deletedPs.setSplitRevisionCodes(deletedPkg.getSplitRevisionCodes());
             }
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index 95561f5f..61fddba 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -482,6 +482,7 @@
                             + " to " + volumeUuid);
             pkgSetting.setVolumeUuid(volumeUuid);
         }
+        pkgSetting.setLeavingSharedUser(parsedPackage.isLeavingSharedUser());
 
         SharedLibraryInfo sdkLibraryInfo = null;
         if (!TextUtils.isEmpty(parsedPackage.getSdkLibraryName())) {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index b7dfd8d..55280b4 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -2585,12 +2585,31 @@
         boolean optional = parser.getAttributeBoolean(null, ATTR_OPTIONAL, true);
 
         if (libName != null && libVersion >= 0) {
+            final int beforeUsesSdkLibrariesLength = outPs.getUsesSdkLibraries().length;
+            // If the lib already exists in the outPs#getUsesSdkLibraries, don't add it
+            // into the array and update its information below
             outPs.setUsesSdkLibraries(ArrayUtils.appendElement(String.class,
                     outPs.getUsesSdkLibraries(), libName));
-            outPs.setUsesSdkLibrariesVersionsMajor(ArrayUtils.appendLong(
-                    outPs.getUsesSdkLibrariesVersionsMajor(), libVersion));
-            outPs.setUsesSdkLibrariesOptional(ArrayUtils.appendBoolean(
-                    outPs.getUsesSdkLibrariesOptional(), optional));
+
+            // If the lib has already been added before, update the other information
+            final int afterUsesSdkLibrariesLength = outPs.getUsesSdkLibraries().length;
+            if (beforeUsesSdkLibrariesLength == afterUsesSdkLibrariesLength) {
+                final int index = ArrayUtils.indexOf(outPs.getUsesSdkLibraries(), libName);
+                final long[] usesSdkLibrariesVersionsMajor =
+                        outPs.getUsesSdkLibrariesVersionsMajor();
+                usesSdkLibrariesVersionsMajor[index] = libVersion;
+                outPs.setUsesSdkLibrariesVersionsMajor(usesSdkLibrariesVersionsMajor);
+
+                final boolean[] usesSdkLibrariesOptional = outPs.getUsesSdkLibrariesOptional();
+                usesSdkLibrariesOptional[index] = optional;
+                outPs.setUsesSdkLibrariesOptional(usesSdkLibrariesOptional);
+            } else {
+                outPs.setUsesSdkLibrariesVersionsMajor(ArrayUtils.appendLong(
+                        outPs.getUsesSdkLibrariesVersionsMajor(), libVersion,
+                        /* allowDuplicates= */ true));
+                outPs.setUsesSdkLibrariesOptional(ArrayUtils.appendBooleanDuplicatesAllowed(
+                        outPs.getUsesSdkLibrariesOptional(), optional));
+            }
         }
 
         XmlUtils.skipCurrentTag(parser);
@@ -2602,10 +2621,24 @@
         long libVersion = parser.getAttributeLong(null, ATTR_VERSION, -1);
 
         if (libName != null && libVersion >= 0) {
+            final int beforeUsesStaticLibrariesLength = outPs.getUsesStaticLibraries().length;
+            // If the lib already exists in the outPs#getUsesStaticLibraries, don't add it
+            // into the array and update its information below
             outPs.setUsesStaticLibraries(ArrayUtils.appendElement(String.class,
                     outPs.getUsesStaticLibraries(), libName));
-            outPs.setUsesStaticLibrariesVersions(ArrayUtils.appendLong(
-                    outPs.getUsesStaticLibrariesVersions(), libVersion));
+
+            // If the lib has already been added before, update the version
+            final int afterUsesStaticLibrariesLength = outPs.getUsesStaticLibraries().length;
+            if (beforeUsesStaticLibrariesLength == afterUsesStaticLibrariesLength) {
+                final int index = ArrayUtils.indexOf(outPs.getUsesStaticLibraries(), libName);
+                final long[] usesStaticLibrariesVersions = outPs.getUsesStaticLibrariesVersions();
+                usesStaticLibrariesVersions[index] = libVersion;
+                outPs.setUsesStaticLibrariesVersions(usesStaticLibrariesVersions);
+            } else {
+                outPs.setUsesStaticLibrariesVersions(ArrayUtils.appendLong(
+                        outPs.getUsesStaticLibrariesVersions(), libVersion,
+                        /* allowDuplicates= */ true));
+            }
         }
 
         XmlUtils.skipCurrentTag(parser);
diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING
index c40608d..c95d88e 100644
--- a/services/core/java/com/android/server/pm/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/TEST_MAPPING
@@ -163,6 +163,22 @@
     },
     {
       "name": "CtsUpdateOwnershipEnforcementTestCases"
+    },
+    {
+      "name": "CtsPackageInstallerCUJTestCases",
+      "file_patterns": [
+        "core/java/.*Install.*",
+        "services/core/.*Install.*",
+        "services/core/java/com/android/server/pm/.*"
+      ],
+      "options":[
+          {
+              "exclude-annotation":"androidx.test.filters.FlakyTest"
+          },
+          {
+              "exclude-annotation":"org.junit.Ignore"
+          }
+      ]
     }
   ],
   "imports": [
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 829ee27..2b639fa 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -379,6 +379,10 @@
 
     /** Count down latch to wait while boot user is not set.*/
     private final CountDownLatch mBootUserLatch = new CountDownLatch(1);
+
+    /** Current boot phase. */
+    private @SystemService.BootPhase int mCurrentBootPhase;
+
     /**
      * Internal non-parcelable wrapper for UserInfo that is not exposed to other system apps.
      */
@@ -968,6 +972,7 @@
 
         @Override
         public void onBootPhase(int phase) {
+            mUms.mCurrentBootPhase = phase;
             if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
                 mUms.cleanupPartialUsers();
 
@@ -2105,6 +2110,10 @@
     @Override
     public void setUserAdmin(@UserIdInt int userId) {
         checkManageUserAndAcrossUsersFullPermission("set user admin");
+        if (Flags.unicornModeRefactoringForHsumReadOnly()) {
+            checkAdminStatusChangeAllowed(userId);
+        }
+
         mUserJourneyLogger.logUserJourneyBegin(userId, USER_JOURNEY_GRANT_ADMIN);
         UserData user;
         synchronized (mPackagesLock) {
@@ -2133,6 +2142,10 @@
     @Override
     public void revokeUserAdmin(@UserIdInt int userId) {
         checkManageUserAndAcrossUsersFullPermission("revoke admin privileges");
+        if (Flags.unicornModeRefactoringForHsumReadOnly()) {
+            checkAdminStatusChangeAllowed(userId);
+        }
+
         mUserJourneyLogger.logUserJourneyBegin(userId, USER_JOURNEY_REVOKE_ADMIN);
         UserData user;
         synchronized (mPackagesLock) {
@@ -4065,6 +4078,26 @@
         }
     }
 
+    /**
+     * Checks if changing the admin status of a target user is restricted
+     * due to the DISALLOW_GRANT_ADMIN restriction. If either the calling
+     * user or the target user has this restriction, a SecurityException
+     * is thrown.
+     *
+     * @param targetUser The user ID of the user whose admin status is being
+     * considered for change.
+     * @throws SecurityException if the admin status change is restricted due
+     * to the DISALLOW_GRANT_ADMIN restriction.
+     */
+    private void checkAdminStatusChangeAllowed(int targetUser) {
+        if (hasUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, UserHandle.getCallingUserId())
+                || hasUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, targetUser)) {
+            throw new SecurityException(
+                    "Admin status change is restricted. The DISALLOW_GRANT_ADMIN "
+                            + "restriction is applied either on the current or the target user.");
+        }
+    }
+
     @GuardedBy({"mPackagesLock"})
     private void writeBitmapLP(UserInfo info, Bitmap bitmap) {
         try {
@@ -5443,6 +5476,13 @@
 
         enforceUserRestriction(restriction, UserHandle.getCallingUserId(),
                 "Cannot add user");
+        if (Flags.unicornModeRefactoringForHsumReadOnly()) {
+            if ((flags & UserInfo.FLAG_ADMIN) != 0) {
+                enforceUserRestriction(UserManager.DISALLOW_GRANT_ADMIN,
+                        UserHandle.getCallingUserId(), "Cannot create ADMIN user");
+            }
+        }
+
         return createUserInternalUnchecked(name, userType, flags, parentId,
                 /* preCreate= */ false, disallowedPackages, /* token= */ null);
     }
@@ -6169,6 +6209,11 @@
             Slog.w(LOG_TAG, "Cannot remove user. " + restriction + " is enabled.");
             return false;
         }
+        if (mCurrentBootPhase < SystemService.PHASE_ACTIVITY_MANAGER_READY) {
+            Slog.w(LOG_TAG, "Cannot remove user, removeUser is called too early during boot. "
+                + "ActivityManager is not ready yet.");
+            return false;
+        }
         return removeUserWithProfilesUnchecked(userId);
     }
 
diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java
index 5876188..bbc17c8 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageState.java
@@ -488,4 +488,10 @@
      * @hide
      */
     boolean isScannedAsStoppedSystemApp();
+
+    /**
+     * see AndroidPackage#isLeavingSharedUser()
+     * @hide
+     */
+    boolean isLeavingSharedUser();
 }
diff --git a/services/core/java/com/android/server/policy/ModifierShortcutManager.java b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
index cefecbc..5a45186 100644
--- a/services/core/java/com/android/server/policy/ModifierShortcutManager.java
+++ b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
@@ -30,6 +30,7 @@
 import android.content.res.XmlResourceParser;
 import android.graphics.drawable.Icon;
 import android.hardware.input.InputManager;
+import android.hardware.input.KeyboardSystemShortcut;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -39,7 +40,6 @@
 import android.util.LongSparseArray;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.view.InputDevice;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
 import android.view.KeyboardShortcutGroup;
@@ -49,8 +49,8 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.IShortcutService;
 import com.android.internal.util.XmlUtils;
-import com.android.server.input.KeyboardMetricsCollector;
-import com.android.server.input.KeyboardMetricsCollector.KeyboardLogEvent;
+import com.android.server.LocalServices;
+import com.android.server.input.InputManagerInternal;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -61,6 +61,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Manages quick launch shortcuts by:
@@ -123,6 +124,7 @@
 
     private final Context mContext;
     private final Handler mHandler;
+    private final InputManagerInternal mInputManagerInternal;
     private boolean mSearchKeyShortcutPending = false;
     private boolean mConsumeSearchKeyUp = true;
     private UserHandle mCurrentUser;
@@ -136,6 +138,7 @@
                     mRoleIntents.remove(roleName);
                 }, UserHandle.ALL);
         mCurrentUser = currentUser;
+        mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
         loadShortcuts();
     }
 
@@ -473,7 +476,7 @@
                             + "keyCode=" + KeyEvent.keyCodeToString(keyCode) + ","
                             + " category=" + category + " role=" + role);
                 }
-                logKeyboardShortcut(keyEvent, KeyboardLogEvent.getLogEventFromIntent(intent));
+                notifyKeyboardShortcutTriggered(keyEvent, getSystemShortcutFromIntent(intent));
                 return true;
             } else {
                 return false;
@@ -494,22 +497,19 @@
                         + "the activity to which it is registered was not found: "
                         + "META+ or SEARCH" + KeyEvent.keyCodeToString(keyCode));
             }
-            logKeyboardShortcut(keyEvent, KeyboardLogEvent.getLogEventFromIntent(shortcutIntent));
+            notifyKeyboardShortcutTriggered(keyEvent, getSystemShortcutFromIntent(shortcutIntent));
             return true;
         }
         return false;
     }
 
-    private void logKeyboardShortcut(KeyEvent event, KeyboardLogEvent logEvent) {
-        mHandler.post(() -> handleKeyboardLogging(event, logEvent));
-    }
-
-    private void handleKeyboardLogging(KeyEvent event, KeyboardLogEvent logEvent) {
-        final InputManager inputManager = mContext.getSystemService(InputManager.class);
-        final InputDevice inputDevice = inputManager != null
-                ? inputManager.getInputDevice(event.getDeviceId()) : null;
-        KeyboardMetricsCollector.logKeyboardSystemsEventReportedAtom(inputDevice,
-                logEvent, event.getMetaState(), event.getKeyCode());
+    private void notifyKeyboardShortcutTriggered(KeyEvent event,
+            @KeyboardSystemShortcut.SystemShortcut int systemShortcut) {
+        if (systemShortcut == KeyboardSystemShortcut.SYSTEM_SHORTCUT_UNSPECIFIED) {
+            return;
+        }
+        mInputManagerInternal.notifyKeyboardShortcutTriggered(event.getDeviceId(),
+                new int[]{event.getKeyCode()}, event.getMetaState(), systemShortcut);
     }
 
     /**
@@ -708,6 +708,97 @@
         return context.getString(resid);
     };
 
+
+    /**
+     * Find Keyboard shortcut event corresponding to intent filter category. Returns
+     * {@code SYSTEM_SHORTCUT_UNSPECIFIED if no matching event found}
+     */
+    @KeyboardSystemShortcut.SystemShortcut
+    private static int getSystemShortcutFromIntent(Intent intent) {
+        Intent selectorIntent = intent.getSelector();
+        if (selectorIntent != null) {
+            Set<String> selectorCategories = selectorIntent.getCategories();
+            if (selectorCategories != null && !selectorCategories.isEmpty()) {
+                for (String intentCategory : selectorCategories) {
+                    int systemShortcut = getEventFromSelectorCategory(intentCategory);
+                    if (systemShortcut == KeyboardSystemShortcut.SYSTEM_SHORTCUT_UNSPECIFIED) {
+                        continue;
+                    }
+                    return systemShortcut;
+                }
+            }
+        }
+
+        // The shortcut may be targeting a system role rather than using an intent selector,
+        // so check for that.
+        String role = intent.getStringExtra(ModifierShortcutManager.EXTRA_ROLE);
+        if (!TextUtils.isEmpty(role)) {
+            return getLogEventFromRole(role);
+        }
+
+        Set<String> intentCategories = intent.getCategories();
+        if (intentCategories == null || intentCategories.isEmpty()
+                || !intentCategories.contains(Intent.CATEGORY_LAUNCHER)) {
+            return KeyboardSystemShortcut.SYSTEM_SHORTCUT_UNSPECIFIED;
+        }
+        if (intent.getComponent() == null) {
+            return KeyboardSystemShortcut.SYSTEM_SHORTCUT_UNSPECIFIED;
+        }
+
+        // TODO(b/280423320): Add new field package name associated in the
+        //  KeyboardShortcutEvent atom and log it accordingly.
+        return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME;
+    }
+
+    @KeyboardSystemShortcut.SystemShortcut
+    private static int getEventFromSelectorCategory(String category) {
+        switch (category) {
+            case Intent.CATEGORY_APP_BROWSER:
+                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER;
+            case Intent.CATEGORY_APP_EMAIL:
+                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL;
+            case Intent.CATEGORY_APP_CONTACTS:
+                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS;
+            case Intent.CATEGORY_APP_CALENDAR:
+                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR;
+            case Intent.CATEGORY_APP_CALCULATOR:
+                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR;
+            case Intent.CATEGORY_APP_MUSIC:
+                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC;
+            case Intent.CATEGORY_APP_MAPS:
+                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS;
+            case Intent.CATEGORY_APP_MESSAGING:
+                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING;
+            case Intent.CATEGORY_APP_GALLERY:
+                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY;
+            case Intent.CATEGORY_APP_FILES:
+                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES;
+            case Intent.CATEGORY_APP_WEATHER:
+                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER;
+            case Intent.CATEGORY_APP_FITNESS:
+                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS;
+            default:
+                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_UNSPECIFIED;
+        }
+    }
+
+    /**
+     * Find KeyboardLogEvent corresponding to the provide system role name.
+     * Returns {@code null} if no matching event found.
+     */
+    @KeyboardSystemShortcut.SystemShortcut
+    private static int getLogEventFromRole(String role) {
+        if (RoleManager.ROLE_BROWSER.equals(role)) {
+            return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER;
+        } else if (RoleManager.ROLE_SMS.equals(role)) {
+            return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING;
+        } else {
+            Log.w(TAG, "Keyboard shortcut to launch "
+                    + role + " not supported for logging");
+            return KeyboardSystemShortcut.SYSTEM_SHORTCUT_UNSPECIFIED;
+        }
+    }
+
     void dump(String prefix, PrintWriter pw) {
         IndentingPrintWriter ipw = new IndentingPrintWriter(pw,  "  ", prefix);
         ipw.println("ModifierShortcutManager shortcuts:");
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 21d6c64..720c1c2 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -139,6 +139,7 @@
 import android.hardware.hdmi.HdmiPlaybackClient;
 import android.hardware.hdmi.HdmiPlaybackClient.OneTouchPlayCallback;
 import android.hardware.input.InputManager;
+import android.hardware.input.KeyboardSystemShortcut;
 import android.media.AudioManager;
 import android.media.AudioManagerInternal;
 import android.media.AudioSystem;
@@ -164,8 +165,6 @@
 import android.os.Trace;
 import android.os.UEventObserver;
 import android.os.UserHandle;
-import android.os.VibrationAttributes;
-import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.provider.DeviceConfig;
 import android.provider.MediaStore;
@@ -229,8 +228,6 @@
 import com.android.server.SystemServiceManager;
 import com.android.server.UiThread;
 import com.android.server.input.InputManagerInternal;
-import com.android.server.input.KeyboardMetricsCollector;
-import com.android.server.input.KeyboardMetricsCollector.KeyboardLogEvent;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.policy.KeyCombinationManager.TwoKeysCombinationRule;
@@ -238,8 +235,6 @@
 import com.android.server.policy.keyguard.KeyguardServiceDelegate.DrawnListener;
 import com.android.server.policy.keyguard.KeyguardStateMonitor.StateCallback;
 import com.android.server.statusbar.StatusBarManagerInternal;
-import com.android.server.vibrator.HapticFeedbackVibrationProvider;
-import com.android.server.vibrator.VibratorFrameworkStatsLogger;
 import com.android.server.vr.VrManagerInternal;
 import com.android.server.wallpaper.WallpaperManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
@@ -462,7 +457,6 @@
     PackageManager mPackageManager;
     SideFpsEventHandler mSideFpsEventHandler;
     LockPatternUtils mLockPatternUtils;
-    private HapticFeedbackVibrationProvider mHapticFeedbackVibrationProvider;
     private boolean mHasFeatureAuto;
     private boolean mHasFeatureWatch;
     private boolean mHasFeatureLeanback;
@@ -737,7 +731,6 @@
     private static final int MSG_LAUNCH_ASSIST = 23;
     private static final int MSG_RINGER_TOGGLE_CHORD = 24;
     private static final int MSG_SWITCH_KEYBOARD_LAYOUT = 25;
-    private static final int MSG_LOG_KEYBOARD_SYSTEM_EVENT = 26;
     private static final int MSG_SET_DEFERRED_KEY_ACTIONS_EXECUTABLE = 27;
 
     private class PolicyHandler extends Handler {
@@ -825,9 +818,6 @@
                     handleSwitchKeyboardLayout(object.keyEvent, object.direction,
                             object.focusedToken);
                     break;
-                case MSG_LOG_KEYBOARD_SYSTEM_EVENT:
-                    handleKeyboardSystemEvent(KeyboardLogEvent.from(msg.arg1), (KeyEvent) msg.obj);
-                    break;
                 case MSG_SET_DEFERRED_KEY_ACTIONS_EXECUTABLE:
                     final int keyCode = msg.arg1;
                     final long downTime = (Long) msg.obj;
@@ -1829,7 +1819,7 @@
     }
 
     private void handleShortPressOnHome(KeyEvent event) {
-        logKeyboardSystemsEvent(event, KeyboardLogEvent.HOME);
+        notifyKeyboardShortcutTriggered(event, KeyboardSystemShortcut.SYSTEM_SHORTCUT_HOME);
 
         // Turn on the connected TV and switch HDMI input if we're a HDMI playback device.
         final HdmiControl hdmiControl = getHdmiControl();
@@ -2063,7 +2053,8 @@
             }
             switch (mDoubleTapOnHomeBehavior) {
                 case DOUBLE_TAP_HOME_RECENT_SYSTEM_UI:
-                    logKeyboardSystemsEvent(event, KeyboardLogEvent.APP_SWITCH);
+                    notifyKeyboardShortcutTriggered(event,
+                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_APP_SWITCH);
                     mHomeConsumed = true;
                     toggleRecentApps();
                     break;
@@ -2091,19 +2082,23 @@
                 case LONG_PRESS_HOME_ALL_APPS:
                     if (mHasFeatureLeanback) {
                         launchAllAppsAction();
-                        logKeyboardSystemsEvent(event, KeyboardLogEvent.ALL_APPS);
+                        notifyKeyboardShortcutTriggered(event,
+                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_ALL_APPS);
                     } else {
                         launchAllAppsViaA11y();
-                        logKeyboardSystemsEvent(event, KeyboardLogEvent.ACCESSIBILITY_ALL_APPS);
+                        notifyKeyboardShortcutTriggered(event,
+                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS);
                     }
                     break;
                 case LONG_PRESS_HOME_ASSIST:
-                    logKeyboardSystemsEvent(event, KeyboardLogEvent.LAUNCH_ASSISTANT);
+                    notifyKeyboardShortcutTriggered(event,
+                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT);
                     launchAssistAction(null, event.getDeviceId(), event.getEventTime(),
                             AssistUtils.INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS);
                     break;
                 case LONG_PRESS_HOME_NOTIFICATION_PANEL:
-                    logKeyboardSystemsEvent(event, KeyboardLogEvent.TOGGLE_NOTIFICATION_PANEL);
+                    notifyKeyboardShortcutTriggered(event,
+                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL);
                     toggleNotificationPanel();
                     break;
                 default:
@@ -2388,8 +2383,6 @@
         mContext.registerReceiver(mMultiuserReceiver, filter);
 
         mVibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
-        mHapticFeedbackVibrationProvider =
-                new HapticFeedbackVibrationProvider(mContext.getResources(), mVibrator);
 
         mGlobalKeyManager = new GlobalKeyManager(mContext);
 
@@ -3292,39 +3285,29 @@
             WindowManager.LayoutParams.TYPE_SYSTEM_ERROR,
         };
 
-    /**
-     * Log the keyboard shortcuts without blocking the current thread.
-     *
-     * We won't log keyboard events when the input device is null
-     * or when it is virtual.
-     */
-    private void handleKeyboardSystemEvent(KeyboardLogEvent keyboardLogEvent, KeyEvent event) {
-        final InputDevice inputDevice = mInputManager.getInputDevice(event.getDeviceId());
-        KeyboardMetricsCollector.logKeyboardSystemsEventReportedAtom(inputDevice,
-                keyboardLogEvent, event.getMetaState(), event.getKeyCode());
-        event.recycle();
-    }
-
-    private void logKeyboardSystemsEventOnActionUp(KeyEvent event,
-            KeyboardLogEvent keyboardSystemEvent) {
+    private void notifyKeyboardShortcutTriggeredOnActionUp(KeyEvent event,
+            @KeyboardSystemShortcut.SystemShortcut int systemShortcut) {
         if (event.getAction() != KeyEvent.ACTION_UP) {
             return;
         }
-        logKeyboardSystemsEvent(event, keyboardSystemEvent);
+        notifyKeyboardShortcutTriggered(event, systemShortcut);
     }
 
-    private void logKeyboardSystemsEventOnActionDown(KeyEvent event,
-            KeyboardLogEvent keyboardSystemEvent) {
+    private void notifyKeyboardShortcutTriggeredOnActionDown(KeyEvent event,
+            @KeyboardSystemShortcut.SystemShortcut int systemShortcut) {
         if (event.getAction() != KeyEvent.ACTION_DOWN) {
             return;
         }
-        logKeyboardSystemsEvent(event, keyboardSystemEvent);
+        notifyKeyboardShortcutTriggered(event, systemShortcut);
     }
 
-    private void logKeyboardSystemsEvent(KeyEvent event, KeyboardLogEvent keyboardSystemEvent) {
-        KeyEvent eventToLog = KeyEvent.obtain(event);
-        mHandler.obtainMessage(MSG_LOG_KEYBOARD_SYSTEM_EVENT, keyboardSystemEvent.getIntValue(), 0,
-                eventToLog).sendToTarget();
+    private void notifyKeyboardShortcutTriggered(KeyEvent event,
+            @KeyboardSystemShortcut.SystemShortcut int systemShortcut) {
+        if (systemShortcut == KeyboardSystemShortcut.SYSTEM_SHORTCUT_UNSPECIFIED) {
+            return;
+        }
+        mInputManagerInternal.notifyKeyboardShortcutTriggered(event.getDeviceId(),
+                new int[]{event.getKeyCode()}, event.getMetaState(), systemShortcut);
     }
 
     @Override
@@ -3434,7 +3417,8 @@
             case KeyEvent.KEYCODE_RECENT_APPS:
                 if (firstDown) {
                     showRecentApps(false /* triggeredFromAltTab */);
-                    logKeyboardSystemsEvent(event, KeyboardLogEvent.RECENT_APPS);
+                    notifyKeyboardShortcutTriggered(event,
+                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_RECENT_APPS);
                 }
                 return true;
             case KeyEvent.KEYCODE_APP_SWITCH:
@@ -3443,7 +3427,8 @@
                         preloadRecentApps();
                     } else if (!down) {
                         toggleRecentApps();
-                        logKeyboardSystemsEvent(event, KeyboardLogEvent.APP_SWITCH);
+                        notifyKeyboardShortcutTriggered(event,
+                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_APP_SWITCH);
                     }
                 }
                 return true;
@@ -3452,7 +3437,8 @@
                     launchAssistAction(Intent.EXTRA_ASSIST_INPUT_HINT_KEYBOARD,
                             deviceId, event.getEventTime(),
                             AssistUtils.INVOCATION_TYPE_UNKNOWN);
-                    logKeyboardSystemsEvent(event, KeyboardLogEvent.LAUNCH_ASSISTANT);
+                    notifyKeyboardShortcutTriggered(event,
+                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT);
                     return true;
                 }
                 break;
@@ -3465,14 +3451,16 @@
             case KeyEvent.KEYCODE_I:
                 if (firstDown && event.isMetaPressed()) {
                     showSystemSettings();
-                    logKeyboardSystemsEvent(event, KeyboardLogEvent.LAUNCH_SYSTEM_SETTINGS);
+                    notifyKeyboardShortcutTriggered(event,
+                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS);
                     return true;
                 }
                 break;
             case KeyEvent.KEYCODE_L:
                 if (firstDown && event.isMetaPressed()) {
                     lockNow(null /* options */);
-                    logKeyboardSystemsEvent(event, KeyboardLogEvent.LOCK_SCREEN);
+                    notifyKeyboardShortcutTriggered(event,
+                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_LOCK_SCREEN);
                     return true;
                 }
                 break;
@@ -3480,10 +3468,12 @@
                 if (firstDown && event.isMetaPressed()) {
                     if (event.isCtrlPressed()) {
                         sendSystemKeyToStatusBarAsync(event);
-                        logKeyboardSystemsEvent(event, KeyboardLogEvent.OPEN_NOTES);
+                        notifyKeyboardShortcutTriggered(event,
+                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_OPEN_NOTES);
                     } else {
                         toggleNotificationPanel();
-                        logKeyboardSystemsEvent(event, KeyboardLogEvent.TOGGLE_NOTIFICATION_PANEL);
+                        notifyKeyboardShortcutTriggered(event,
+                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL);
                     }
                     return true;
                 }
@@ -3491,7 +3481,8 @@
             case KeyEvent.KEYCODE_S:
                 if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
                     interceptScreenshotChord(SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
-                    logKeyboardSystemsEvent(event, KeyboardLogEvent.TAKE_SCREENSHOT);
+                    notifyKeyboardShortcutTriggered(event,
+                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_TAKE_SCREENSHOT);
                     return true;
                 }
                 break;
@@ -3504,14 +3495,16 @@
                         } catch (RemoteException e) {
                             Slog.d(TAG, "Error taking bugreport", e);
                         }
-                        logKeyboardSystemsEvent(event, KeyboardLogEvent.TRIGGER_BUG_REPORT);
+                        notifyKeyboardShortcutTriggered(event,
+                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT);
                         return true;
                     }
                 }
                 // fall through
             case KeyEvent.KEYCODE_ESCAPE:
                 if (firstDown && event.isMetaPressed()) {
-                    logKeyboardSystemsEvent(event, KeyboardLogEvent.BACK);
+                    notifyKeyboardShortcutTriggered(event,
+                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_BACK);
                     injectBackGesture(event.getDownTime());
                     return true;
                 }
@@ -3520,7 +3513,8 @@
                     StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
                     if (statusbar != null) {
                         statusbar.moveFocusedTaskToFullscreen(getTargetDisplayIdForKeyEvent(event));
-                        logKeyboardSystemsEvent(event, KeyboardLogEvent.MULTI_WINDOW_NAVIGATION);
+                        notifyKeyboardShortcutTriggered(event,
+                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION);
                         return true;
                     }
                 }
@@ -3530,7 +3524,8 @@
                     StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
                     if (statusbar != null) {
                         statusbar.moveFocusedTaskToDesktop(getTargetDisplayIdForKeyEvent(event));
-                        logKeyboardSystemsEvent(event, KeyboardLogEvent.DESKTOP_MODE);
+                        notifyKeyboardShortcutTriggered(event,
+                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_DESKTOP_MODE);
                         return true;
                     }
                 }
@@ -3540,12 +3535,15 @@
                     if (event.isCtrlPressed()) {
                         moveFocusedTaskToStageSplit(getTargetDisplayIdForKeyEvent(event),
                                 true /* leftOrTop */);
-                        logKeyboardSystemsEvent(event, KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION);
+                        notifyKeyboardShortcutTriggered(event,
+                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION);
                     } else if (event.isAltPressed()) {
                         setSplitscreenFocus(true /* leftOrTop */);
-                        logKeyboardSystemsEvent(event, KeyboardLogEvent.CHANGE_SPLITSCREEN_FOCUS);
+                        notifyKeyboardShortcutTriggered(event,
+                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS);
                     } else {
-                        logKeyboardSystemsEvent(event, KeyboardLogEvent.BACK);
+                        notifyKeyboardShortcutTriggered(event,
+                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_BACK);
                         injectBackGesture(event.getDownTime());
                     }
                     return true;
@@ -3556,11 +3554,13 @@
                     if (event.isCtrlPressed()) {
                         moveFocusedTaskToStageSplit(getTargetDisplayIdForKeyEvent(event),
                                 false /* leftOrTop */);
-                        logKeyboardSystemsEvent(event, KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION);
+                        notifyKeyboardShortcutTriggered(event,
+                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION);
                         return true;
                     } else if (event.isAltPressed()) {
                         setSplitscreenFocus(false /* leftOrTop */);
-                        logKeyboardSystemsEvent(event, KeyboardLogEvent.CHANGE_SPLITSCREEN_FOCUS);
+                        notifyKeyboardShortcutTriggered(event,
+                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS);
                         return true;
                     }
                 }
@@ -3568,7 +3568,8 @@
             case KeyEvent.KEYCODE_SLASH:
                 if (firstDown && event.isMetaPressed() && !keyguardOn) {
                     toggleKeyboardShortcutsMenu(event.getDeviceId());
-                    logKeyboardSystemsEvent(event, KeyboardLogEvent.OPEN_SHORTCUT_HELPER);
+                    notifyKeyboardShortcutTriggered(event,
+                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER);
                     return true;
                 }
                 break;
@@ -3620,25 +3621,32 @@
                             | Intent.FLAG_ACTIVITY_NO_USER_ACTION);
                     intent.putExtra(EXTRA_FROM_BRIGHTNESS_KEY, true);
                     startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF);
-                    logKeyboardSystemsEvent(event, KeyboardLogEvent.getBrightnessEvent(keyCode));
+
+                    int systemShortcut = keyCode == KeyEvent.KEYCODE_BRIGHTNESS_DOWN
+                            ? KeyboardSystemShortcut.SYSTEM_SHORTCUT_BRIGHTNESS_DOWN
+                            : KeyboardSystemShortcut.SYSTEM_SHORTCUT_BRIGHTNESS_UP;
+                    notifyKeyboardShortcutTriggered(event, systemShortcut);
                 }
                 return true;
             case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN:
                 if (down) {
                     mInputManagerInternal.decrementKeyboardBacklight(event.getDeviceId());
-                    logKeyboardSystemsEvent(event, KeyboardLogEvent.KEYBOARD_BACKLIGHT_DOWN);
+                    notifyKeyboardShortcutTriggered(event,
+                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN);
                 }
                 return true;
             case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP:
                 if (down) {
                     mInputManagerInternal.incrementKeyboardBacklight(event.getDeviceId());
-                    logKeyboardSystemsEvent(event, KeyboardLogEvent.KEYBOARD_BACKLIGHT_UP);
+                    notifyKeyboardShortcutTriggered(event,
+                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP);
                 }
                 return true;
             case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE:
                 // TODO: Add logic
                 if (!down) {
-                    logKeyboardSystemsEvent(event, KeyboardLogEvent.KEYBOARD_BACKLIGHT_TOGGLE);
+                    notifyKeyboardShortcutTriggered(event,
+                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE);
                 }
                 return true;
             case KeyEvent.KEYCODE_VOLUME_UP:
@@ -3665,7 +3673,8 @@
                 if (firstDown && !keyguardOn && isUserSetupComplete()) {
                     if (event.isMetaPressed()) {
                         showRecentApps(false);
-                        logKeyboardSystemsEvent(event, KeyboardLogEvent.RECENT_APPS);
+                        notifyKeyboardShortcutTriggered(event,
+                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_RECENT_APPS);
                         return true;
                     } else if (mRecentAppsHeldModifiers == 0) {
                         final int shiftlessModifiers =
@@ -3674,7 +3683,8 @@
                                 shiftlessModifiers, KeyEvent.META_ALT_ON)) {
                             mRecentAppsHeldModifiers = shiftlessModifiers;
                             showRecentApps(true);
-                            logKeyboardSystemsEvent(event, KeyboardLogEvent.RECENT_APPS);
+                            notifyKeyboardShortcutTriggered(event,
+                                    KeyboardSystemShortcut.SYSTEM_SHORTCUT_RECENT_APPS);
                             return true;
                         }
                     }
@@ -3687,17 +3697,20 @@
                         Message msg = mHandler.obtainMessage(MSG_HANDLE_ALL_APPS);
                         msg.setAsynchronous(true);
                         msg.sendToTarget();
-                        logKeyboardSystemsEvent(event, KeyboardLogEvent.ALL_APPS);
+                        notifyKeyboardShortcutTriggered(event,
+                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_ALL_APPS);
                     } else {
                         launchAllAppsViaA11y();
-                        logKeyboardSystemsEvent(event, KeyboardLogEvent.ACCESSIBILITY_ALL_APPS);
+                        notifyKeyboardShortcutTriggered(event,
+                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS);
                     }
                 }
                 return true;
             case KeyEvent.KEYCODE_NOTIFICATION:
                 if (!down) {
                     toggleNotificationPanel();
-                    logKeyboardSystemsEvent(event, KeyboardLogEvent.TOGGLE_NOTIFICATION_PANEL);
+                    notifyKeyboardShortcutTriggered(event,
+                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL);
                 }
                 return true;
             case KeyEvent.KEYCODE_SEARCH:
@@ -3705,7 +3718,8 @@
                     switch (mSearchKeyBehavior) {
                         case SEARCH_KEY_BEHAVIOR_TARGET_ACTIVITY: {
                             launchTargetSearchActivity();
-                            logKeyboardSystemsEvent(event, KeyboardLogEvent.LAUNCH_SEARCH);
+                            notifyKeyboardShortcutTriggered(event,
+                                    KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_SEARCH);
                             return true;
                         }
                         case SEARCH_KEY_BEHAVIOR_DEFAULT_SEARCH:
@@ -3718,7 +3732,8 @@
                 if (firstDown) {
                     int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
                     sendSwitchKeyboardLayout(event, focusedToken, direction);
-                    logKeyboardSystemsEvent(event, KeyboardLogEvent.LANGUAGE_SWITCH);
+                    notifyKeyboardShortcutTriggered(event,
+                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_LANGUAGE_SWITCH);
                     return true;
                 }
                 break;
@@ -3737,11 +3752,13 @@
                     if (mPendingCapsLockToggle) {
                         mInputManagerInternal.toggleCapsLock(event.getDeviceId());
                         mPendingCapsLockToggle = false;
-                        logKeyboardSystemsEvent(event, KeyboardLogEvent.TOGGLE_CAPS_LOCK);
+                        notifyKeyboardShortcutTriggered(event,
+                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK);
                     } else if (mPendingMetaAction) {
                         if (!canceled) {
                             launchAllAppsViaA11y();
-                            logKeyboardSystemsEvent(event, KeyboardLogEvent.ACCESSIBILITY_ALL_APPS);
+                            notifyKeyboardShortcutTriggered(event,
+                                    KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS);
                         }
                         mPendingMetaAction = false;
                     }
@@ -3769,14 +3786,16 @@
                     if (mPendingCapsLockToggle) {
                         mInputManagerInternal.toggleCapsLock(event.getDeviceId());
                         mPendingCapsLockToggle = false;
-                        logKeyboardSystemsEvent(event, KeyboardLogEvent.TOGGLE_CAPS_LOCK);
+                        notifyKeyboardShortcutTriggered(event,
+                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK);
                         return true;
                     }
                 }
                 break;
             case KeyEvent.KEYCODE_CAPS_LOCK:
                 if (!down) {
-                    logKeyboardSystemsEvent(event, KeyboardLogEvent.TOGGLE_CAPS_LOCK);
+                    notifyKeyboardShortcutTriggered(event,
+                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK);
                 }
                 break;
             case KeyEvent.KEYCODE_STYLUS_BUTTON_PRIMARY:
@@ -3790,10 +3809,12 @@
                 if (firstDown) {
                     if (mSettingsKeyBehavior == SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL) {
                         toggleNotificationPanel();
-                        logKeyboardSystemsEvent(event, KeyboardLogEvent.TOGGLE_NOTIFICATION_PANEL);
+                        notifyKeyboardShortcutTriggered(event,
+                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL);
                     } else if (mSettingsKeyBehavior == SETTINGS_KEY_BEHAVIOR_SETTINGS_ACTIVITY) {
                         showSystemSettings();
-                        logKeyboardSystemsEvent(event, KeyboardLogEvent.LAUNCH_SYSTEM_SETTINGS);
+                        notifyKeyboardShortcutTriggered(event,
+                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS);
                     }
                 }
                 return true;
@@ -4739,7 +4760,8 @@
         // Handle special keys.
         switch (keyCode) {
             case KeyEvent.KEYCODE_BACK: {
-                logKeyboardSystemsEventOnActionUp(event, KeyboardLogEvent.BACK);
+                notifyKeyboardShortcutTriggeredOnActionUp(event,
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_BACK);
                 if (down) {
                     // There may have other embedded activities on the same Task. Try to move the
                     // focus before processing the back event.
@@ -4760,8 +4782,12 @@
             case KeyEvent.KEYCODE_VOLUME_DOWN:
             case KeyEvent.KEYCODE_VOLUME_UP:
             case KeyEvent.KEYCODE_VOLUME_MUTE: {
-                logKeyboardSystemsEventOnActionDown(event,
-                        KeyboardLogEvent.getVolumeEvent(keyCode));
+                int systemShortcut = keyCode == KEYCODE_VOLUME_DOWN
+                        ? KeyboardSystemShortcut.SYSTEM_SHORTCUT_VOLUME_DOWN
+                        : keyCode == KEYCODE_VOLUME_UP
+                                ? KeyboardSystemShortcut.SYSTEM_SHORTCUT_VOLUME_UP
+                                : KeyboardSystemShortcut.SYSTEM_SHORTCUT_VOLUME_MUTE;
+                notifyKeyboardShortcutTriggeredOnActionDown(event, systemShortcut);
                 if (down) {
                     sendSystemKeyToStatusBarAsync(event);
 
@@ -4862,7 +4888,8 @@
             }
 
             case KeyEvent.KEYCODE_TV_POWER: {
-                logKeyboardSystemsEventOnActionUp(event, KeyboardLogEvent.TOGGLE_POWER);
+                notifyKeyboardShortcutTriggeredOnActionUp(event,
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_POWER);
                 result &= ~ACTION_PASS_TO_USER;
                 isWakeKey = false; // wake-up will be handled separately
                 if (down && hdmiControlManager != null) {
@@ -4872,7 +4899,8 @@
             }
 
             case KeyEvent.KEYCODE_POWER: {
-                logKeyboardSystemsEventOnActionUp(event, KeyboardLogEvent.TOGGLE_POWER);
+                notifyKeyboardShortcutTriggeredOnActionUp(event,
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_POWER);
                 EventLogTags.writeInterceptPower(
                         KeyEvent.actionToString(event.getAction()),
                         mPowerKeyHandled ? 1 : 0,
@@ -4895,14 +4923,16 @@
             case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT:
                 // fall through
             case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT: {
-                logKeyboardSystemsEventOnActionUp(event, KeyboardLogEvent.SYSTEM_NAVIGATION);
+                notifyKeyboardShortcutTriggeredOnActionUp(event,
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SYSTEM_NAVIGATION);
                 result &= ~ACTION_PASS_TO_USER;
                 interceptSystemNavigationKey(event);
                 break;
             }
 
             case KeyEvent.KEYCODE_SLEEP: {
-                logKeyboardSystemsEventOnActionUp(event, KeyboardLogEvent.SLEEP);
+                notifyKeyboardShortcutTriggeredOnActionUp(event,
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SLEEP);
                 result &= ~ACTION_PASS_TO_USER;
                 isWakeKey = false;
                 if (!mPowerManager.isInteractive()) {
@@ -4918,7 +4948,8 @@
             }
 
             case KeyEvent.KEYCODE_SOFT_SLEEP: {
-                logKeyboardSystemsEventOnActionUp(event, KeyboardLogEvent.SLEEP);
+                notifyKeyboardShortcutTriggeredOnActionUp(event,
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SLEEP);
                 result &= ~ACTION_PASS_TO_USER;
                 isWakeKey = false;
                 if (!down) {
@@ -4929,7 +4960,8 @@
             }
 
             case KeyEvent.KEYCODE_WAKEUP: {
-                logKeyboardSystemsEventOnActionUp(event, KeyboardLogEvent.WAKEUP);
+                notifyKeyboardShortcutTriggeredOnActionUp(event,
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_WAKEUP);
                 result &= ~ACTION_PASS_TO_USER;
                 isWakeKey = true;
                 break;
@@ -4938,7 +4970,8 @@
             case KeyEvent.KEYCODE_MUTE:
                 result &= ~ACTION_PASS_TO_USER;
                 if (down && event.getRepeatCount() == 0) {
-                    logKeyboardSystemsEvent(event, KeyboardLogEvent.SYSTEM_MUTE);
+                    notifyKeyboardShortcutTriggered(event,
+                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_SYSTEM_MUTE);
                     toggleMicrophoneMuteFromKey();
                 }
                 break;
@@ -4953,7 +4986,8 @@
             case KeyEvent.KEYCODE_MEDIA_RECORD:
             case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
             case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: {
-                logKeyboardSystemsEventOnActionUp(event, KeyboardLogEvent.MEDIA_KEY);
+                notifyKeyboardShortcutTriggeredOnActionUp(event,
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_MEDIA_KEY);
                 if (MediaSessionLegacyHelper.getHelper(mContext).isGlobalPriorityActive()) {
                     // If the global session is active pass all media keys to it
                     // instead of the active window.
@@ -4998,7 +5032,8 @@
                             0 /* unused */, event.getEventTime() /* eventTime */);
                     msg.setAsynchronous(true);
                     msg.sendToTarget();
-                    logKeyboardSystemsEvent(event, KeyboardLogEvent.LAUNCH_ASSISTANT);
+                    notifyKeyboardShortcutTriggered(event,
+                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT);
                 }
                 result &= ~ACTION_PASS_TO_USER;
                 break;
@@ -5009,7 +5044,8 @@
                     Message msg = mHandler.obtainMessage(MSG_LAUNCH_VOICE_ASSIST_WITH_WAKE_LOCK);
                     msg.setAsynchronous(true);
                     msg.sendToTarget();
-                    logKeyboardSystemsEvent(event, KeyboardLogEvent.LAUNCH_VOICE_ASSISTANT);
+                    notifyKeyboardShortcutTriggered(event,
+                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT);
                 }
                 result &= ~ACTION_PASS_TO_USER;
                 break;
@@ -5975,10 +6011,10 @@
     public void setSafeMode(boolean safeMode) {
         mSafeMode = safeMode;
         if (safeMode) {
-            performHapticFeedback(Process.myUid(), mContext.getOpPackageName(),
+            performHapticFeedback(
                     HapticFeedbackConstants.SAFE_MODE_ENABLED,
-                    "Safe Mode Enabled", HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING,
-                    0 /* privFlags */);
+                    "Safe Mode Enabled" /* reason */,
+                    HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
         }
     }
 
@@ -6447,9 +6483,13 @@
                 Settings.Global.THEATER_MODE_ON, 0) == 1;
     }
 
-    private boolean performHapticFeedback(int effectId, String reason) {
-        return performHapticFeedback(Process.myUid(), mContext.getOpPackageName(),
-            effectId, reason, 0 /* flags */, 0 /* privFlags */);
+    private void performHapticFeedback(int effectId, String reason) {
+        performHapticFeedback(effectId, reason, 0 /* flags */);
+    }
+
+    private void performHapticFeedback(
+            int effectId, String reason, @HapticFeedbackConstants.Flags int flags) {
+        mVibrator.performHapticFeedback(effectId, reason, flags, 0 /* privFlags */);
     }
 
     @Override
@@ -6457,25 +6497,6 @@
         return mGlobalKeyManager.shouldHandleGlobalKey(keyCode);
     }
 
-    @Override
-    public boolean performHapticFeedback(int uid, String packageName, int effectId, String reason,
-            int flags, int privFlags) {
-        if (!mVibrator.hasVibrator()) {
-            return false;
-        }
-        VibrationEffect effect =
-                mHapticFeedbackVibrationProvider.getVibrationForHapticFeedback(effectId);
-        if (effect == null) {
-            return false;
-        }
-        VibrationAttributes attrs =
-                mHapticFeedbackVibrationProvider.getVibrationAttributesForHapticFeedback(
-                        effectId, flags, privFlags);
-        VibratorFrameworkStatsLogger.logPerformHapticsFeedbackIfKeyboard(uid, effectId);
-        mVibrator.vibrate(uid, packageName, effect, reason, attrs);
-        return true;
-    }
-
 
     @Override
     public void keepScreenOnStartedLw() {
@@ -6651,7 +6672,6 @@
                 pw.print(" mLockScreenTimerActive="); pw.println(mLockScreenTimerActive);
         pw.print(prefix); pw.print("mKidsModeEnabled="); pw.println(mKidsModeEnabled);
 
-        mHapticFeedbackVibrationProvider.dump(prefix, pw);
         mGlobalKeyManager.dump(prefix, pw);
         mKeyCombinationManager.dump(prefix, pw);
         mSingleKeyGestureDetector.dump(prefix, pw);
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 1b394f6..67f5f27 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -80,7 +80,6 @@
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 import android.view.Display;
-import android.view.HapticFeedbackConstants;
 import android.view.IDisplayFoldListener;
 import android.view.KeyEvent;
 import android.view.KeyboardShortcutGroup;
@@ -1077,13 +1076,6 @@
     public void enableScreenAfterBoot();
 
     /**
-     * Call from application to perform haptic feedback on its window.
-     */
-    public boolean performHapticFeedback(int uid, String packageName, int effectId,
-            String reason, @HapticFeedbackConstants.Flags int flags,
-            @HapticFeedbackConstants.PrivateFlags int privFlags);
-
-    /**
      * Called when we have started keeping the screen on because a window
      * requesting this has become visible.
      */
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index b28da55b..1a2a196 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -360,7 +360,13 @@
             IWakeLockCallback callback, int newFlags, String newTag, String newPackageName,
             int newOwnerUid, int newOwnerPid, WorkSource newWorkSource, String newHistoryTag,
             IWakeLockCallback newCallback) {
-
+        // Todo(b/359154665): We do this because the newWorkSource can potentially be updated
+        // before the request is processed on the notifier thread. This would generally happen is
+        // the Worksource's set method is called, which as of this comment happens only in
+        // PowerManager#setWorksource and WifiManager#WifiLock#setWorksource. Both these places
+        // need to be updated and the WorkSource#set should be deprecated to avoid falling into
+        // such traps
+        newWorkSource = (newWorkSource == null) ? null : new WorkSource(newWorkSource);
         final int monitorType = getBatteryStatsWakeLockMonitorType(flags);
         final int newMonitorType = getBatteryStatsWakeLockMonitorType(newFlags);
         if (workSource != null && newWorkSource != null
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index a4a29a0..2faa68a 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -20,6 +20,8 @@
 import android.app.ITransientNotificationCallback;
 import android.content.ComponentName;
 import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
+import android.inputmethodservice.InputMethodService.BackDispositionMode;
+import android.inputmethodservice.InputMethodService.ImeWindowVisibility;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.UserHandle;
@@ -54,13 +56,12 @@
      * Used by InputMethodManagerService to notify the IME status.
      *
      * @param displayId The display to which the IME is bound to.
-     * @param vis Bit flags about the IME visibility.
-     *            (e.g. {@link android.inputmethodservice.InputMethodService#IME_ACTIVE})
-     * @param backDisposition Bit flags about the IME back disposition.
-     *         (e.g. {@link android.inputmethodservice.InputMethodService#BACK_DISPOSITION_DEFAULT})
+     * @param vis The IME visibility.
+     * @param backDisposition The IME back disposition.
      * @param showImeSwitcher {@code true} when the IME switcher button should be shown.
      */
-    void setImeWindowStatus(int displayId, int vis, int backDisposition, boolean showImeSwitcher);
+    void setImeWindowStatus(int displayId, @ImeWindowVisibility int vis,
+            @BackDispositionMode int backDisposition, boolean showImeSwitcher);
 
     /**
      * See {@link android.app.StatusBarManager#setIcon(String, int, int, String)}.
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index c3601b3c..7d812ee 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -26,6 +26,10 @@
 import static android.app.StatusBarManager.NavBarMode;
 import static android.app.StatusBarManager.SessionFlags;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT;
+import static android.os.UserHandle.USER_SYSTEM;
+import static android.os.UserHandle.getCallingUserId;
+import static android.os.UserManager.isVisibleBackgroundUsersEnabled;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.ViewRootImpl.CLIENT_TRANSIENT;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY;
@@ -60,6 +64,8 @@
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManager.DisplayListener;
 import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
+import android.inputmethodservice.InputMethodService.BackDispositionMode;
+import android.inputmethodservice.InputMethodService.ImeWindowVisibility;
 import android.media.INearbyMediaDevicesProvider;
 import android.media.MediaRoute2Info;
 import android.net.Uri;
@@ -113,6 +119,7 @@
 import com.android.server.UiThread;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.notification.NotificationDelegate;
+import com.android.server.pm.UserManagerService;
 import com.android.server.policy.GlobalActionsProvider;
 import com.android.server.power.ShutdownCheckPoints;
 import com.android.server.power.ShutdownThread;
@@ -145,9 +152,9 @@
 
     /**
      * In apps targeting {@link android.os.Build.VERSION_CODES#TIRAMISU} or higher, calling
-     * {@link android.service.quicksettings.TileService#requestListeningState} will check that the 
-     * calling package (uid) and the package of the target {@link android.content.ComponentName} 
-     * match. It'll also make sure that the context used can take actions on behalf of the current 
+     * {@link android.service.quicksettings.TileService#requestListeningState} will check that the
+     * calling package (uid) and the package of the target {@link android.content.ComponentName}
+     * match. It'll also make sure that the context used can take actions on behalf of the current
      * user.
      */
     @ChangeId
@@ -197,6 +204,9 @@
 
     private IOverlayManager mOverlayManager;
 
+    private final boolean mVisibleBackgroundUsersEnabled;
+    private final UserManagerService mUserManager;
+
     private class DeathRecipient implements IBinder.DeathRecipient {
         public void binderDied() {
             mBar.asBinder().unlinkToDeath(this,0);
@@ -297,6 +307,9 @@
 
         mTileRequestTracker = new TileRequestTracker(mContext);
         mSessionMonitor = new SessionMonitor(mContext);
+
+        mVisibleBackgroundUsersEnabled = isVisibleBackgroundUsersEnabled();
+        mUserManager = UserManagerService.getInstance();
     }
 
     /**
@@ -534,8 +547,8 @@
         }
 
         @Override
-        public void setImeWindowStatus(int displayId, int vis, int backDisposition,
-                boolean showImeSwitcher) {
+        public void setImeWindowStatus(int displayId, @ImeWindowVisibility int vis,
+                @BackDispositionMode int backDisposition, boolean showImeSwitcher) {
             StatusBarManagerService.this.setImeWindowStatus(displayId, vis, backDisposition,
                     showImeSwitcher);
         }
@@ -911,6 +924,7 @@
     @Override
     public void expandNotificationsPanel() {
         enforceExpandStatusBar();
+        enforceValidCallingUser();
 
         if (isDisable2FlagSet(DISABLE2_NOTIFICATION_SHADE)) {
             return;
@@ -926,6 +940,8 @@
 
     @Override
     public void collapsePanels() {
+        enforceValidCallingUser();
+
         if (!checkCanCollapseStatusBar("collapsePanels")) {
             return;
         }
@@ -940,6 +956,8 @@
 
     @Override
     public void togglePanel() {
+        enforceValidCallingUser();
+
         if (!checkCanCollapseStatusBar("togglePanel")) {
             return;
         }
@@ -959,6 +977,7 @@
     @Override
     public void expandSettingsPanel(String subPanel) {
         enforceExpandStatusBar();
+        enforceValidCallingUser();
 
         if (mBar != null) {
             try {
@@ -973,6 +992,7 @@
             addQsTileToFrontOrEnd(component, false);
         } else {
             enforceStatusBarOrShell();
+            enforceValidCallingUser();
 
             if (mBar != null) {
                 try {
@@ -985,6 +1005,7 @@
 
     private void addQsTileToFrontOrEnd(ComponentName tile, boolean end) {
         enforceStatusBarOrShell();
+        enforceValidCallingUser();
 
         if (mBar != null) {
             try {
@@ -996,6 +1017,7 @@
 
     public void remTile(ComponentName component) {
         enforceStatusBarOrShell();
+        enforceValidCallingUser();
 
         if (mBar != null) {
             try {
@@ -1018,6 +1040,7 @@
 
     public void clickTile(ComponentName component) {
         enforceStatusBarOrShell();
+        enforceValidCallingUser();
 
         if (mBar != null) {
             try {
@@ -1029,6 +1052,8 @@
 
     @Override
     public void handleSystemKey(KeyEvent key) throws RemoteException {
+        enforceValidCallingUser();
+
         if (!checkCanCollapseStatusBar("handleSystemKey")) {
             return;
         }
@@ -1053,6 +1078,8 @@
 
     @Override
     public void showPinningEnterExitToast(boolean entering) throws RemoteException {
+        enforceValidCallingUser();
+
         if (mBar != null) {
             try {
                 mBar.showPinningEnterExitToast(entering);
@@ -1063,6 +1090,8 @@
 
     @Override
     public void showPinningEscapeToast() throws RemoteException {
+        enforceValidCallingUser();
+
         if (mBar != null) {
             try {
                 mBar.showPinningEscapeToast();
@@ -1076,6 +1105,8 @@
             int[] sensorIds, boolean credentialAllowed, boolean requireConfirmation,
             int userId, long operationId, String opPackageName, long requestId) {
         enforceBiometricDialog();
+        enforceValidCallingUser();
+
         if (mBar != null) {
             try {
                 mBar.showAuthenticationDialog(promptInfo, receiver, sensorIds, credentialAllowed,
@@ -1088,6 +1119,8 @@
     @Override
     public void onBiometricAuthenticated(@Modality int modality) {
         enforceBiometricDialog();
+        enforceValidCallingUser();
+
         if (mBar != null) {
             try {
                 mBar.onBiometricAuthenticated(modality);
@@ -1099,6 +1132,8 @@
     @Override
     public void onBiometricHelp(@Modality int modality, String message) {
         enforceBiometricDialog();
+        enforceValidCallingUser();
+
         if (mBar != null) {
             try {
                 mBar.onBiometricHelp(modality, message);
@@ -1110,6 +1145,8 @@
     @Override
     public void onBiometricError(int modality, int error, int vendorCode) {
         enforceBiometricDialog();
+        enforceValidCallingUser();
+
         if (mBar != null) {
             try {
                 mBar.onBiometricError(modality, error, vendorCode);
@@ -1121,6 +1158,8 @@
     @Override
     public void hideAuthenticationDialog(long requestId) {
         enforceBiometricDialog();
+        enforceValidCallingUser();
+
         if (mBar != null) {
             try {
                 mBar.hideAuthenticationDialog(requestId);
@@ -1132,6 +1171,8 @@
     @Override
     public void setBiometicContextListener(IBiometricContextListener listener) {
         enforceStatusBarService();
+        enforceValidCallingUser();
+
         synchronized (mLock) {
             mBiometricContextListener = listener;
         }
@@ -1146,6 +1187,8 @@
     @Override
     public void setUdfpsRefreshRateCallback(IUdfpsRefreshRateRequestCallback callback) {
         enforceStatusBarService();
+        enforceValidCallingUser();
+
         if (mBar != null) {
             try {
                 mBar.setUdfpsRefreshRateCallback(callback);
@@ -1156,6 +1199,8 @@
 
     @Override
     public void startTracing() {
+        enforceValidCallingUser();
+
         if (mBar != null) {
             try {
                 mBar.startTracing();
@@ -1167,6 +1212,8 @@
 
     @Override
     public void stopTracing() {
+        enforceValidCallingUser();
+
         if (mBar != null) {
             try {
                 mTracingEnabled = false;
@@ -1190,6 +1237,7 @@
     @Override
     public void disableForUser(int what, IBinder token, String pkg, int userId) {
         enforceStatusBar();
+        enforceValidCallingUser();
 
         synchronized (mLock) {
             disableLocked(DEFAULT_DISPLAY, userId, what, token, pkg, 1);
@@ -1293,6 +1341,7 @@
     public void setIcon(String slot, String iconPackage, int iconId, int iconLevel,
             String contentDescription) {
         enforceStatusBar();
+        enforceValidCallingUser();
 
         synchronized (mIcons) {
             StatusBarIcon icon = new StatusBarIcon(iconPackage, UserHandle.SYSTEM, iconId,
@@ -1313,6 +1362,7 @@
     @Override
     public void setIconVisibility(String slot, boolean visibility) {
         enforceStatusBar();
+        enforceValidCallingUser();
 
         synchronized (mIcons) {
             StatusBarIcon icon = mIcons.get(slot);
@@ -1336,6 +1386,7 @@
     @Override
     public void removeIcon(String slot) {
         enforceStatusBar();
+        enforceValidCallingUser();
 
         synchronized (mIcons) {
             mIcons.remove(slot);
@@ -1351,9 +1402,10 @@
     }
 
     @Override
-    public void setImeWindowStatus(int displayId, final int vis, final int backDisposition,
-            final boolean showImeSwitcher) {
+    public void setImeWindowStatus(int displayId, @ImeWindowVisibility final int vis,
+            @BackDispositionMode final int backDisposition, final boolean showImeSwitcher) {
         enforceStatusBar();
+        enforceValidCallingUser();
 
         if (SPEW) {
             Slog.d(TAG, "setImeWindowStatus vis=" + vis + " backDisposition=" + backDisposition);
@@ -1418,8 +1470,10 @@
         private String mPackageName = "none";
         private int mDisabled1 = 0;
         private int mDisabled2 = 0;
+        @ImeWindowVisibility
         private int mImeWindowVis = 0;
-        private int mImeBackDisposition = 0;
+        @BackDispositionMode
+        private int mImeBackDisposition = BACK_DISPOSITION_DEFAULT;
         private boolean mShowImeSwitcher = false;
         private LetterboxDetails[] mLetterboxDetails = new LetterboxDetails[0];
 
@@ -1462,7 +1516,8 @@
             return mDisabled1 == disabled1 && mDisabled2 == disabled2;
         }
 
-        private void setImeWindowState(final int vis, final int backDisposition,
+        private void setImeWindowState(@ImeWindowVisibility final int vis,
+                @BackDispositionMode final int backDisposition,
                 final boolean showImeSwitcher) {
             mImeWindowVis = vis;
             mImeBackDisposition = backDisposition;
@@ -1544,6 +1599,7 @@
     @Override
     public RegisterStatusBarResult registerStatusBar(IStatusBar bar) {
         enforceStatusBarService();
+        enforceValidCallingUser();
 
         Slog.i(TAG, "registerStatusBar bar=" + bar);
         mBar = bar;
@@ -1594,6 +1650,8 @@
     @Override
     public void onPanelRevealed(boolean clearNotificationEffects, int numItems) {
         enforceStatusBarService();
+        enforceValidCallingUser();
+
         final long identity = Binder.clearCallingIdentity();
         try {
             mNotificationDelegate.onPanelRevealed(clearNotificationEffects, numItems);
@@ -1605,6 +1663,8 @@
     @Override
     public void clearNotificationEffects() throws RemoteException {
         enforceStatusBarService();
+        enforceValidCallingUser();
+
         final long identity = Binder.clearCallingIdentity();
         try {
             mNotificationDelegate.clearEffects();
@@ -1616,6 +1676,8 @@
     @Override
     public void onPanelHidden() throws RemoteException {
         enforceStatusBarService();
+        enforceValidCallingUser();
+
         final long identity = Binder.clearCallingIdentity();
         try {
             mNotificationDelegate.onPanelHidden();
@@ -1630,6 +1692,8 @@
     @Override
     public void shutdown() {
         enforceStatusBarService();
+        enforceValidCallingUser();
+
         String reason = PowerManager.SHUTDOWN_USER_REQUESTED;
         ShutdownCheckPoints.recordCheckPoint(Binder.getCallingPid(), reason);
         final long identity = Binder.clearCallingIdentity();
@@ -1649,6 +1713,8 @@
     @Override
     public void reboot(boolean safeMode) {
         enforceStatusBarService();
+        enforceValidCallingUser();
+
         String reason = safeMode
                 ? PowerManager.REBOOT_SAFE_MODE
                 : PowerManager.SHUTDOWN_USER_REQUESTED;
@@ -1675,6 +1741,8 @@
     @Override
     public void restart() {
         enforceStatusBarService();
+        enforceValidCallingUser();
+
         final long identity = Binder.clearCallingIdentity();
         try {
             mHandler.post(() -> {
@@ -1688,6 +1756,8 @@
     @Override
     public void onGlobalActionsShown() {
         enforceStatusBarService();
+        enforceValidCallingUser();
+
         final long identity = Binder.clearCallingIdentity();
         try {
             if (mGlobalActionListener == null) return;
@@ -1700,6 +1770,8 @@
     @Override
     public void onGlobalActionsHidden() {
         enforceStatusBarService();
+        enforceValidCallingUser();
+
         final long identity = Binder.clearCallingIdentity();
         try {
             if (mGlobalActionListener == null) return;
@@ -1711,6 +1783,8 @@
 
     @Override
     public void onNotificationClick(String key, NotificationVisibility nv) {
+        // enforceValidCallingUser is not required here as the NotificationManagerService
+        // will handle multi-user scenarios
         enforceStatusBarService();
         final int callingUid = Binder.getCallingUid();
         final int callingPid = Binder.getCallingPid();
@@ -1726,6 +1800,8 @@
     public void onNotificationActionClick(
             String key, int actionIndex, Notification.Action action, NotificationVisibility nv,
             boolean generatedByAssistant) {
+        // enforceValidCallingUser is not required here as the NotificationManagerService
+        // will handle multi-user scenarios
         enforceStatusBarService();
         final int callingUid = Binder.getCallingUid();
         final int callingPid = Binder.getCallingPid();
@@ -1741,6 +1817,8 @@
     @Override
     public void onNotificationError(String pkg, String tag, int id,
             int uid, int initialPid, String message, int userId) {
+        // enforceValidCallingUser is not required here as the NotificationManagerService
+        // will handle multi-user scenarios
         enforceStatusBarService();
         final int callingUid = Binder.getCallingUid();
         final int callingPid = Binder.getCallingPid();
@@ -1759,6 +1837,8 @@
             @NotificationStats.DismissalSurface int dismissalSurface,
             @NotificationStats.DismissalSentiment int dismissalSentiment,
             NotificationVisibility nv) {
+        // enforceValidCallingUser is not required here as the NotificationManagerService
+        // will handle multi-user scenarios
         enforceStatusBarService();
         final int callingUid = Binder.getCallingUid();
         final int callingPid = Binder.getCallingPid();
@@ -1776,6 +1856,8 @@
             NotificationVisibility[] newlyVisibleKeys, NotificationVisibility[] noLongerVisibleKeys)
             throws RemoteException {
         enforceStatusBarService();
+        enforceValidCallingUser();
+
         final long identity = Binder.clearCallingIdentity();
         try {
             mNotificationDelegate.onNotificationVisibilityChanged(
@@ -1789,6 +1871,8 @@
     public void onNotificationExpansionChanged(String key, boolean userAction, boolean expanded,
             int location) throws RemoteException {
         enforceStatusBarService();
+        enforceValidCallingUser();
+
         final long identity = Binder.clearCallingIdentity();
         try {
             mNotificationDelegate.onNotificationExpansionChanged(
@@ -1801,6 +1885,8 @@
     @Override
     public void onNotificationDirectReplied(String key) throws RemoteException {
         enforceStatusBarService();
+        enforceValidCallingUser();
+
         final long identity = Binder.clearCallingIdentity();
         try {
             mNotificationDelegate.onNotificationDirectReplied(key);
@@ -1813,6 +1899,8 @@
     public void onNotificationSmartSuggestionsAdded(String key, int smartReplyCount,
             int smartActionCount, boolean generatedByAssistant, boolean editBeforeSending) {
         enforceStatusBarService();
+        enforceValidCallingUser();
+
         final long identity = Binder.clearCallingIdentity();
         try {
             mNotificationDelegate.onNotificationSmartSuggestionsAdded(key, smartReplyCount,
@@ -1827,6 +1915,8 @@
             String key, int replyIndex, CharSequence reply, int notificationLocation,
             boolean modifiedBeforeSending) throws RemoteException {
         enforceStatusBarService();
+        enforceValidCallingUser();
+
         final long identity = Binder.clearCallingIdentity();
         try {
             mNotificationDelegate.onNotificationSmartReplySent(key, replyIndex, reply,
@@ -1839,6 +1929,8 @@
     @Override
     public void onNotificationSettingsViewed(String key) throws RemoteException {
         enforceStatusBarService();
+        enforceValidCallingUser();
+
         final long identity = Binder.clearCallingIdentity();
         try {
             mNotificationDelegate.onNotificationSettingsViewed(key);
@@ -1863,6 +1955,8 @@
     @Override
     public void onNotificationBubbleChanged(String key, boolean isBubble, int flags) {
         enforceStatusBarService();
+        enforceValidCallingUser();
+
         final long identity = Binder.clearCallingIdentity();
         try {
             mNotificationDelegate.onNotificationBubbleChanged(key, isBubble, flags);
@@ -1874,6 +1968,8 @@
     @Override
     public void onBubbleMetadataFlagChanged(String key, int flags) {
         enforceStatusBarService();
+        enforceValidCallingUser();
+
         final long identity = Binder.clearCallingIdentity();
         try {
             mNotificationDelegate.onBubbleMetadataFlagChanged(key, flags);
@@ -1885,6 +1981,8 @@
     @Override
     public void hideCurrentInputMethodForBubbles(int displayId) {
         enforceStatusBarService();
+        enforceValidCallingUser();
+
         final long token = Binder.clearCallingIdentity();
         try {
             InputMethodManagerInternal.get().hideInputMethod(
@@ -1923,6 +2021,8 @@
     @Override
     public void onNotificationFeedbackReceived(String key, Bundle feedback) {
         enforceStatusBarService();
+        enforceValidCallingUser();
+
         final long identity = Binder.clearCallingIdentity();
         try {
             mNotificationDelegate.onNotificationFeedbackReceived(key, feedback);
@@ -1942,6 +2042,8 @@
     @Override
     public void showInattentiveSleepWarning() {
         enforceStatusBarService();
+        enforceValidCallingUser();
+
         IStatusBar bar = mBar;
         if (bar != null) {
             try {
@@ -1954,6 +2056,8 @@
     @Override
     public void dismissInattentiveSleepWarning(boolean animated) {
         enforceStatusBarService();
+        enforceValidCallingUser();
+
         IStatusBar bar = mBar;
         if (bar != null) {
             try {
@@ -1966,6 +2070,8 @@
     @Override
     public void suppressAmbientDisplay(boolean suppress) {
         enforceStatusBarService();
+        enforceValidCallingUser();
+
         IStatusBar bar = mBar;
         if (bar != null) {
             try {
@@ -2179,6 +2285,8 @@
     @Override
     public void cancelRequestAddTile(@NonNull String packageName) {
         enforceStatusBar();
+        enforceValidCallingUser();
+
         cancelRequestAddTileInternal(packageName);
     }
 
@@ -2202,23 +2310,31 @@
 
     @Override
     public void onSessionStarted(@SessionFlags int sessionType, InstanceId instance) {
+        enforceValidCallingUser();
+
         mSessionMonitor.onSessionStarted(sessionType, instance);
     }
 
     @Override
     public void onSessionEnded(@SessionFlags int sessionType, InstanceId instance) {
+        enforceValidCallingUser();
+
         mSessionMonitor.onSessionEnded(sessionType, instance);
     }
 
     @Override
     public void registerSessionListener(@SessionFlags int sessionFlags,
             ISessionListener listener) {
+        enforceValidCallingUser();
+
         mSessionMonitor.registerSessionListener(sessionFlags, listener);
     }
 
     @Override
     public void unregisterSessionListener(@SessionFlags int sessionFlags,
             ISessionListener listener) {
+        enforceValidCallingUser();
+
         mSessionMonitor.unregisterSessionListener(sessionFlags, listener);
     }
 
@@ -2233,6 +2349,8 @@
      */
     public void setNavBarMode(@NavBarMode int navBarMode) {
         enforceStatusBar();
+        enforceValidCallingUser();
+
         if (navBarMode != NAV_BAR_MODE_DEFAULT && navBarMode != NAV_BAR_MODE_KIDS) {
             throw new IllegalArgumentException("Supplied navBarMode not supported: " + navBarMode);
         }
@@ -2243,6 +2361,7 @@
             throw new SecurityException("Calling user id: " + callingUserId
                     + ", cannot call on behalf of current user id: " + mCurrentUserId + ".");
         }
+
         final long userIdentity = Binder.clearCallingIdentity();
         try {
             Settings.Secure.putIntForUser(mContext.getContentResolver(),
@@ -2316,6 +2435,8 @@
             @Nullable IUndoMediaTransferCallback undoCallback
     ) {
         enforceMediaContentControl();
+        enforceValidCallingUser();
+
         IStatusBar bar = mBar;
         if (bar != null) {
             try {
@@ -2340,6 +2461,8 @@
             @Nullable Icon appIcon,
             @Nullable CharSequence appName) {
         enforceMediaContentControl();
+        enforceValidCallingUser();
+
         IStatusBar bar = mBar;
         if (bar != null) {
             try {
@@ -2367,6 +2490,8 @@
             @NonNull INearbyMediaDevicesProvider provider
     ) {
         enforceMediaContentControl();
+        enforceValidCallingUser();
+
         IStatusBar bar = mBar;
         if (bar != null) {
             try {
@@ -2393,6 +2518,8 @@
             @NonNull INearbyMediaDevicesProvider provider
     ) {
         enforceMediaContentControl();
+        enforceValidCallingUser();
+
         IStatusBar bar = mBar;
         if (bar != null) {
             try {
@@ -2407,6 +2534,8 @@
     @Override
     public void showRearDisplayDialog(int currentState) {
         enforceControlDeviceStatePermission();
+        enforceValidCallingUser();
+
         IStatusBar bar = mBar;
         if (bar != null) {
             try {
@@ -2579,4 +2708,32 @@
     private static final Context getUiContext() {
         return ActivityThread.currentActivityThread().getSystemUiContext();
     }
+
+    /**
+     * This method validates whether the calling user is allowed to control the status bar
+     * on a device that enables visible background users.
+     * Only system or current user or the user that belongs to the same profile group as the
+     * current user is permitted to control the status bar.
+     */
+    private void enforceValidCallingUser() {
+        if (!mVisibleBackgroundUsersEnabled) {
+            return;
+        }
+
+        int callingUserId = getCallingUserId();
+        if (callingUserId == USER_SYSTEM || callingUserId == mCurrentUserId) {
+            return;
+        }
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            if (mUserManager.isSameProfileGroup(callingUserId, mCurrentUserId)) {
+                return;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+
+        throw new SecurityException("User " + callingUserId
+                + " is not permitted to use this method");
+    }
 }
diff --git a/services/core/java/com/android/server/vibrator/HalVibration.java b/services/core/java/com/android/server/vibrator/HalVibration.java
index 46bd7af..fe0cf59 100644
--- a/services/core/java/com/android/server/vibrator/HalVibration.java
+++ b/services/core/java/com/android/server/vibrator/HalVibration.java
@@ -170,9 +170,11 @@
 
     /** Return {@link VibrationStats.StatsInfo} with read-only metrics about this vibration. */
     public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) {
-        int vibrationType = isRepeating()
-                ? FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__REPEATED
-                : FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE;
+        int vibrationType = mEffectToPlay.hasVendorEffects()
+                ? FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__VENDOR
+                : isRepeating()
+                        ? FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__REPEATED
+                        : FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE;
         return new VibrationStats.StatsInfo(
                 callerInfo.uid, vibrationType, callerInfo.attrs.getUsage(), mStatus,
                 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 503a726..65fc7b2 100644
--- a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
+++ b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
@@ -18,6 +18,7 @@
 
 import android.annotation.Nullable;
 import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
 import android.os.VibrationEffect;
 import android.os.VibratorInfo;
 import android.os.vibrator.Flags;
@@ -28,6 +29,7 @@
 import android.util.SparseArray;
 import android.util.Xml;
 
+import com.android.internal.util.XmlUtils;
 import com.android.internal.vibrator.persistence.XmlParserException;
 import com.android.internal.vibrator.persistence.XmlReader;
 import com.android.internal.vibrator.persistence.XmlValidator;
@@ -39,6 +41,7 @@
 import java.io.FileNotFoundException;
 import java.io.FileReader;
 import java.io.IOException;
+import java.io.Reader;
 
 /**
  * Class that loads custom {@link VibrationEffect} to be performed for each
@@ -127,27 +130,19 @@
             Slog.d(TAG, "Haptic feedback customization feature is not enabled.");
             return null;
         }
-        String customizationFile =
-                res.getString(
-                        com.android.internal.R.string.config_hapticFeedbackCustomizationFile);
-        if (TextUtils.isEmpty(customizationFile)) {
-            Slog.d(TAG, "Customization file not configured.");
+
+        // 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 (parser == null) {
+            Slog.d(TAG, "No loadable haptic feedback customization.");
             return null;
         }
 
-        FileReader fileReader;
-        try {
-            fileReader = new FileReader(customizationFile);
-        } catch (FileNotFoundException e) {
-            Slog.d(TAG, "Specified customization file not found.");
-            return  null;
-        }
-
-        TypedXmlPullParser parser = Xml.newFastPullParser();
-        parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
-        parser.setInput(fileReader);
-
-        XmlReader.readDocumentStartTag(parser, TAG_CONSTANTS);
+        XmlUtils.beginDocument(parser, TAG_CONSTANTS);
         XmlValidator.checkTagHasNoUnexpectedAttributes(parser);
         int rootDepth = parser.getDepth();
 
@@ -191,6 +186,46 @@
         return mapping;
     }
 
+    // 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);
+        if (TextUtils.isEmpty(customizationFile)) {
+            return null;
+        }
+
+        final Reader customizationReader;
+        try {
+            customizationReader = new FileReader(customizationFile);
+        } catch (FileNotFoundException e) {
+            Slog.e(TAG, "Specified customization file not found.", e);
+            return null;
+        }
+
+        final TypedXmlPullParser parser;
+        parser = Xml.newFastPullParser();
+        parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
+        parser.setInput(customizationReader);
+        Slog.d(TAG, "Successfully opened customization file.");
+        return parser;
+    }
+
+    private static TypedXmlPullParser readCustomizationResources(Resources res) {
+        if (!Flags.loadHapticFeedbackVibrationCustomizationFromResources()) {
+            return null;
+        }
+        final XmlResourceParser resParser;
+        try {
+            resParser = res.getXml(com.android.internal.R.xml.haptic_feedback_customization);
+        } catch (Resources.NotFoundException e) {
+            Slog.e(TAG, "Haptic customization resource not found.", e);
+            return null;
+        }
+        Slog.d(TAG, "Successfully opened customization resource.");
+        return XmlUtils.makeTyped(resParser);
+    }
+
     /**
      * Represents an error while parsing a haptic feedback customization XML.
      */
diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
index 3f9da82..eccbffb 100644
--- a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
+++ b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
@@ -22,7 +22,6 @@
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.os.VibratorInfo;
-import android.os.vibrator.Flags;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.HapticFeedbackConstants;
@@ -348,7 +347,7 @@
                 predefinedEffectId = VibrationEffect.EFFECT_CLICK;
                 predefinedEffectFallback = true;
         }
-        if (Flags.keyboardCategoryEnabled() && mKeyboardVibrationFixedAmplitude > 0) {
+        if (mKeyboardVibrationFixedAmplitude > 0) {
             if (mVibratorInfo.isPrimitiveSupported(primitiveId)) {
                 return VibrationEffect.startComposition()
                         .addPrimitive(primitiveId, mKeyboardVibrationFixedAmplitude)
@@ -361,10 +360,6 @@
 
     private VibrationAttributes createKeyboardVibrationAttributes(
             @HapticFeedbackConstants.PrivateFlags int privFlags) {
-        // Use touch attribute when the keyboard category is disable.
-        if (!Flags.keyboardCategoryEnabled()) {
-            return TOUCH_VIBRATION_ATTRIBUTES;
-        }
         // Use touch attribute when the haptic is not apply to IME.
         if ((privFlags & HapticFeedbackConstants.PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS) == 0) {
             return TOUCH_VIBRATION_ATTRIBUTES;
diff --git a/services/core/java/com/android/server/vibrator/PerformVendorEffectVibratorStep.java b/services/core/java/com/android/server/vibrator/PerformVendorEffectVibratorStep.java
index 8f36118..407f3d9 100644
--- a/services/core/java/com/android/server/vibrator/PerformVendorEffectVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/PerformVendorEffectVibratorStep.java
@@ -52,6 +52,7 @@
             long vibratorOnResult = controller.on(effect, getVibration().id);
             vibratorOnResult = Math.min(vibratorOnResult, VENDOR_EFFECT_MAX_DURATION_MS);
             handleVibratorOnResult(vibratorOnResult);
+            getVibration().stats.reportPerformVendorEffect(vibratorOnResult);
             return List.of(new CompleteEffectVibratorStep(conductor, startTime,
                     /* cancelled= */ false, controller, mPendingVibratorOffDeadline));
         } finally {
diff --git a/services/core/java/com/android/server/vibrator/VibrationScaler.java b/services/core/java/com/android/server/vibrator/VibrationScaler.java
index 3933759..a74c4e0 100644
--- a/services/core/java/com/android/server/vibrator/VibrationScaler.java
+++ b/services/core/java/com/android/server/vibrator/VibrationScaler.java
@@ -17,7 +17,6 @@
 package com.android.server.vibrator;
 
 import android.annotation.NonNull;
-import android.content.Context;
 import android.hardware.vibrator.V1_0.EffectStrength;
 import android.os.ExternalVibrationScale;
 import android.os.VibrationAttributes;
@@ -25,6 +24,7 @@
 import android.os.Vibrator;
 import android.os.vibrator.Flags;
 import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.VibrationConfig;
 import android.util.IndentingPrintWriter;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -37,8 +37,11 @@
 final class VibrationScaler {
     private static final String TAG = "VibrationScaler";
 
+    // TODO(b/345186129): remove this once we finish migrating to scale factor and clean up flags.
     // Scale levels. Each level, except MUTE, is defined as the delta between the current setting
     // and the default intensity for that type of vibration (i.e. current - default).
+    // It's important that we apply the scaling on the delta between the two so
+    // that the default intensity level applies no scaling to application provided effects.
     static final int SCALE_VERY_LOW = ExternalVibrationScale.ScaleLevel.SCALE_VERY_LOW; // -2
     static final int SCALE_LOW = ExternalVibrationScale.ScaleLevel.SCALE_LOW; // -1
     static final int SCALE_NONE = ExternalVibrationScale.ScaleLevel.SCALE_NONE; // 0
@@ -53,35 +56,15 @@
     private static final float SCALE_FACTOR_HIGH = 1.2f;
     private static final float SCALE_FACTOR_VERY_HIGH = 1.4f;
 
-    private static final ScaleLevel SCALE_LEVEL_NONE = new ScaleLevel(SCALE_FACTOR_NONE);
-
-    // A mapping from the intensity adjustment to the scaling to apply, where the intensity
-    // adjustment is defined as the delta between the default intensity level and the user selected
-    // intensity level. It's important that we apply the scaling on the delta between the two so
-    // that the default intensity level applies no scaling to application provided effects.
-    private final SparseArray<ScaleLevel> mScaleLevels;
     private final VibrationSettings mSettingsController;
     private final int mDefaultVibrationAmplitude;
+    private final float mDefaultVibrationScaleLevelGain;
     private final SparseArray<Float> mAdaptiveHapticsScales = new SparseArray<>();
 
-    VibrationScaler(Context context, VibrationSettings settingsController) {
+    VibrationScaler(VibrationConfig config, VibrationSettings settingsController) {
         mSettingsController = settingsController;
-        mDefaultVibrationAmplitude = context.getResources().getInteger(
-                com.android.internal.R.integer.config_defaultVibrationAmplitude);
-
-        mScaleLevels = new SparseArray<>();
-        mScaleLevels.put(SCALE_VERY_LOW, new ScaleLevel(SCALE_FACTOR_VERY_LOW));
-        mScaleLevels.put(SCALE_LOW, new ScaleLevel(SCALE_FACTOR_LOW));
-        mScaleLevels.put(SCALE_NONE, SCALE_LEVEL_NONE);
-        mScaleLevels.put(SCALE_HIGH, new ScaleLevel(SCALE_FACTOR_HIGH));
-        mScaleLevels.put(SCALE_VERY_HIGH, new ScaleLevel(SCALE_FACTOR_VERY_HIGH));
-    }
-
-    /**
-     * Returns the default vibration amplitude configured for this device, value in [1,255].
-     */
-    public int getDefaultVibrationAmplitude() {
-        return mDefaultVibrationAmplitude;
+        mDefaultVibrationAmplitude = config.getDefaultVibrationAmplitude();
+        mDefaultVibrationScaleLevelGain = config.getDefaultVibrationScaleLevelGain();
     }
 
     /**
@@ -111,6 +94,16 @@
     }
 
     /**
+     * Calculates the scale factor to be applied to a vibration with given usage.
+     *
+     * @param usageHint one of VibrationAttributes.USAGE_*
+     * @return The scale factor.
+     */
+    public float getScaleFactor(int usageHint) {
+        return scaleLevelToScaleFactor(getScaleLevel(usageHint));
+    }
+
+    /**
      * Returns the adaptive haptics scale that should be applied to the vibrations with
      * the given usage. When no adaptive scales are available for the usages, then returns 1
      * indicating no scaling will be applied
@@ -135,20 +128,12 @@
     @NonNull
     public VibrationEffect scale(@NonNull VibrationEffect effect, int usageHint) {
         int newEffectStrength = getEffectStrength(usageHint);
-        ScaleLevel scaleLevel = mScaleLevels.get(getScaleLevel(usageHint));
+        float scaleFactor = getScaleFactor(usageHint);
         float adaptiveScale = getAdaptiveHapticsScale(usageHint);
 
-        if (scaleLevel == null) {
-            // Something about our scaling has gone wrong, so just play with no scaling.
-            Slog.e(TAG, "No configured scaling level found! (current="
-                    + mSettingsController.getCurrentIntensity(usageHint) + ", default= "
-                    + mSettingsController.getDefaultIntensity(usageHint) + ")");
-            scaleLevel = SCALE_LEVEL_NONE;
-        }
-
         return effect.resolve(mDefaultVibrationAmplitude)
                 .applyEffectStrength(newEffectStrength)
-                .scale(scaleLevel.factor)
+                .scale(scaleFactor)
                 .scaleLinearly(adaptiveScale);
     }
 
@@ -192,14 +177,11 @@
     void dump(IndentingPrintWriter pw) {
         pw.println("VibrationScaler:");
         pw.increaseIndent();
-        pw.println("defaultVibrationAmplitude = " + mDefaultVibrationAmplitude);
 
         pw.println("ScaleLevels:");
         pw.increaseIndent();
-        for (int i = 0; i < mScaleLevels.size(); i++) {
-            int scaleLevelKey = mScaleLevels.keyAt(i);
-            ScaleLevel scaleLevel = mScaleLevels.valueAt(i);
-            pw.println(scaleLevelToString(scaleLevelKey) + " = " + scaleLevel);
+        for (int level = SCALE_VERY_LOW; level <= SCALE_VERY_HIGH; level++) {
+            pw.println(scaleLevelToString(level) + " = " + scaleLevelToScaleFactor(level));
         }
         pw.decreaseIndent();
 
@@ -224,16 +206,24 @@
 
     @Override
     public String toString() {
+        StringBuilder scaleLevelsStr = new StringBuilder("{");
+        for (int level = SCALE_VERY_LOW; level <= SCALE_VERY_HIGH; level++) {
+            scaleLevelsStr.append(scaleLevelToString(level))
+                    .append("=").append(scaleLevelToScaleFactor(level));
+            if (level < SCALE_FACTOR_VERY_HIGH) {
+                scaleLevelsStr.append(", ");
+            }
+        }
+        scaleLevelsStr.append("}");
+
         return "VibrationScaler{"
-                + "mScaleLevels=" + mScaleLevels
-                + ", mDefaultVibrationAmplitude=" + mDefaultVibrationAmplitude
+                + "mScaleLevels=" + scaleLevelsStr
                 + ", mAdaptiveHapticsScales=" + mAdaptiveHapticsScales
                 + '}';
     }
 
     private int getEffectStrength(int usageHint) {
         int currentIntensity = mSettingsController.getCurrentIntensity(usageHint);
-
         if (currentIntensity == Vibrator.VIBRATION_INTENSITY_OFF) {
             // Bypassing user settings, or it has changed between checking and scaling. Use default.
             currentIntensity = mSettingsController.getDefaultIntensity(usageHint);
@@ -244,17 +234,44 @@
 
     /** Mapping of Vibrator.VIBRATION_INTENSITY_* values to {@link EffectStrength}. */
     private static int intensityToEffectStrength(int intensity) {
-        switch (intensity) {
-            case Vibrator.VIBRATION_INTENSITY_LOW:
-                return EffectStrength.LIGHT;
-            case Vibrator.VIBRATION_INTENSITY_MEDIUM:
-                return EffectStrength.MEDIUM;
-            case Vibrator.VIBRATION_INTENSITY_HIGH:
-                return EffectStrength.STRONG;
-            default:
+        return switch (intensity) {
+            case Vibrator.VIBRATION_INTENSITY_LOW -> EffectStrength.LIGHT;
+            case Vibrator.VIBRATION_INTENSITY_MEDIUM -> EffectStrength.MEDIUM;
+            case Vibrator.VIBRATION_INTENSITY_HIGH -> EffectStrength.STRONG;
+            default -> {
                 Slog.w(TAG, "Got unexpected vibration intensity: " + intensity);
-                return EffectStrength.STRONG;
+                yield EffectStrength.STRONG;
+            }
+        };
+    }
+
+    /** Mapping of ExternalVibrationScale.ScaleLevel.SCALE_* values to scale factor. */
+    private float scaleLevelToScaleFactor(int level) {
+        if (Flags.hapticsScaleV2Enabled()) {
+            if (level == SCALE_NONE || level < SCALE_VERY_LOW || level > SCALE_VERY_HIGH) {
+                // Scale set to none or to a bad value, use default factor for no scaling.
+                return SCALE_FACTOR_NONE;
+            }
+            float scaleFactor = (float) Math.pow(mDefaultVibrationScaleLevelGain, level);
+            if (scaleFactor <= 0) {
+                // Something about our scaling has gone wrong, so just play with no scaling.
+                Slog.wtf(TAG, String.format(Locale.ROOT, "Error in scaling calculations, ended up"
+                                + " with invalid scale factor %.2f for scale level %s and default"
+                                + " level gain of %.2f", scaleFactor, scaleLevelToString(level),
+                        mDefaultVibrationScaleLevelGain));
+                scaleFactor = SCALE_FACTOR_NONE;
+            }
+            return scaleFactor;
         }
+
+        return switch (level) {
+            case SCALE_VERY_LOW -> SCALE_FACTOR_VERY_LOW;
+            case SCALE_LOW -> SCALE_FACTOR_LOW;
+            case SCALE_HIGH -> SCALE_FACTOR_HIGH;
+            case SCALE_VERY_HIGH -> SCALE_FACTOR_VERY_HIGH;
+            // Scale set to none or to a bad value, use default factor for no scaling.
+            default -> SCALE_FACTOR_NONE;
+        };
     }
 
     static String scaleLevelToString(int scaleLevel) {
@@ -267,18 +284,4 @@
             default -> String.valueOf(scaleLevel);
         };
     }
-
-    /** Represents the scale that must be applied to a vibration effect intensity. */
-    private static final class ScaleLevel {
-        public final float factor;
-
-        ScaleLevel(float factor) {
-            this.factor = factor;
-        }
-
-        @Override
-        public String toString() {
-            return "ScaleLevel{factor=" + factor + "}";
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index fb92d60..0d6778c 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -16,7 +16,6 @@
 
 package com.android.server.vibrator;
 
-import static android.os.VibrationAttributes.CATEGORY_KEYBOARD;
 import static android.os.VibrationAttributes.USAGE_ACCESSIBILITY;
 import static android.os.VibrationAttributes.USAGE_ALARM;
 import static android.os.VibrationAttributes.USAGE_COMMUNICATION_REQUEST;
@@ -55,7 +54,6 @@
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.os.Vibrator.VibrationIntensity;
-import android.os.vibrator.Flags;
 import android.os.vibrator.VibrationConfig;
 import android.provider.Settings;
 import android.util.IndentingPrintWriter;
@@ -192,8 +190,6 @@
     @GuardedBy("mLock")
     private boolean mVibrateOn;
     @GuardedBy("mLock")
-    private boolean mKeyboardVibrationOn;
-    @GuardedBy("mLock")
     private int mRingerMode;
     @GuardedBy("mLock")
     private boolean mOnWirelessCharger;
@@ -533,15 +529,6 @@
             return false;
         }
 
-        if (Flags.keyboardCategoryEnabled()
-                && mVibrationConfig.isKeyboardVibrationSettingsSupported()) {
-            int category = callerInfo.attrs.getCategory();
-            if (usage == USAGE_TOUCH && category == CATEGORY_KEYBOARD) {
-                // Keyboard touch has a different user setting.
-                return mKeyboardVibrationOn;
-            }
-        }
-
         // Apply individual user setting based on usage.
         return getCurrentIntensity(usage) != Vibrator.VIBRATION_INTENSITY_OFF;
     }
@@ -558,10 +545,11 @@
             mVibrateInputDevices =
                     loadSystemSetting(Settings.System.VIBRATE_INPUT_DEVICES, 0, userHandle) > 0;
             mVibrateOn = loadSystemSetting(Settings.System.VIBRATE_ON, 1, userHandle) > 0;
-            mKeyboardVibrationOn = loadSystemSetting(
-                    Settings.System.KEYBOARD_VIBRATION_ENABLED, 1, userHandle) > 0;
 
-            int keyboardIntensity = getDefaultIntensity(USAGE_IME_FEEDBACK);
+            boolean isKeyboardVibrationOn = loadSystemSetting(
+                    Settings.System.KEYBOARD_VIBRATION_ENABLED, 1, userHandle) > 0;
+            int keyboardIntensity = toIntensity(isKeyboardVibrationOn,
+                    getDefaultIntensity(USAGE_IME_FEEDBACK));
             int alarmIntensity = toIntensity(
                     loadSystemSetting(Settings.System.ALARM_VIBRATION_INTENSITY, -1, userHandle),
                     getDefaultIntensity(USAGE_ALARM));
@@ -656,7 +644,6 @@
             return "VibrationSettings{"
                     + "mVibratorConfig=" + mVibrationConfig
                     + ", mVibrateOn=" + mVibrateOn
-                    + ", mKeyboardVibrationOn=" + mKeyboardVibrationOn
                     + ", mVibrateInputDevices=" + mVibrateInputDevices
                     + ", mBatterySaverMode=" + mBatterySaverMode
                     + ", mRingerMode=" + ringerModeToString(mRingerMode)
@@ -673,7 +660,6 @@
             pw.println("VibrationSettings:");
             pw.increaseIndent();
             pw.println("vibrateOn = " + mVibrateOn);
-            pw.println("keyboardVibrationOn = " + mKeyboardVibrationOn);
             pw.println("vibrateInputDevices = " + mVibrateInputDevices);
             pw.println("batterySaverMode = " + mBatterySaverMode);
             pw.println("ringerMode = " + ringerModeToString(mRingerMode));
@@ -700,8 +686,6 @@
     void dump(ProtoOutputStream proto) {
         synchronized (mLock) {
             proto.write(VibratorManagerServiceDumpProto.VIBRATE_ON, mVibrateOn);
-            proto.write(VibratorManagerServiceDumpProto.KEYBOARD_VIBRATION_ON,
-                    mKeyboardVibrationOn);
             proto.write(VibratorManagerServiceDumpProto.LOW_POWER_MODE, mBatterySaverMode);
             proto.write(VibratorManagerServiceDumpProto.ALARM_INTENSITY,
                     getCurrentIntensity(USAGE_ALARM));
@@ -776,6 +760,11 @@
         return value;
     }
 
+    @VibrationIntensity
+    private int toIntensity(boolean enabled, @VibrationIntensity int defaultValue) {
+        return enabled ? defaultValue : Vibrator.VIBRATION_INTENSITY_OFF;
+    }
+
     private boolean loadBooleanSetting(String settingKey, int userHandle) {
         return loadSystemSetting(settingKey, 0, userHandle) != 0;
     }
diff --git a/services/core/java/com/android/server/vibrator/VibrationStats.java b/services/core/java/com/android/server/vibrator/VibrationStats.java
index dd66809..8179d6a 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStats.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStats.java
@@ -79,6 +79,7 @@
     private int mVibratorSetAmplitudeCount;
     private int mVibratorSetExternalControlCount;
     private int mVibratorPerformCount;
+    private int mVibratorPerformVendorCount;
     private int mVibratorComposeCount;
     private int mVibratorComposePwleCount;
 
@@ -239,6 +240,11 @@
         }
     }
 
+    /** Report a call to vibrator method to trigger a vendor vibration effect. */
+    void reportPerformVendorEffect(long halResult) {
+        mVibratorPerformVendorCount++;
+    }
+
     /** Report a call to vibrator method to trigger a vibration as a composition of primitives. */
     void reportComposePrimitives(long halResult, PrimitiveSegment[] primitives) {
         mVibratorComposeCount++;
@@ -313,6 +319,7 @@
         public final int halOnCount;
         public final int halOffCount;
         public final int halPerformCount;
+        public final int halPerformVendorCount;
         public final int halSetAmplitudeCount;
         public final int halSetExternalControlCount;
         public final int halCompositionSize;
@@ -357,6 +364,7 @@
             halOnCount = stats.mVibratorOnCount;
             halOffCount = stats.mVibratorOffCount;
             halPerformCount = stats.mVibratorPerformCount;
+            halPerformVendorCount = stats.mVibratorPerformVendorCount;
             halSetAmplitudeCount = stats.mVibratorSetAmplitudeCount;
             halSetExternalControlCount = stats.mVibratorSetExternalControlCount;
             halCompositionSize = stats.mVibrationCompositionTotalSize;
@@ -390,7 +398,8 @@
                     halOnCount, halOffCount, halPerformCount, halSetAmplitudeCount,
                     halSetExternalControlCount, halSupportedCompositionPrimitivesUsed,
                     halSupportedEffectsUsed, halUnsupportedCompositionPrimitivesUsed,
-                    halUnsupportedEffectsUsed, halCompositionSize, halPwleSize, adaptiveScale);
+                    halUnsupportedEffectsUsed, halCompositionSize, halPwleSize, adaptiveScale,
+                    halPerformVendorCount);
         }
 
         private static int[] filteredKeys(SparseBooleanArray supportArray, boolean supported) {
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 7610d7d..f2ad5b9 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -57,6 +57,7 @@
 import android.os.VibratorInfo;
 import android.os.vibrator.Flags;
 import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.VibrationConfig;
 import android.os.vibrator.VibrationEffectSegment;
 import android.os.vibrator.VibratorInfoFactory;
 import android.os.vibrator.persistence.ParsedVibration;
@@ -251,8 +252,9 @@
         mHandler = injector.createHandler(Looper.myLooper());
         mFrameworkStatsLogger = injector.getFrameworkStatsLogger(mHandler);
 
-        mVibrationSettings = new VibrationSettings(mContext, mHandler);
-        mVibrationScaler = new VibrationScaler(mContext, mVibrationSettings);
+        VibrationConfig vibrationConfig = new VibrationConfig(context.getResources());
+        mVibrationSettings = new VibrationSettings(mContext, mHandler, vibrationConfig);
+        mVibrationScaler = new VibrationScaler(vibrationConfig, mVibrationSettings);
         mVibratorControlService = new VibratorControlService(mContext,
                 injector.createVibratorControllerHolder(), mVibrationScaler, mVibrationSettings,
                 mFrameworkStatsLogger, mLock);
@@ -467,6 +469,14 @@
                 this, flags, privFlags);
     }
 
+    @Override // Binder call
+    public void performHapticFeedbackForInputDevice(int uid, int deviceId, String opPkg,
+            int constant, int inputDeviceId, int inputSource, String reason, int flags,
+            int privFlags) {
+        performHapticFeedbackForInputDeviceInternal(uid, deviceId, opPkg, constant, inputDeviceId,
+                inputSource, reason, /* token= */ this, flags, privFlags);
+    }
+
     /**
      * An internal-only version of performHapticFeedback that allows the caller access to the
      * {@link HalVibration}.
@@ -501,6 +511,24 @@
     }
 
     /**
+     * An internal-only version of performHapticFeedback that allows the caller access to the
+     * {@link HalVibration}.
+     * The Vibration is only returned if it is ongoing after this method returns.
+     */
+    @VisibleForTesting
+    @Nullable
+    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.");
+        }
+        return performHapticFeedbackInternal(uid, deviceId, opPkg, constant, reason, /* token= */
+                this, flags, privFlags);
+    }
+
+    /**
      * An internal-only version of vibrate that allows the caller access to the
      * {@link HalVibration}.
      * The Vibration is only returned if it is ongoing after this method returns.
@@ -1672,7 +1700,7 @@
             IBinder.DeathRecipient {
 
         public final ExternalVibration externalVibration;
-        public ExternalVibrationScale scale = new ExternalVibrationScale();
+        public final ExternalVibrationScale scale = new ExternalVibrationScale();
 
         private Vibration.Status mStatus;
 
@@ -1686,8 +1714,18 @@
             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);
         }
@@ -2021,7 +2059,7 @@
             // Create Vibration.Stats as close to the received request as possible, for tracking.
             ExternalVibrationHolder vibHolder = new ExternalVibrationHolder(vib);
             // Mute the request until we run all the checks and accept the vibration.
-            vibHolder.scale.scaleLevel = ExternalVibrationScale.ScaleLevel.SCALE_MUTE;
+            vibHolder.muteScale();
             boolean alreadyUnderExternalControl = false;
             boolean waitForCompletion = false;
 
@@ -2120,7 +2158,7 @@
                                 new Vibration.EndInfo(Vibration.Status.IGNORED_ERROR_CANCELLING),
                                 /* continueExternalControl= */ false);
                         // Mute the request, vibration will be ignored.
-                        vibHolder.scale.scaleLevel = ExternalVibrationScale.ScaleLevel.SCALE_MUTE;
+                        vibHolder.muteScale();
                     }
                     return vibHolder.scale;
                 }
diff --git a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
index 68f3738..19eba5f 100644
--- a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
+++ b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
@@ -330,6 +330,7 @@
         builder.setIsTranslucent(isTranslucent);
         builder.setWindowingMode(source.getWindowingMode());
         builder.setAppearance(mainWindow.mAttrs.insetsFlags.appearance);
+        builder.setUiMode(activity.getConfiguration().uiMode);
 
         final Configuration taskConfig = activity.getTask().getConfiguration();
         final int displayRotation = taskConfig.windowConfiguration.getDisplayRotation();
@@ -448,7 +449,8 @@
                 mainWindow.getWindowConfiguration().getRotation(), new Point(taskWidth, taskHeight),
                 contentInsets, letterboxInsets, false /* isLowResolution */,
                 false /* isRealSnapshot */, source.getWindowingMode(),
-                attrs.insetsFlags.appearance, false /* isTranslucent */, false /* hasImeSurface */);
+                attrs.insetsFlags.appearance, false /* isTranslucent */, false /* hasImeSurface */,
+                topActivity.getConfiguration().uiMode /* uiMode */);
         return validateSnapshot(taskSnapshot);
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 7210098..5c096ec 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2524,8 +2524,11 @@
         // trampoline that will be always created and finished immediately. Then give a chance to
         // see if the snapshot is usable for the current running activity so the transition will
         // look smoother, instead of showing a splash screen on the second launch.
-        if (!newTask && taskSwitch && processRunning && !activityCreated && task.intent != null
-                && mActivityComponent.equals(task.intent.getComponent())) {
+        if (!newTask && taskSwitch && !activityCreated && task.intent != null
+                // Another case where snapshot is allowed to be used is if this activity has not yet
+                // been created && is translucent or floating.
+                // The component isn't necessary to be matched in this case.
+                && (!mOccludesParent || mActivityComponent.equals(task.intent.getComponent()))) {
             final ActivityRecord topAttached = task.getActivity(ActivityRecord::attachedToProcess);
             if (topAttached != null) {
                 if (topAttached.isSnapshotCompatible(snapshot)
@@ -5463,6 +5466,7 @@
             }
         }
 
+        mAtmService.mBackNavigationController.onAppVisibilityChanged(this, visible);
         onChildVisibilityRequested(visible);
 
         final DisplayContent displayContent = getDisplayContent();
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 509a060..8ef2693 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2846,6 +2846,11 @@
         } finally {
             SaferIntentUtils.DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS.set(false);
             synchronized (mService.mGlobalLock) {
+                // Remove the empty task in case the activity was failed to be launched on the
+                // task that was restored from Recents.
+                if (!task.hasChild() && task.shouldRemoveSelfOnLastChildRemoval()) {
+                    task.removeIfPossible("start-from-recents");
+                }
                 mService.continueWindowLayout();
             }
         }
diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java
index d38edfc..4290051 100644
--- a/services/core/java/com/android/server/wm/AppCompatController.java
+++ b/services/core/java/com/android/server/wm/AppCompatController.java
@@ -40,6 +40,8 @@
     private final AppCompatOverrides mAppCompatOverrides;
     @NonNull
     private final AppCompatDeviceStateQuery mAppCompatDeviceStateQuery;
+    @NonNull
+    private final AppCompatLetterboxPolicy mAppCompatLetterboxPolicy;
 
     AppCompatController(@NonNull WindowManagerService wmService,
                         @NonNull ActivityRecord activityRecord) {
@@ -57,6 +59,7 @@
                 mTransparentPolicy, mAppCompatOverrides);
         mAppCompatReachabilityPolicy = new AppCompatReachabilityPolicy(mActivityRecord,
                 wmService.mAppCompatConfiguration);
+        mAppCompatLetterboxPolicy = new AppCompatLetterboxPolicy(mActivityRecord);
     }
 
     @NonNull
@@ -113,6 +116,11 @@
     }
 
     @NonNull
+    AppCompatLetterboxPolicy getAppCompatLetterboxPolicy() {
+        return mAppCompatLetterboxPolicy;
+    }
+
+    @NonNull
     AppCompatFocusOverrides getAppCompatFocusOverrides() {
         return mAppCompatOverrides.getAppCompatFocusOverrides();
     }
@@ -127,4 +135,9 @@
         return mAppCompatDeviceStateQuery;
     }
 
+    @NonNull
+    AppCompatLetterboxOverrides getAppCompatLetterboxOverrides() {
+        return mAppCompatOverrides.getAppCompatLetterboxOverrides();
+    }
+
 }
diff --git a/services/core/java/com/android/server/wm/AppCompatLetterboxOverrides.java b/services/core/java/com/android/server/wm/AppCompatLetterboxOverrides.java
new file mode 100644
index 0000000..24ed14c
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AppCompatLetterboxOverrides.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND;
+import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
+import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR;
+import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_WALLPAPER;
+
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.graphics.Color;
+import android.util.Slog;
+import android.view.WindowManager;
+
+import com.android.server.wm.AppCompatConfiguration.LetterboxBackgroundType;
+
+/**
+ * Encapsulates overrides and configuration related to the Letterboxing policy.
+ */
+class AppCompatLetterboxOverrides {
+
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "AppCompatLetterboxOverrides" : TAG_ATM;
+
+    @NonNull
+    private final ActivityRecord mActivityRecord;
+    @NonNull
+    private final AppCompatConfiguration mAppCompatConfiguration;
+
+    private boolean mShowWallpaperForLetterboxBackground;
+
+    AppCompatLetterboxOverrides(@NonNull ActivityRecord activityRecord,
+            @NonNull AppCompatConfiguration appCompatConfiguration) {
+        mActivityRecord = activityRecord;
+        mAppCompatConfiguration = appCompatConfiguration;
+    }
+
+    boolean shouldLetterboxHaveRoundedCorners() {
+        // TODO(b/214030873): remove once background is drawn for transparent activities
+        // Letterbox shouldn't have rounded corners if the activity is transparent
+        return mAppCompatConfiguration.isLetterboxActivityCornersRounded()
+                && mActivityRecord.fillsParent();
+    }
+
+    boolean isLetterboxEducationEnabled() {
+        return mAppCompatConfiguration.getIsEducationEnabled();
+    }
+
+    boolean hasWallpaperBackgroundForLetterbox() {
+        return mShowWallpaperForLetterboxBackground;
+    }
+
+    boolean checkWallpaperBackgroundForLetterbox(boolean wallpaperShouldBeShown) {
+        if (mShowWallpaperForLetterboxBackground != wallpaperShouldBeShown) {
+            mShowWallpaperForLetterboxBackground = wallpaperShouldBeShown;
+            return true;
+        }
+        return false;
+    }
+
+    @NonNull
+    Color getLetterboxBackgroundColor() {
+        final WindowState w = mActivityRecord.findMainWindow();
+        if (w == null || w.isLetterboxedForDisplayCutout()) {
+            return Color.valueOf(Color.BLACK);
+        }
+        final @LetterboxBackgroundType int letterboxBackgroundType =
+                mAppCompatConfiguration.getLetterboxBackgroundType();
+        final ActivityManager.TaskDescription taskDescription = mActivityRecord.taskDescription;
+        switch (letterboxBackgroundType) {
+            case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING:
+                if (taskDescription != null && taskDescription.getBackgroundColorFloating() != 0) {
+                    return Color.valueOf(taskDescription.getBackgroundColorFloating());
+                }
+                break;
+            case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND:
+                if (taskDescription != null && taskDescription.getBackgroundColor() != 0) {
+                    return Color.valueOf(taskDescription.getBackgroundColor());
+                }
+                break;
+            case LETTERBOX_BACKGROUND_WALLPAPER:
+                if (hasWallpaperBackgroundForLetterbox()) {
+                    // Color is used for translucent scrim that dims wallpaper.
+                    return mAppCompatConfiguration.getLetterboxBackgroundColor();
+                }
+                Slog.w(TAG, "Wallpaper option is selected for letterbox background but "
+                        + "blur is not supported by a device or not supported in the current "
+                        + "window configuration or both alpha scrim and blur radius aren't "
+                        + "provided so using solid color background");
+                break;
+            case LETTERBOX_BACKGROUND_SOLID_COLOR:
+                return mAppCompatConfiguration.getLetterboxBackgroundColor();
+            default:
+                throw new AssertionError(
+                        "Unexpected letterbox background type: " + letterboxBackgroundType);
+        }
+        // If picked option configured incorrectly or not supported then default to a solid color
+        // background.
+        return mAppCompatConfiguration.getLetterboxBackgroundColor();
+    }
+
+    int getLetterboxActivityCornersRadius() {
+        return mAppCompatConfiguration.getLetterboxActivityCornersRadius();
+    }
+
+    boolean isLetterboxActivityCornersRounded() {
+        return mAppCompatConfiguration.isLetterboxActivityCornersRounded();
+    }
+
+    @LetterboxBackgroundType
+    int getLetterboxBackgroundType() {
+        return mAppCompatConfiguration.getLetterboxBackgroundType();
+    }
+
+    int getLetterboxWallpaperBlurRadiusPx() {
+        int blurRadius = mAppCompatConfiguration.getLetterboxBackgroundWallpaperBlurRadiusPx();
+        return Math.max(blurRadius, 0);
+    }
+
+    float getLetterboxWallpaperDarkScrimAlpha() {
+        float alpha = mAppCompatConfiguration.getLetterboxBackgroundWallpaperDarkScrimAlpha();
+        // No scrim by default.
+        return (alpha < 0 || alpha >= 1) ? 0.0f : alpha;
+    }
+
+    boolean isLetterboxWallpaperBlurSupported() {
+        return mAppCompatConfiguration.mContext.getSystemService(WindowManager.class)
+                .isCrossWindowBlurEnabled();
+    }
+
+}
diff --git a/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java b/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java
new file mode 100644
index 0000000..b03af4c
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java
@@ -0,0 +1,372 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+
+import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_WALLPAPER;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.view.SurfaceControl;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.statusbar.LetterboxDetails;
+import com.android.server.wm.AppCompatConfiguration.LetterboxBackgroundType;
+
+/**
+ * Encapsulates the logic for the Letterboxing policy.
+ */
+class AppCompatLetterboxPolicy {
+
+    @NonNull
+    private final ActivityRecord mActivityRecord;
+    @NonNull
+    private final LetterboxPolicyState mLetterboxPolicyState;
+    @NonNull
+    private final AppCompatRoundedCorners mAppCompatRoundedCorners;
+
+    private boolean mLastShouldShowLetterboxUi;
+
+    AppCompatLetterboxPolicy(@NonNull ActivityRecord  activityRecord) {
+        mActivityRecord = activityRecord;
+        mLetterboxPolicyState = new LetterboxPolicyState();
+        // TODO (b/358334569) Improve cutout logic dependency on app compat.
+        mAppCompatRoundedCorners = new AppCompatRoundedCorners(mActivityRecord,
+                this::isLetterboxedNotForDisplayCutout);
+    }
+
+    /** Cleans up {@link Letterbox} if it exists.*/
+    void destroy() {
+        mLetterboxPolicyState.destroy();
+    }
+
+    /** @return {@value true} if the letterbox policy is running and the activity letterboxed. */
+    boolean isRunning() {
+        return mLetterboxPolicyState.isRunning();
+    }
+
+    void onMovedToDisplay(int displayId) {
+        mLetterboxPolicyState.onMovedToDisplay(displayId);
+    }
+
+    /** Gets the letterbox insets. The insets will be empty if there is no letterbox. */
+    @NonNull
+    Rect getLetterboxInsets() {
+        return mLetterboxPolicyState.getLetterboxInsets();
+    }
+
+    /** Gets the inner bounds of letterbox. The bounds will be empty if there is no letterbox. */
+    void getLetterboxInnerBounds(@NonNull Rect outBounds) {
+        mLetterboxPolicyState.getLetterboxInnerBounds(outBounds);
+    }
+
+    @Nullable
+    LetterboxDetails getLetterboxDetails() {
+        return mLetterboxPolicyState.getLetterboxDetails();
+    }
+
+    /**
+     * @return {@code true} if bar shown within a given rectangle is allowed to be fully transparent
+     *     when the current activity is displayed.
+     */
+    boolean isFullyTransparentBarAllowed(@NonNull Rect rect) {
+        return mLetterboxPolicyState.isFullyTransparentBarAllowed(rect);
+    }
+
+    void updateLetterboxSurfaceIfNeeded(@NonNull WindowState winHint,
+            @NonNull SurfaceControl.Transaction t,
+            @NonNull SurfaceControl.Transaction inputT) {
+        mLetterboxPolicyState.updateLetterboxSurfaceIfNeeded(winHint, t, inputT);
+    }
+
+    void updateLetterboxSurfaceIfNeeded(@NonNull WindowState winHint) {
+        mLetterboxPolicyState.updateLetterboxSurfaceIfNeeded(winHint,
+                mActivityRecord.getSyncTransaction(), mActivityRecord.getPendingTransaction());
+    }
+
+    void start(@NonNull WindowState w) {
+        if (shouldNotLayoutLetterbox(w)) {
+            return;
+        }
+        mAppCompatRoundedCorners.updateRoundedCornersIfNeeded(w);
+        updateWallpaperForLetterbox(w);
+        if (shouldShowLetterboxUi(w)) {
+            mLetterboxPolicyState.layoutLetterboxIfNeeded(w);
+        }  else {
+            mLetterboxPolicyState.hide();
+        }
+    }
+
+    @VisibleForTesting
+    boolean shouldShowLetterboxUi(@NonNull WindowState mainWindow) {
+        if (mActivityRecord.mAppCompatController.getAppCompatOrientationOverrides()
+                .getIsRelaunchingAfterRequestedOrientationChanged()) {
+            return mLastShouldShowLetterboxUi;
+        }
+
+        final boolean shouldShowLetterboxUi =
+                (mActivityRecord.isInLetterboxAnimation() || mActivityRecord.isVisible()
+                        || mActivityRecord.isVisibleRequested())
+                        && mainWindow.areAppWindowBoundsLetterboxed()
+                        // Check for FLAG_SHOW_WALLPAPER explicitly instead of using
+                        // WindowContainer#showWallpaper because the later will return true when
+                        // this activity is using blurred wallpaper for letterbox background.
+                        && (mainWindow.getAttrs().flags & FLAG_SHOW_WALLPAPER) == 0;
+
+        mLastShouldShowLetterboxUi = shouldShowLetterboxUi;
+
+        return shouldShowLetterboxUi;
+    }
+
+    @VisibleForTesting
+    @Nullable
+    Rect getCropBoundsIfNeeded(@NonNull final WindowState mainWindow) {
+        return mAppCompatRoundedCorners.getCropBoundsIfNeeded(mainWindow);
+    }
+
+    /**
+     * Returns rounded corners radius the letterboxed activity should have based on override in
+     * R.integer.config_letterboxActivityCornersRadius or min device bottom corner radii.
+     * Device corners can be different on the right and left sides, but we use the same radius
+     * for all corners for consistency and pick a minimal bottom one for consistency with a
+     * taskbar rounded corners.
+     *
+     * @param mainWindow    The {@link WindowState} to consider for the rounded corners calculation.
+     */
+    int getRoundedCornersRadius(@NonNull final WindowState mainWindow) {
+        return mAppCompatRoundedCorners.getRoundedCornersRadius(mainWindow);
+    }
+
+    private void updateWallpaperForLetterbox(@NonNull WindowState mainWindow) {
+        final AppCompatLetterboxOverrides letterboxOverrides = mActivityRecord
+                .mAppCompatController.getAppCompatLetterboxOverrides();
+        final @LetterboxBackgroundType int letterboxBackgroundType =
+                letterboxOverrides.getLetterboxBackgroundType();
+        boolean wallpaperShouldBeShown =
+                letterboxBackgroundType == LETTERBOX_BACKGROUND_WALLPAPER
+                        // Don't use wallpaper as a background if letterboxed for display cutout.
+                        && isLetterboxedNotForDisplayCutout(mainWindow)
+                        // Check that dark scrim alpha or blur radius are provided
+                        && (letterboxOverrides.getLetterboxWallpaperBlurRadiusPx() > 0
+                        || letterboxOverrides.getLetterboxWallpaperDarkScrimAlpha() > 0)
+                        // Check that blur is supported by a device if blur radius is provided.
+                        && (letterboxOverrides.getLetterboxWallpaperBlurRadiusPx() <= 0
+                        || letterboxOverrides.isLetterboxWallpaperBlurSupported());
+        if (letterboxOverrides.checkWallpaperBackgroundForLetterbox(wallpaperShouldBeShown)) {
+            mActivityRecord.requestUpdateWallpaperIfNeeded();
+        }
+    }
+
+    private boolean isLetterboxedNotForDisplayCutout(@NonNull WindowState mainWindow) {
+        return shouldShowLetterboxUi(mainWindow)
+                && !mainWindow.isLetterboxedForDisplayCutout();
+    }
+
+    private static boolean shouldNotLayoutLetterbox(@Nullable WindowState w) {
+        if (w == null) {
+            return true;
+        }
+        final int type = w.mAttrs.type;
+        // Allow letterbox to be displayed early for base application or application starting
+        // windows even if it is not on the top z order to prevent flickering when the
+        // letterboxed window is brought to the top
+        return (type != TYPE_BASE_APPLICATION && type != TYPE_APPLICATION_STARTING)
+                || w.mAnimatingExit;
+    }
+
+    private class LetterboxPolicyState {
+
+        @Nullable
+        private Letterbox mLetterbox;
+
+        void layoutLetterboxIfNeeded(@NonNull WindowState w) {
+            if (!isRunning()) {
+                final AppCompatLetterboxOverrides letterboxOverrides = mActivityRecord
+                        .mAppCompatController.getAppCompatLetterboxOverrides();
+                final AppCompatReachabilityPolicy reachabilityPolicy = mActivityRecord
+                        .mAppCompatController.getAppCompatReachabilityPolicy();
+                mLetterbox = new Letterbox(() -> mActivityRecord.makeChildSurface(null),
+                        mActivityRecord.mWmService.mTransactionFactory,
+                        reachabilityPolicy, letterboxOverrides,
+                        this::getLetterboxParentSurface);
+                mLetterbox.attachInput(w);
+                mActivityRecord.mAppCompatController.getAppCompatReachabilityPolicy()
+                        .setLetterboxInnerBoundsSupplier(mLetterbox::getInnerFrame);
+            }
+            final Point letterboxPosition = new Point();
+            if (mActivityRecord.isInLetterboxAnimation()) {
+                // In this case we attach the letterbox to the task instead of the activity.
+                mActivityRecord.getTask().getPosition(letterboxPosition);
+            } else {
+                mActivityRecord.getPosition(letterboxPosition);
+            }
+
+            // Get the bounds of the "space-to-fill". The transformed bounds have the highest
+            // priority because the activity is launched in a rotated environment. In multi-window
+            // mode, the taskFragment-level represents this for both split-screen
+            // and activity-embedding. In fullscreen-mode, the task container does
+            // (since the orientation letterbox is also applied to the task).
+            final Rect transformedBounds = mActivityRecord.getFixedRotationTransformDisplayBounds();
+            final Rect spaceToFill = transformedBounds != null
+                    ? transformedBounds
+                    : mActivityRecord.inMultiWindowMode()
+                            ? mActivityRecord.getTaskFragment().getBounds()
+                            : mActivityRecord.getRootTask().getParent().getBounds();
+            // In case of translucent activities an option is to use the WindowState#getFrame() of
+            // the first opaque activity beneath. In some cases (e.g. an opaque activity is using
+            // non MATCH_PARENT layouts or a Dialog theme) this might not provide the correct
+            // information and in particular it might provide a value for a smaller area making
+            // the letterbox overlap with the translucent activity's frame.
+            // If we use WindowState#getFrame() for the translucent activity's letterbox inner
+            // frame, the letterbox will then be overlapped with the translucent activity's frame.
+            // Because the surface layer of letterbox is lower than an activity window, this
+            // won't crop the content, but it may affect other features that rely on values stored
+            // in mLetterbox, e.g. transitions, a status bar scrim and recents preview in Launcher
+            // For this reason we use ActivityRecord#getBounds() that the translucent activity
+            // inherits from the first opaque activity beneath and also takes care of the scaling
+            // in case of activities in size compat mode.
+            final TransparentPolicy transparentPolicy =
+                    mActivityRecord.mAppCompatController.getTransparentPolicy();
+            final Rect innerFrame =
+                    transparentPolicy.isRunning() ? mActivityRecord.getBounds() : w.getFrame();
+            mLetterbox.layout(spaceToFill, innerFrame, letterboxPosition);
+            if (mActivityRecord.mAppCompatController.getAppCompatReachabilityOverrides()
+                    .isDoubleTapEvent()) {
+                // We need to notify Shell that letterbox position has changed.
+                mActivityRecord.getTask().dispatchTaskInfoChangedIfNeeded(true /* force */);
+            }
+        }
+
+        /**
+         * @return  {@code true} if the policy is running and so if the current activity is
+         *          letterboxed.
+         */
+        boolean isRunning() {
+            return mLetterbox != null;
+        }
+
+        void onMovedToDisplay(int displayId) {
+            if (isRunning()) {
+                mLetterbox.onMovedToDisplay(displayId);
+            }
+        }
+
+        /** Cleans up {@link Letterbox} if it exists.*/
+        void destroy() {
+            if (isRunning()) {
+                mLetterbox.destroy();
+                mLetterbox = null;
+            }
+            mActivityRecord.mAppCompatController.getAppCompatReachabilityPolicy()
+                    .setLetterboxInnerBoundsSupplier(null);
+        }
+
+        void updateLetterboxSurfaceIfNeeded(@NonNull WindowState winHint,
+                @NonNull SurfaceControl.Transaction t,
+                @NonNull SurfaceControl.Transaction inputT) {
+            if (shouldNotLayoutLetterbox(winHint)) {
+                return;
+            }
+            start(winHint);
+            if (isRunning() && mLetterbox.needsApplySurfaceChanges()) {
+                mLetterbox.applySurfaceChanges(t, inputT);
+            }
+        }
+
+        void hide() {
+            if (isRunning()) {
+                mLetterbox.hide();
+            }
+        }
+
+        /** Gets the letterbox insets. The insets will be empty if there is no letterbox. */
+        @NonNull
+        Rect getLetterboxInsets() {
+            if (isRunning()) {
+                return mLetterbox.getInsets();
+            } else {
+                return new Rect();
+            }
+        }
+
+        /** Gets the inner bounds of letterbox. The bounds will be empty with no letterbox. */
+        void getLetterboxInnerBounds(@NonNull Rect outBounds) {
+            if (isRunning()) {
+                outBounds.set(mLetterbox.getInnerFrame());
+                final WindowState w = mActivityRecord.findMainWindow();
+                if (w != null) {
+                    AppCompatUtils.adjustBoundsForTaskbar(w, outBounds);
+                }
+            } else {
+                outBounds.setEmpty();
+            }
+        }
+
+        /** Gets the outer bounds of letterbox. The bounds will be empty with no letterbox. */
+        private void getLetterboxOuterBounds(@NonNull Rect outBounds) {
+            if (isRunning()) {
+                outBounds.set(mLetterbox.getOuterFrame());
+            } else {
+                outBounds.setEmpty();
+            }
+        }
+
+        /**
+         * @return {@code true} if bar shown within a given rectangle is allowed to be fully
+         *          transparent when the current activity is displayed.
+         */
+        boolean isFullyTransparentBarAllowed(@NonNull Rect rect) {
+            return !isRunning() || mLetterbox.notIntersectsOrFullyContains(rect);
+        }
+
+        @Nullable
+        LetterboxDetails getLetterboxDetails() {
+            final WindowState w = mActivityRecord.findMainWindow();
+            if (!isRunning() || w == null || w.isLetterboxedForDisplayCutout()) {
+                return null;
+            }
+            final Rect letterboxInnerBounds = new Rect();
+            final Rect letterboxOuterBounds = new Rect();
+            getLetterboxInnerBounds(letterboxInnerBounds);
+            getLetterboxOuterBounds(letterboxOuterBounds);
+
+            if (letterboxInnerBounds.isEmpty() || letterboxOuterBounds.isEmpty()) {
+                return null;
+            }
+
+            return new LetterboxDetails(
+                    letterboxInnerBounds,
+                    letterboxOuterBounds,
+                    w.mAttrs.insetsFlags.appearance
+            );
+        }
+
+        @Nullable
+        private SurfaceControl getLetterboxParentSurface() {
+            if (mActivityRecord.isInLetterboxAnimation()) {
+                return mActivityRecord.getTask().getSurfaceControl();
+            }
+            return mActivityRecord.getSurfaceControl();
+        }
+
+    }
+}
diff --git a/services/core/java/com/android/server/wm/AppCompatOverrides.java b/services/core/java/com/android/server/wm/AppCompatOverrides.java
index 80bbee3..2f03105 100644
--- a/services/core/java/com/android/server/wm/AppCompatOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatOverrides.java
@@ -37,6 +37,8 @@
     private final AppCompatResizeOverrides mAppCompatResizeOverrides;
     @NonNull
     private final AppCompatReachabilityOverrides mAppCompatReachabilityOverrides;
+    @NonNull
+    private final AppCompatLetterboxOverrides mAppCompatLetterboxOverrides;
 
     AppCompatOverrides(@NonNull ActivityRecord activityRecord,
             @NonNull AppCompatConfiguration appCompatConfiguration,
@@ -54,6 +56,8 @@
         mAppCompatFocusOverrides = new AppCompatFocusOverrides(activityRecord,
                 appCompatConfiguration, optPropBuilder);
         mAppCompatResizeOverrides = new AppCompatResizeOverrides(activityRecord, optPropBuilder);
+        mAppCompatLetterboxOverrides = new AppCompatLetterboxOverrides(activityRecord,
+                appCompatConfiguration);
     }
 
     @NonNull
@@ -85,4 +89,9 @@
     AppCompatReachabilityOverrides getAppCompatReachabilityOverrides() {
         return mAppCompatReachabilityOverrides;
     }
+
+    @NonNull
+    AppCompatLetterboxOverrides getAppCompatLetterboxOverrides() {
+        return mAppCompatLetterboxOverrides;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/AppCompatReachabilityOverrides.java b/services/core/java/com/android/server/wm/AppCompatReachabilityOverrides.java
index b9bdc32..caff96b 100644
--- a/services/core/java/com/android/server/wm/AppCompatReachabilityOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatReachabilityOverrides.java
@@ -35,7 +35,6 @@
 import android.content.res.Configuration;
 import android.graphics.Rect;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.window.flags.Flags;
 
 /**
@@ -112,12 +111,10 @@
                 : mAppCompatConfiguration.getLetterboxVerticalPositionMultiplier(tabletopMode);
     }
 
-    @VisibleForTesting
     boolean isHorizontalReachabilityEnabled() {
         return isHorizontalReachabilityEnabled(mActivityRecord.getParent().getConfiguration());
     }
 
-    @VisibleForTesting
     boolean isVerticalReachabilityEnabled() {
         return isVerticalReachabilityEnabled(mActivityRecord.getParent().getConfiguration());
     }
diff --git a/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java b/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java
index 90bfddb..c3bf116 100644
--- a/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java
@@ -31,6 +31,8 @@
 import android.annotation.Nullable;
 import android.graphics.Rect;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.util.function.Supplier;
 
 /**
@@ -43,7 +45,8 @@
     @NonNull
     private final AppCompatConfiguration mAppCompatConfiguration;
     @Nullable
-    private Supplier<Rect> mLetterboxInnerBoundsSupplier;
+    @VisibleForTesting
+    Supplier<Rect> mLetterboxInnerBoundsSupplier;
 
     AppCompatReachabilityPolicy(@NonNull ActivityRecord activityRecord,
             @NonNull AppCompatConfiguration appCompatConfiguration) {
diff --git a/services/core/java/com/android/server/wm/AppCompatRoundedCorners.java b/services/core/java/com/android/server/wm/AppCompatRoundedCorners.java
new file mode 100644
index 0000000..077ce00
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AppCompatRoundedCorners.java
@@ -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.server.wm;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Rect;
+import android.view.InsetsState;
+import android.view.RoundedCorner;
+import android.view.SurfaceControl;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.function.Predicate;
+
+/**
+ * Utility to unify rounded corners in app compat.
+ */
+class AppCompatRoundedCorners {
+
+    @NonNull
+    private final ActivityRecord mActivityRecord;
+    @NonNull
+    private final Predicate<WindowState> mIsLetterboxedNotForDisplayCutout;
+
+    AppCompatRoundedCorners(@NonNull ActivityRecord  activityRecord,
+            @NonNull Predicate<WindowState> isLetterboxedNotForDisplayCutout) {
+        mActivityRecord = activityRecord;
+        mIsLetterboxedNotForDisplayCutout = isLetterboxedNotForDisplayCutout;
+    }
+
+    void updateRoundedCornersIfNeeded(@NonNull final WindowState mainWindow) {
+        final SurfaceControl windowSurface = mainWindow.getSurfaceControl();
+        if (windowSurface == null || !windowSurface.isValid()) {
+            return;
+        }
+
+        // cropBounds must be non-null for the cornerRadius to be ever applied.
+        mActivityRecord.getSyncTransaction()
+                .setCrop(windowSurface, getCropBoundsIfNeeded(mainWindow))
+                .setCornerRadius(windowSurface, getRoundedCornersRadius(mainWindow));
+    }
+
+    @VisibleForTesting
+    @Nullable
+    Rect getCropBoundsIfNeeded(@NonNull final WindowState mainWindow) {
+        if (!requiresRoundedCorners(mainWindow) || mActivityRecord.isInLetterboxAnimation()) {
+            // We don't want corner radius on the window.
+            // In the case the ActivityRecord requires a letterboxed animation we never want
+            // rounded corners on the window because rounded corners are applied at the
+            // animation-bounds surface level and rounded corners on the window would interfere
+            // with that leading to unexpected rounded corner positioning during the animation.
+            return null;
+        }
+
+        final Rect cropBounds = new Rect(mActivityRecord.getBounds());
+
+        // In case of translucent activities we check if the requested size is different from
+        // the size provided using inherited bounds. In that case we decide to not apply rounded
+        // corners because we assume the specific layout would. This is the case when the layout
+        // of the translucent activity uses only a part of all the bounds because of the use of
+        // LayoutParams.WRAP_CONTENT.
+        final TransparentPolicy transparentPolicy = mActivityRecord.mAppCompatController
+                .getTransparentPolicy();
+        if (transparentPolicy.isRunning() && (cropBounds.width() != mainWindow.mRequestedWidth
+                || cropBounds.height() != mainWindow.mRequestedHeight)) {
+            return null;
+        }
+
+        // It is important to call {@link #adjustBoundsIfNeeded} before {@link cropBounds.offsetTo}
+        // because taskbar bounds used in {@link #adjustBoundsIfNeeded}
+        // are in screen coordinates
+        AppCompatUtils.adjustBoundsForTaskbar(mainWindow, cropBounds);
+
+        final float scale = mainWindow.mInvGlobalScale;
+        if (scale != 1f && scale > 0f) {
+            cropBounds.scale(scale);
+        }
+
+        // ActivityRecord bounds are in screen coordinates while (0,0) for activity's surface
+        // control is in the top left corner of an app window so offsetting bounds
+        // accordingly.
+        cropBounds.offsetTo(0, 0);
+        return cropBounds;
+    }
+
+    /**
+     * Returns rounded corners radius the letterboxed activity should have based on override in
+     * R.integer.config_letterboxActivityCornersRadius or min device bottom corner radii.
+     * Device corners can be different on the right and left sides, but we use the same radius
+     * for all corners for consistency and pick a minimal bottom one for consistency with a
+     * taskbar rounded corners.
+     *
+     * @param mainWindow    The {@link WindowState} to consider for rounded corners calculation.
+     */
+    int getRoundedCornersRadius(@NonNull final WindowState mainWindow) {
+        if (!requiresRoundedCorners(mainWindow)) {
+            return 0;
+        }
+        final AppCompatLetterboxOverrides letterboxOverrides = mActivityRecord
+                .mAppCompatController.getAppCompatLetterboxOverrides();
+        final int radius;
+        if (letterboxOverrides.getLetterboxActivityCornersRadius() >= 0) {
+            radius = letterboxOverrides.getLetterboxActivityCornersRadius();
+        } else {
+            final InsetsState insetsState = mainWindow.getInsetsState();
+            radius = Math.min(
+                    getInsetsStateCornerRadius(insetsState, RoundedCorner.POSITION_BOTTOM_LEFT),
+                    getInsetsStateCornerRadius(insetsState, RoundedCorner.POSITION_BOTTOM_RIGHT));
+        }
+
+        final float scale = mainWindow.mInvGlobalScale;
+        return (scale != 1f && scale > 0f) ? (int) (scale * radius) : radius;
+    }
+
+    private static int getInsetsStateCornerRadius(@NonNull InsetsState insetsState,
+            @RoundedCorner.Position int position) {
+        final RoundedCorner corner = insetsState.getRoundedCorners().getRoundedCorner(position);
+        return corner == null ? 0 : corner.getRadius();
+    }
+
+    private boolean requiresRoundedCorners(@NonNull final WindowState mainWindow) {
+        final AppCompatLetterboxOverrides letterboxOverrides = mActivityRecord
+                .mAppCompatController.getAppCompatLetterboxOverrides();
+        return mIsLetterboxedNotForDisplayCutout.test(mainWindow)
+                && letterboxOverrides.isLetterboxActivityCornersRounded();
+    }
+
+}
diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java
index 0244d27..2b842d9 100644
--- a/services/core/java/com/android/server/wm/AppCompatUtils.java
+++ b/services/core/java/com/android/server/wm/AppCompatUtils.java
@@ -28,13 +28,16 @@
 import android.app.TaskInfo;
 import android.content.res.Configuration;
 import android.graphics.Rect;
+import android.view.InsetsSource;
+import android.view.InsetsState;
+import android.view.WindowInsets;
 
 import java.util.function.BooleanSupplier;
 
 /**
  * Utilities for App Compat policies and overrides.
  */
-class AppCompatUtils {
+final class AppCompatUtils {
 
     /**
      * Lazy version of a {@link BooleanSupplier} which access an existing BooleanSupplier and
@@ -212,6 +215,40 @@
         return "UNKNOWN_REASON";
     }
 
+    /**
+     * Returns the taskbar in case it is visible and expanded in height, otherwise returns null.
+     */
+    @Nullable
+    static InsetsSource getExpandedTaskbarOrNull(@NonNull final WindowState mainWindow) {
+        final InsetsState state = mainWindow.getInsetsState();
+        for (int i = state.sourceSize() - 1; i >= 0; i--) {
+            final InsetsSource source = state.sourceAt(i);
+            if (source.getType() == WindowInsets.Type.navigationBars()
+                    && source.hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER)
+                    && source.isVisible()) {
+                return source;
+            }
+        }
+        return null;
+    }
+
+    static void adjustBoundsForTaskbar(@NonNull final WindowState mainWindow,
+            @NonNull final Rect bounds) {
+        // Rounded corners should be displayed above the taskbar. When taskbar is hidden,
+        // an insets frame is equal to a navigation bar which shouldn't affect position of
+        // rounded corners since apps are expected to handle navigation bar inset.
+        // This condition checks whether the taskbar is visible.
+        // Do not crop the taskbar inset if the window is in immersive mode - the user can
+        // swipe to show/hide the taskbar as an overlay.
+        // Adjust the bounds only in case there is an expanded taskbar,
+        // otherwise the rounded corners will be shown behind the navbar.
+        final InsetsSource expandedTaskbarOrNull = getExpandedTaskbarOrNull(mainWindow);
+        if (expandedTaskbarOrNull != null) {
+            // Rounded corners should be displayed above the expanded taskbar.
+            bounds.bottom = Math.min(bounds.bottom, expandedTaskbarOrNull.getFrame().top);
+        }
+    }
+
     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/AppSnapshotLoader.java b/services/core/java/com/android/server/wm/AppSnapshotLoader.java
index ed65a2b..5b697e5 100644
--- a/services/core/java/com/android/server/wm/AppSnapshotLoader.java
+++ b/services/core/java/com/android/server/wm/AppSnapshotLoader.java
@@ -203,7 +203,7 @@
                     new Rect(proto.letterboxInsetLeft, proto.letterboxInsetTop,
                             proto.letterboxInsetRight, proto.letterboxInsetBottom),
                     loadLowResolutionBitmap, proto.isRealSnapshot, proto.windowingMode,
-                    proto.appearance, proto.isTranslucent, false /* hasImeSurface */);
+                    proto.appearance, proto.isTranslucent, false /* hasImeSurface */, proto.uiMode);
         } catch (IOException e) {
             Slog.w(TAG, "Unable to load task snapshot data for Id=" + id);
             return null;
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 48e1079..fe5b142 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -761,13 +761,55 @@
             if (isMonitorForRemote()) {
                 mObserver.sendResult(null /* result */);
             }
-            if (isMonitorAnimationOrTransition()) {
+            if (isMonitorAnimationOrTransition() && canCancelAnimations()) {
                 clearBackAnimations(true /* cancel */);
             }
             cancelPendingAnimation();
         }
     }
 
+    void onAppVisibilityChanged(@NonNull ActivityRecord ar, boolean visible) {
+        if (!mAnimationHandler.mComposed) {
+            return;
+        }
+
+        final boolean openingTransition = mAnimationHandler.mOpenAnimAdaptor
+                .mPreparedOpenTransition != null;
+        // Detect if another transition is collecting during predictive back animation.
+        if (openingTransition && !visible && mAnimationHandler.isTarget(ar, false /* open */)
+                && ar.mTransitionController.isCollecting(ar)) {
+            final TransitionController controller = ar.mTransitionController;
+            boolean collectTask = false;
+            ActivityRecord changedActivity = null;
+            for (int i = mAnimationHandler.mOpenActivities.length - 1; i >= 0; --i) {
+                final ActivityRecord next = mAnimationHandler.mOpenActivities[i];
+                if (next.mLaunchTaskBehind) {
+                    // collect previous activity, so shell side can handle the transition.
+                    controller.collect(next);
+                    collectTask = true;
+                    restoreLaunchBehind(next, true /* cancel */, false /* finishTransition */);
+                    changedActivity = next;
+                }
+            }
+            if (collectTask && mAnimationHandler.mOpenAnimAdaptor.mAdaptors[0].mSwitchType
+                    == AnimationHandler.TASK_SWITCH) {
+                final Task topTask = mAnimationHandler.mOpenAnimAdaptor.mAdaptors[0].getTopTask();
+                if (topTask != null) {
+                    WindowContainer parent = mAnimationHandler.mOpenActivities[0].getParent();
+                    while (parent != topTask && parent.isDescendantOf(topTask)) {
+                        controller.collect(parent);
+                        parent = parent.getParent();
+                    }
+                    controller.collect(topTask);
+                }
+            }
+            if (changedActivity != null) {
+                changedActivity.getDisplayContent().ensureActivitiesVisible(null /* starting */,
+                        true /* notifyClients */);
+            }
+        }
+    }
+
     // For shell transition
     /**
      * Check whether the transition targets was animated by back gesture animation.
@@ -784,7 +826,13 @@
             mAnimationHandler.markStartingSurfaceMatch(startTransaction);
             return;
         }
-        if (!isMonitoringFinishTransition() || targets.isEmpty()) {
+        if (targets.isEmpty()) {
+            return;
+        }
+        final boolean migratePredictToTransition = Flags.migratePredictiveBackTransition();
+        if (migratePredictToTransition && !mAnimationHandler.mComposed) {
+            return;
+        } else if (!isMonitoringFinishTransition()) {
             return;
         }
         if (mAnimationHandler.hasTargetDetached()) {
@@ -808,20 +856,27 @@
                 mTmpCloseApps.add(wc);
             }
         }
-        final boolean matchAnimationTargets = isWaitBackTransition()
+        final boolean matchAnimationTargets;
+        if (migratePredictToTransition) {
+            matchAnimationTargets =
+                    mAnimationHandler.containsBackAnimationTargets(mTmpOpenApps, mTmpCloseApps);
+        } else {
+            matchAnimationTargets = isWaitBackTransition()
                 && (transition.mType == TRANSIT_CLOSE || transition.mType == TRANSIT_TO_BACK)
                 && mAnimationHandler.containsBackAnimationTargets(mTmpOpenApps, mTmpCloseApps);
+        }
         ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
                 "onTransactionReady, opening: %s, closing: %s, animating: %s, match: %b",
                 mTmpOpenApps, mTmpCloseApps, mAnimationHandler, matchAnimationTargets);
-        if (!matchAnimationTargets) {
+        // Don't cancel transition, let transition handler to handle it
+        if (!matchAnimationTargets && !migratePredictToTransition) {
             mNavigationMonitor.onTransitionReadyWhileNavigate(mTmpOpenApps, mTmpCloseApps);
         } else {
             if (mAnimationHandler.mPrepareCloseTransition != null) {
                 Slog.e(TAG, "Gesture animation is applied on another transition?");
             }
             mAnimationHandler.mPrepareCloseTransition = transition;
-            if (!Flags.migratePredictiveBackTransition()) {
+            if (!migratePredictToTransition) {
                 // Because the target will reparent to transition root, so it cannot be controlled
                 // by animation leash. Hide the close target when transition starts.
                 startTransaction.hide(mAnimationHandler.mCloseAdaptor.mTarget.getSurfaceControl());
@@ -839,7 +894,19 @@
     }
 
     boolean isMonitorTransitionTarget(WindowContainer wc) {
-        if ((isWaitBackTransition() && mAnimationHandler.mPrepareCloseTransition != null)
+        if (Flags.migratePredictiveBackTransition()) {
+            if (!mAnimationHandler.mComposed) {
+                return false;
+            }
+            if (mAnimationHandler.mSwitchType == AnimationHandler.TASK_SWITCH
+                    && wc.asActivityRecord() != null
+                    || (mAnimationHandler.mSwitchType == AnimationHandler.ACTIVITY_SWITCH
+                    && wc.asTask() != null)) {
+                return false;
+            }
+            return (mAnimationHandler.isTarget(wc, true /* open */)
+                    || mAnimationHandler.isTarget(wc, false /* open */));
+        } else if ((isWaitBackTransition() && mAnimationHandler.mPrepareCloseTransition != null)
                 || (mAnimationHandler.mOpenAnimAdaptor != null
                 && mAnimationHandler.mOpenAnimAdaptor.mPreparedOpenTransition != null)) {
             return mAnimationHandler.isTarget(wc, wc.isVisibleRequested() /* open */);
@@ -1840,6 +1907,42 @@
         return openActivities;
     }
 
+    boolean restoreBackNavigation() {
+        if (!mAnimationHandler.mComposed) {
+            return false;
+        }
+        ActivityRecord[] penActivities = mAnimationHandler.mOpenActivities;
+        boolean changed = false;
+        if (penActivities != null) {
+            for (int i = penActivities.length - 1; i >= 0; --i) {
+                ActivityRecord resetActivity = penActivities[i];
+                if (resetActivity.mLaunchTaskBehind) {
+                    resetActivity.mTransitionController.collect(resetActivity);
+                    restoreLaunchBehind(resetActivity, true, false);
+                    changed = true;
+                }
+            }
+        }
+        return changed;
+    }
+
+    boolean restoreBackNavigationSetTransitionReady(Transition transition) {
+        if (!mAnimationHandler.mComposed) {
+            return false;
+        }
+        ActivityRecord[] penActivities = mAnimationHandler.mOpenActivities;
+        if (penActivities != null) {
+            for (int i = penActivities.length - 1; i >= 0; --i) {
+                ActivityRecord resetActivity = penActivities[i];
+                if (transition.isInTransition(resetActivity)) {
+                    transition.setReady(resetActivity.getDisplayContent(), true);
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
     private static Transition setLaunchBehind(@NonNull ActivityRecord[] activities) {
         final boolean migrateBackTransition = Flags.migratePredictiveBackTransition();
         final ArrayList<ActivityRecord> affects = new ArrayList<>();
@@ -1887,18 +1990,12 @@
                 activity.makeVisibleIfNeeded(null /* starting */, true /* notifyToClient */);
             }
         }
-        boolean needTransition = false;
-        final DisplayContent dc = affects.get(0).getDisplayContent();
-        for (int i = affects.size() - 1; i >= 0; --i) {
-            final ActivityRecord activity = affects.get(i);
-            needTransition |= tc.isCollecting(activity);
-        }
         if (prepareOpen != null) {
-            if (needTransition) {
+            if (prepareOpen.hasChanges()) {
                 tc.requestStartTransition(prepareOpen,
                         null /*startTask */, null /* remoteTransition */,
                         null /* displayChange */);
-                tc.setReady(dc);
+                prepareOpen.setReady(affects.get(0), true);
                 return prepareOpen;
             } else {
                 prepareOpen.abort();
@@ -1919,9 +2016,12 @@
                 activity);
         if (cancel) {
             final boolean migrateBackTransition = Flags.migratePredictiveBackTransition();
-            if (migrateBackTransition && finishTransition) {
-                activity.commitVisibility(false /* visible */, false /* performLayout */,
-                        true /* fromTransition */);
+            // could be visible if transition is canceled due to top activity is finishing.
+            if (migrateBackTransition) {
+                if (finishTransition && !activity.shouldBeVisible()) {
+                    activity.commitVisibility(false /* visible */, false /* performLayout */,
+                            true /* fromTransition */);
+                }
             } else {
                 // Restore the launch-behind state
                 // TODO b/347168362 Change status directly during collecting for a transition.
@@ -1946,11 +2046,22 @@
         }
     }
 
+    /** If the open transition is playing, wait for transition to clear the animation */
+    private boolean canCancelAnimations() {
+        if (!Flags.migratePredictiveBackTransition()) {
+            return true;
+        }
+        return mAnimationHandler.mOpenAnimAdaptor == null
+                || mAnimationHandler.mOpenAnimAdaptor.mPreparedOpenTransition == null;
+    }
+
     void startAnimation() {
         if (!mBackAnimationInProgress) {
             // gesture is already finished, do not start animation
             if (mPendingAnimation != null) {
-                clearBackAnimations(true /* cancel */);
+                if (canCancelAnimations()) {
+                    clearBackAnimations(true /* cancel */);
+                }
                 mPendingAnimation = null;
             }
             return;
@@ -2015,7 +2126,7 @@
         return isSnapshotCompatible(snapshot, visibleOpenActivities) ? snapshot : null;
     }
 
-    static boolean isSnapshotCompatible(@NonNull TaskSnapshot snapshot,
+    static boolean isSnapshotCompatible(@Nullable TaskSnapshot snapshot,
             @NonNull ActivityRecord[] visibleOpenActivities) {
         if (snapshot == null) {
             return false;
@@ -2026,6 +2137,12 @@
             if (!ar.isSnapshotOrientationCompatible(snapshot)) {
                 return false;
             }
+            final int appNightMode = ar.getConfiguration().uiMode
+                    & Configuration.UI_MODE_NIGHT_MASK;
+            final int snapshotNightMode = snapshot.getUiMode() & Configuration.UI_MODE_NIGHT_MASK;
+            if (appNightMode != snapshotNightMode) {
+                return false;
+            }
             oneComponentMatch |= ar.isSnapshotComponentCompatible(snapshot);
         }
         return oneComponentMatch;
diff --git a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
index 2755a80..9b142f28 100644
--- a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
+++ b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
@@ -109,10 +109,10 @@
     }
 
     @Override
-    public boolean onCameraOpened(@NonNull ActivityRecord cameraActivity,
+    public void onCameraOpened(@NonNull ActivityRecord cameraActivity,
             @NonNull String cameraId) {
         if (!isTreatmentEnabledForActivity(cameraActivity)) {
-            return false;
+            return;
         }
         final int existingCameraCompatMode = cameraActivity.mAppCompatController
                 .getAppCompatCameraOverrides()
@@ -124,11 +124,9 @@
             cameraActivity.mAppCompatController.getAppCompatCameraOverrides()
                     .setFreeformCameraCompatMode(newCameraCompatMode);
             forceUpdateActivityAndTask(cameraActivity);
-            return true;
         } else {
             mIsCameraCompatTreatmentPending = false;
         }
-        return false;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/CameraStateMonitor.java b/services/core/java/com/android/server/wm/CameraStateMonitor.java
index 068fc00..63c90ff 100644
--- a/services/core/java/com/android/server/wm/CameraStateMonitor.java
+++ b/services/core/java/com/android/server/wm/CameraStateMonitor.java
@@ -73,16 +73,6 @@
 
     private final ArrayList<CameraCompatStateListener> mCameraStateListeners = new ArrayList<>();
 
-    /**
-     * {@link CameraCompatStateListener} which returned {@code true} on the last {@link
-     * CameraCompatStateListener#onCameraOpened(ActivityRecord, String)}, if any.
-     *
-     * <p>This allows the {@link CameraStateMonitor} to notify a particular listener when camera
-     * closes, so they can revert any changes.
-     */
-    @Nullable
-    private CameraCompatStateListener mCurrentListenerForCameraActivity;
-
     private final CameraManager.AvailabilityCallback mAvailabilityCallback =
             new  CameraManager.AvailabilityCallback() {
                 @Override
@@ -167,12 +157,7 @@
             @NonNull String cameraId) {
         for (int i = 0; i < mCameraStateListeners.size(); i++) {
             CameraCompatStateListener listener = mCameraStateListeners.get(i);
-            boolean activeCameraTreatment = listener.onCameraOpened(
-                    cameraActivity, cameraId);
-            if (activeCameraTreatment) {
-                mCurrentListenerForCameraActivity = listener;
-                break;
-            }
+            listener.onCameraOpened(cameraActivity, cameraId);
         }
     }
 
@@ -226,19 +211,30 @@
                 return;
             }
 
-            if (mCurrentListenerForCameraActivity != null) {
-                boolean closeSuccessful =
-                        mCurrentListenerForCameraActivity.onCameraClosed(cameraId);
-                if (closeSuccessful) {
-                    mCameraIdPackageBiMapping.removeCameraId(cameraId);
-                    mCurrentListenerForCameraActivity = null;
-                } else {
-                    rescheduleRemoveCameraActivity(cameraId);
-                }
+            final boolean closeSuccessfulForAllListeners = notifyListenersCameraClosed(cameraId);
+            if (closeSuccessfulForAllListeners) {
+                // Finish cleaning up.
+                mCameraIdPackageBiMapping.removeCameraId(cameraId);
+            } else {
+                // Not ready to process closure yet - the camera activity might be refreshing.
+                // Try again later.
+                rescheduleRemoveCameraActivity(cameraId);
             }
         }
     }
 
+    /**
+     * @return {@code false} if any listeners have reported issues processing the close.
+     */
+    private boolean notifyListenersCameraClosed(@NonNull String cameraId) {
+        boolean closeSuccessfulForAllListeners = true;
+        for (int i = 0; i < mCameraStateListeners.size(); i++) {
+            closeSuccessfulForAllListeners &= mCameraStateListeners.get(i).onCameraClosed(cameraId);
+        }
+
+        return closeSuccessfulForAllListeners;
+    }
+
     // TODO(b/335165310): verify that this works in multi instance and permission dialogs.
     /**
      * Finds a visible activity with the given package name.
@@ -286,11 +282,9 @@
     interface CameraCompatStateListener {
         /**
          * Notifies the compat listener that an activity has opened camera.
-         *
-         * @return true if the treatment has been applied.
          */
         // TODO(b/336474959): try to decouple `cameraId` from the listeners.
-        boolean onCameraOpened(@NonNull ActivityRecord cameraActivity, @NonNull String cameraId);
+        void onCameraOpened(@NonNull ActivityRecord cameraActivity, @NonNull String cameraId);
         /**
          * Notifies the compat listener that camera is closed.
          *
diff --git a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
index f566df5..8f1828d 100644
--- a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
+++ b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
@@ -109,6 +109,13 @@
         if (!DesktopModeFlagsUtil.DYNAMIC_INITIAL_BOUNDS.isEnabled(activity.mWmService.mContext)) {
             return centerInScreen(idealSize, screenBounds);
         }
+        if (activity.mAppCompatController.getAppCompatAspectRatioOverrides()
+                .hasFullscreenOverride()) {
+            // If the activity has a fullscreen override applied, it should be treated as
+            // resizeable and match the device orientation. Thus the ideal size can be
+            // applied.
+            return centerInScreen(idealSize, screenBounds);
+        }
         // TODO(b/353457301): Replace with app compat aspect ratio method when refactoring complete.
         float appAspectRatio = calculateAspectRatio(task, activity);
         final float tdaWidth = stableBounds.width();
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 9c8c759..fcc6b11 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1804,9 +1804,7 @@
             return;
         }
         final int displayRotation = getRotation();
-        final int rotation = ar.isVisible()
-                ? ar.getWindowConfiguration().getDisplayRotation()
-                : mDisplayRotation.rotationForOrientation(orientation, displayRotation);
+        final int rotation = mDisplayRotation.rotationForOrientation(orientation, displayRotation);
         if (rotation == displayRotation) {
             return;
         }
@@ -6710,6 +6708,11 @@
         final boolean rotationChanged = super.setIgnoreOrientationRequest(ignoreOrientationRequest);
         mWmService.mDisplayWindowSettings.setIgnoreOrientationRequest(
                 this, mSetIgnoreOrientationRequest);
+        if (ignoreOrientationRequest && mWmService.mFlags.mRespectNonTopVisibleFixedOrientation) {
+            forAllActivities(r -> {
+                r.finishFixedRotationTransform();
+            });
+        }
         return rotationChanged;
     }
 
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 8272e16..a5da5e7 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -1239,7 +1239,6 @@
      * @param lastRotation The most recently used rotation.
      * @return The surface rotation to use.
      */
-    @VisibleForTesting
     @Surface.Rotation
     int rotationForOrientation(@ScreenOrientation int orientation,
             @Surface.Rotation int lastRotation) {
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index e50a089..762180b 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -298,7 +298,7 @@
     }
 
     @Override
-    public boolean onCameraOpened(@NonNull ActivityRecord cameraActivity,
+    public void onCameraOpened(@NonNull ActivityRecord cameraActivity,
             @NonNull String cameraId) {
         mCameraTask = cameraActivity.getTask();
         // Checking whether an activity in fullscreen rather than the task as this camera
@@ -306,7 +306,7 @@
         if (cameraActivity.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
             recomputeConfigurationForCameraCompatIfNeeded(cameraActivity);
             mDisplayContent.updateOrientation();
-            return true;
+            return;
         }
         // Checking that the whole app is in multi-window mode as we shouldn't show toast
         // for the activity embedding case.
@@ -320,7 +320,6 @@
                         (String) packageManager.getApplicationLabel(
                                 packageManager.getApplicationInfo(cameraActivity.packageName,
                                         /* flags */ 0)));
-                return true;
             } catch (PackageManager.NameNotFoundException e) {
                 ProtoLog.e(WM_DEBUG_ORIENTATION,
                         "DisplayRotationCompatPolicy: Multi-window toast not shown as "
@@ -328,7 +327,6 @@
                         cameraActivity.packageName);
             }
         }
-        return false;
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
index 7a95c2d..2f0ee17 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
@@ -129,21 +129,33 @@
     @WindowConfiguration.WindowingMode
     private int getWindowingModeLocked(@NonNull SettingsProvider.SettingsEntry settings,
             @NonNull DisplayContent dc) {
-        int windowingMode = settings.mWindowingMode;
+        final int windowingModeFromDisplaySettings = settings.mWindowingMode;
         // This display used to be in freeform, but we don't support freeform anymore, so fall
         // back to fullscreen.
-        if (windowingMode == WindowConfiguration.WINDOWING_MODE_FREEFORM
+        if (windowingModeFromDisplaySettings == WindowConfiguration.WINDOWING_MODE_FREEFORM
                 && !mService.mAtmService.mSupportsFreeformWindowManagement) {
             return WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
         }
-        // No record is present so use default windowing mode policy.
-        if (windowingMode == WindowConfiguration.WINDOWING_MODE_UNDEFINED) {
-            windowingMode = mService.mAtmService.mSupportsFreeformWindowManagement
-                    && (mService.mIsPc || dc.forceDesktopMode())
-                    ? WindowConfiguration.WINDOWING_MODE_FREEFORM
-                    : WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+        if (windowingModeFromDisplaySettings != WindowConfiguration.WINDOWING_MODE_UNDEFINED) {
+            return windowingModeFromDisplaySettings;
         }
-        return windowingMode;
+        // No record is present so use default windowing mode policy.
+        final boolean forceFreeForm = mService.mAtmService.mSupportsFreeformWindowManagement
+                && (mService.mIsPc || dc.forceDesktopMode());
+        if (forceFreeForm) {
+            return WindowConfiguration.WINDOWING_MODE_FREEFORM;
+        }
+        final int currentWindowingMode = dc.getDefaultTaskDisplayArea().getWindowingMode();
+        if (currentWindowingMode == WindowConfiguration.WINDOWING_MODE_UNDEFINED) {
+            // No record preset in settings + no mode set via the display area policy.
+            // Move to fullscreen as a fallback.
+            return WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+        }
+        if (currentWindowingMode == WindowConfiguration.WINDOWING_MODE_FREEFORM) {
+            // Freeform was enabled before but disabled now, the TDA should now move to fullscreen.
+            return WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+        }
+        return currentWindowingMode;
     }
 
     @WindowConfiguration.WindowingMode
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 7a0fd3e..5d8a96c 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -39,6 +39,7 @@
 import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
 import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER;
 
+import static com.android.window.flags.Flags.reduceKeyguardTransitions;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -50,6 +51,7 @@
 import android.annotation.Nullable;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.os.Trace;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -77,6 +79,8 @@
 
     private static final int DEFER_WAKE_TRANSITION_TIMEOUT_MS = 5000;
 
+    private static final int GOING_AWAY_TIMEOUT_MS = 10500;
+
     private final ActivityTaskSupervisor mTaskSupervisor;
     private WindowManagerService mWindowManager;
 
@@ -232,6 +236,7 @@
                 dc.mWallpaperController.adjustWallpaperWindows();
                 dc.executeAppTransition();
             }
+            scheduleGoingAwayTimeout(displayId);
         }
 
         // Update the sleep token first such that ensureActivitiesVisible has correct sleep token
@@ -286,6 +291,8 @@
             mRootWindowContainer.ensureActivitiesVisible();
             mRootWindowContainer.addStartingWindowsForVisibleActivities();
             mWindowManager.executeAppTransition();
+
+            scheduleGoingAwayTimeout(displayId);
         } finally {
             mService.continueWindowLayout();
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
@@ -417,31 +424,42 @@
 
         final TransitionController tc = mRootWindowContainer.mTransitionController;
         final KeyguardDisplayState state = getDisplayState(displayId);
+        final DisplayContent dc = mRootWindowContainer.getDisplayContent(displayId);
 
-        final boolean occluded = state.mOccluded;
-        final boolean performTransition = isKeyguardLocked(displayId);
-        final boolean executeTransition = performTransition && !tc.isCollecting();
+        final boolean locked = isKeyguardLocked(displayId);
+        final boolean executeTransition = !tc.isShellTransitionsEnabled()
+                || (locked && !tc.isCollecting() && !reduceKeyguardTransitions());
 
-        mWindowManager.mPolicy.onKeyguardOccludedChangedLw(occluded);
+        final int transitType, transitFlags, notFlags;
+        if (state.mOccluded) {
+            transitType = TRANSIT_KEYGUARD_OCCLUDE;
+            transitFlags = TRANSIT_FLAG_KEYGUARD_OCCLUDING;
+            notFlags = TRANSIT_FLAG_KEYGUARD_UNOCCLUDING;
+        } else {
+            transitType = TRANSIT_KEYGUARD_UNOCCLUDE;
+            transitFlags = TRANSIT_FLAG_KEYGUARD_UNOCCLUDING;
+            notFlags = TRANSIT_FLAG_KEYGUARD_OCCLUDING;
+        }
+
+        mWindowManager.mPolicy.onKeyguardOccludedChangedLw(state.mOccluded);
         mService.deferWindowLayout();
         try {
-            if (isKeyguardLocked(displayId)) {
-                final int type = occluded ? TRANSIT_KEYGUARD_OCCLUDE : TRANSIT_KEYGUARD_UNOCCLUDE;
-                final int flag = occluded ? TRANSIT_FLAG_KEYGUARD_OCCLUDING
-                        : TRANSIT_FLAG_KEYGUARD_UNOCCLUDING;
+            if (locked) {
                 if (tc.isShellTransitionsEnabled()) {
-                    final Task trigger = (occluded && topActivity != null)
+                    final Task trigger = (state.mOccluded && topActivity != null)
                             ? topActivity.getRootTask() : null;
-                    Transition transition = tc.requestTransitionIfNeeded(type, flag, trigger,
-                            mRootWindowContainer.getDefaultDisplay());
+                    tc.requestTransitionIfNeeded(transitType, transitFlags, trigger, dc);
+                    final Transition transition = tc.getCollectingTransition();
+                    if ((transition.getFlags() & notFlags) != 0 && reduceKeyguardTransitions()) {
+                        transition.removeFlag(notFlags);
+                    } else {
+                        transition.addFlag(transitFlags);
+                    }
                     if (trigger != null) {
-                        if (transition == null) {
-                            transition = tc.getCollectingTransition();
-                        }
                         transition.collect(trigger);
                     }
                 } else {
-                    mRootWindowContainer.getDefaultDisplay().prepareAppTransition(type, flag);
+                    dc.prepareAppTransition(transitType, transitFlags);
                 }
             } else {
                 if (tc.inTransition()) {
@@ -451,8 +469,8 @@
                 }
             }
             updateKeyguardSleepToken(displayId);
-            if (performTransition && executeTransition) {
-                mWindowManager.executeAppTransition();
+            if (executeTransition) {
+                dc.executeAppTransition();
             }
         } finally {
             mService.continueWindowLayout();
@@ -590,6 +608,35 @@
         }
     }
 
+    /**
+     * Called when the default display's mKeyguardGoingAway has been left as {@code true} for too
+     * long. Send an explicit message to the KeyguardService asking it to wrap up.
+     */
+    private final Runnable mGoingAwayTimeout = () -> {
+        synchronized (mWindowManager.mGlobalLock) {
+            KeyguardDisplayState state = getDisplayState(DEFAULT_DISPLAY);
+            if (!state.mKeyguardGoingAway) {
+                return;
+            }
+            state.mKeyguardGoingAway = false;
+            state.writeEventLog("goingAwayTimeout");
+            mWindowManager.mPolicy.startKeyguardExitAnimation(
+                    SystemClock.uptimeMillis() - GOING_AWAY_TIMEOUT_MS);
+        }
+    };
+
+    private void scheduleGoingAwayTimeout(int displayId) {
+        if (displayId != DEFAULT_DISPLAY) {
+            return;
+        }
+        if (getDisplayState(displayId).mKeyguardGoingAway) {
+            if (!mWindowManager.mH.hasCallbacks(mGoingAwayTimeout)) {
+                mWindowManager.mH.postDelayed(mGoingAwayTimeout, GOING_AWAY_TIMEOUT_MS);
+            }
+        } else {
+            mWindowManager.mH.removeCallbacks(mGoingAwayTimeout);
+        }
+    }
 
     /** Represents Keyguard state per individual display. */
     private static class KeyguardDisplayState {
@@ -709,6 +756,7 @@
             if (!lastKeyguardGoingAway && mKeyguardGoingAway) {
                 writeEventLog("dismissIfInsecure");
                 controller.handleDismissInsecureKeyguard(display);
+                controller.scheduleGoingAwayTimeout(mDisplayId);
                 hasChange = true;
             } else if (lastOccluded != mOccluded) {
                 controller.handleOccludedChanged(mDisplayId, mTopOccludesActivity);
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index 3fc5eaf..252590e 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -38,9 +38,6 @@
 
 import com.android.server.UiThread;
 
-import java.util.function.BooleanSupplier;
-import java.util.function.DoubleSupplier;
-import java.util.function.IntSupplier;
 import java.util.function.Supplier;
 
 /**
@@ -54,12 +51,6 @@
 
     private final Supplier<SurfaceControl.Builder> mSurfaceControlFactory;
     private final Supplier<SurfaceControl.Transaction> mTransactionFactory;
-    private final BooleanSupplier mAreCornersRounded;
-    private final Supplier<Color> mColorSupplier;
-    // Parameters for "blurred wallpaper" letterbox background.
-    private final BooleanSupplier mHasWallpaperBackgroundSupplier;
-    private final IntSupplier mBlurRadiusSupplier;
-    private final DoubleSupplier mDarkScrimAlphaSupplier;
     private final Supplier<SurfaceControl> mParentSurfaceSupplier;
 
     private final Rect mOuter = new Rect();
@@ -77,6 +68,8 @@
     private final LetterboxSurface[] mSurfaces = { mLeft, mTop, mRight, mBottom };
     @NonNull
     private final AppCompatReachabilityPolicy mAppCompatReachabilityPolicy;
+    @NonNull
+    private final AppCompatLetterboxOverrides mAppCompatLetterboxOverrides;
 
     /**
      * Constructs a Letterbox.
@@ -85,24 +78,14 @@
      */
     public Letterbox(Supplier<SurfaceControl.Builder> surfaceControlFactory,
             Supplier<SurfaceControl.Transaction> transactionFactory,
-            BooleanSupplier areCornersRounded,
-            Supplier<Color> colorSupplier,
-            BooleanSupplier hasWallpaperBackgroundSupplier,
-            IntSupplier blurRadiusSupplier,
-            DoubleSupplier darkScrimAlphaSupplier,
             @NonNull AppCompatReachabilityPolicy appCompatReachabilityPolicy,
+            @NonNull AppCompatLetterboxOverrides appCompatLetterboxOverrides,
             Supplier<SurfaceControl> parentSurface) {
         mSurfaceControlFactory = surfaceControlFactory;
         mTransactionFactory = transactionFactory;
-        mAreCornersRounded = areCornersRounded;
-        mColorSupplier = colorSupplier;
-        mHasWallpaperBackgroundSupplier = hasWallpaperBackgroundSupplier;
-        mBlurRadiusSupplier = blurRadiusSupplier;
-        mDarkScrimAlphaSupplier = darkScrimAlphaSupplier;
         mAppCompatReachabilityPolicy = appCompatReachabilityPolicy;
+        mAppCompatLetterboxOverrides = appCompatLetterboxOverrides;
         mParentSurfaceSupplier = parentSurface;
-        // TODO Remove after Letterbox refactoring.
-        mAppCompatReachabilityPolicy.setLetterboxInnerBoundsSupplier(this::getInnerFrame);
     }
 
     /**
@@ -252,7 +235,8 @@
      * Returns {@code true} when using {@link #mFullWindowSurface} instead of {@link mSurfaces}.
      */
     private boolean useFullWindowSurface() {
-        return mAreCornersRounded.getAsBoolean() || mHasWallpaperBackgroundSupplier.getAsBoolean();
+        return mAppCompatLetterboxOverrides.shouldLetterboxHaveRoundedCorners()
+                || mAppCompatLetterboxOverrides.hasWallpaperBackgroundForLetterbox();
     }
 
     private final class TapEventReceiver extends InputEventReceiver {
@@ -431,7 +415,7 @@
                     createSurface(t);
                 }
 
-                mColor = mColorSupplier.get();
+                mColor = mAppCompatLetterboxOverrides.getLetterboxBackgroundColor();
                 mParentSurface = mParentSurfaceSupplier.get();
                 t.setColor(mSurface, getRgbColorArray());
                 t.setPosition(mSurface, mSurfaceFrameRelative.left, mSurfaceFrameRelative.top);
@@ -439,7 +423,8 @@
                         mSurfaceFrameRelative.height());
                 t.reparent(mSurface, mParentSurface);
 
-                mHasWallpaperBackground = mHasWallpaperBackgroundSupplier.getAsBoolean();
+                mHasWallpaperBackground = mAppCompatLetterboxOverrides
+                        .hasWallpaperBackgroundForLetterbox();
                 updateAlphaAndBlur(t);
 
                 t.show(mSurface);
@@ -460,17 +445,19 @@
                 t.setBackgroundBlurRadius(mSurface, 0);
                 return;
             }
-            final float alpha = (float) mDarkScrimAlphaSupplier.getAsDouble();
+            final float alpha = mAppCompatLetterboxOverrides.getLetterboxWallpaperDarkScrimAlpha();
             t.setAlpha(mSurface, alpha);
 
             // Translucent dark scrim can be shown without blur.
-            if (mBlurRadiusSupplier.getAsInt() <= 0) {
+            final int blurRadiusPx = mAppCompatLetterboxOverrides
+                    .getLetterboxWallpaperBlurRadiusPx();
+            if (blurRadiusPx <= 0) {
                 // Removing pre-exesting blur
                 t.setBackgroundBlurRadius(mSurface, 0);
                 return;
             }
 
-            t.setBackgroundBlurRadius(mSurface, mBlurRadiusSupplier.getAsInt());
+            t.setBackgroundBlurRadius(mSurface, blurRadiusPx);
         }
 
         private float[] getRgbColorArray() {
@@ -487,8 +474,9 @@
                     // and mParentSurface may never be updated in applySurfaceChanges but this
                     // doesn't mean that update is needed.
                     || !mSurfaceFrameRelative.isEmpty()
-                    && (mHasWallpaperBackgroundSupplier.getAsBoolean() != mHasWallpaperBackground
-                    || !mColorSupplier.get().equals(mColor)
+                    && (mAppCompatLetterboxOverrides.hasWallpaperBackgroundForLetterbox()
+                        != mHasWallpaperBackground
+                    || !mAppCompatLetterboxOverrides.getLetterboxBackgroundColor().equals(mColor)
                     || mParentSurfaceSupplier.get() != mParentSurface);
         }
     }
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 38df1b0..0e33734 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -16,36 +16,19 @@
 
 package com.android.server.wm;
 
-import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
-import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-
 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.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND;
-import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
-import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR;
 import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_WALLPAPER;
 import static com.android.server.wm.AppCompatConfiguration.letterboxBackgroundTypeToString;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.ActivityManager.TaskDescription;
 import android.graphics.Color;
-import android.graphics.Point;
 import android.graphics.Rect;
-import android.util.Slog;
-import android.view.InsetsSource;
-import android.view.InsetsState;
-import android.view.RoundedCorner;
-import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
-import android.view.WindowInsets;
-import android.view.WindowManager;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.LetterboxDetails;
-import com.android.server.wm.AppCompatConfiguration.LetterboxBackgroundType;
 
 import java.io.PrintWriter;
 
@@ -56,8 +39,6 @@
 
     private static final String TAG = TAG_WITH_CLASS_NAME ? "LetterboxUiController" : TAG_ATM;
 
-    private final Point mTmpPoint = new Point();
-
     private final AppCompatConfiguration mAppCompatConfiguration;
 
     private final ActivityRecord mActivityRecord;
@@ -66,18 +47,9 @@
     @NonNull
     private final AppCompatReachabilityOverrides mAppCompatReachabilityOverrides;
     @NonNull
-    private final AppCompatReachabilityPolicy mAppCompatReachabilityPolicy;
+    private final AppCompatLetterboxPolicy mAppCompatLetterboxPolicy;
     @NonNull
-    private final TransparentPolicy mTransparentPolicy;
-    @NonNull
-    private final AppCompatOrientationOverrides mAppCompatOrientationOverrides;
-
-    private boolean mShowWallpaperForLetterboxBackground;
-
-    @Nullable
-    private Letterbox mLetterbox;
-
-    private boolean mLastShouldShowLetterboxUi;
+    private final AppCompatLetterboxOverrides mAppCompatLetterboxOverrides;
 
     LetterboxUiController(WindowManagerService wmService, ActivityRecord activityRecord) {
         mAppCompatConfiguration = wmService.mAppCompatConfiguration;
@@ -88,63 +60,34 @@
         // TODO(b/356385137): Remove these we added to make dependencies temporarily explicit.
         mAppCompatReachabilityOverrides = mActivityRecord.mAppCompatController
                 .getAppCompatReachabilityOverrides();
-        mAppCompatReachabilityPolicy = mActivityRecord.mAppCompatController
-                .getAppCompatReachabilityPolicy();
-        mTransparentPolicy = mActivityRecord.mAppCompatController.getTransparentPolicy();
-        mAppCompatOrientationOverrides = mActivityRecord.mAppCompatController
-                .getAppCompatOrientationOverrides();
+        mAppCompatLetterboxPolicy = mActivityRecord.mAppCompatController
+                .getAppCompatLetterboxPolicy();
+        mAppCompatLetterboxOverrides = mActivityRecord.mAppCompatController
+                .getAppCompatLetterboxOverrides();
 
     }
 
     /** Cleans up {@link Letterbox} if it exists.*/
     void destroy() {
-        if (mLetterbox != null) {
-            mLetterbox.destroy();
-            mLetterbox = null;
-            // TODO Remove after Letterbox refactoring.
-            mAppCompatReachabilityPolicy.setLetterboxInnerBoundsSupplier(null);
-        }
+        mAppCompatLetterboxPolicy.destroy();
     }
 
     void onMovedToDisplay(int displayId) {
-        if (mLetterbox != null) {
-            mLetterbox.onMovedToDisplay(displayId);
-        }
+        mAppCompatLetterboxPolicy.onMovedToDisplay(displayId);
     }
 
     boolean hasWallpaperBackgroundForLetterbox() {
-        return mShowWallpaperForLetterboxBackground;
+        return mAppCompatLetterboxOverrides.hasWallpaperBackgroundForLetterbox();
     }
 
     /** Gets the letterbox insets. The insets will be empty if there is no letterbox. */
     Rect getLetterboxInsets() {
-        if (mLetterbox != null) {
-            return mLetterbox.getInsets();
-        } else {
-            return new Rect();
-        }
+        return mAppCompatLetterboxPolicy.getLetterboxInsets();
     }
 
     /** Gets the inner bounds of letterbox. The bounds will be empty if there is no letterbox. */
     void getLetterboxInnerBounds(Rect outBounds) {
-        if (mLetterbox != null) {
-            outBounds.set(mLetterbox.getInnerFrame());
-            final WindowState w = mActivityRecord.findMainWindow();
-            if (w != null) {
-                adjustBoundsForTaskbar(w, outBounds);
-            }
-        } else {
-            outBounds.setEmpty();
-        }
-    }
-
-    /** Gets the outer bounds of letterbox. The bounds will be empty if there is no letterbox. */
-    private void getLetterboxOuterBounds(Rect outBounds) {
-        if (mLetterbox != null) {
-            outBounds.set(mLetterbox.getOuterFrame());
-        } else {
-            outBounds.setEmpty();
-        }
+        mAppCompatLetterboxPolicy.getLetterboxInnerBounds(outBounds);
     }
 
     /**
@@ -152,234 +95,39 @@
      *     when the current activity is displayed.
      */
     boolean isFullyTransparentBarAllowed(Rect rect) {
-        return mLetterbox == null || mLetterbox.notIntersectsOrFullyContains(rect);
+        return mAppCompatLetterboxPolicy.isFullyTransparentBarAllowed(rect);
     }
 
     void updateLetterboxSurfaceIfNeeded(WindowState winHint) {
-        updateLetterboxSurfaceIfNeeded(winHint, mActivityRecord.getSyncTransaction(),
-                mActivityRecord.getPendingTransaction());
+        mAppCompatLetterboxPolicy.updateLetterboxSurfaceIfNeeded(winHint);
     }
 
     void updateLetterboxSurfaceIfNeeded(WindowState winHint, @NonNull Transaction t,
             @NonNull Transaction inputT) {
-        if (shouldNotLayoutLetterbox(winHint)) {
-            return;
-        }
-        layoutLetterboxIfNeeded(winHint);
-        if (mLetterbox != null && mLetterbox.needsApplySurfaceChanges()) {
-            mLetterbox.applySurfaceChanges(t, inputT);
-        }
+        mAppCompatLetterboxPolicy.updateLetterboxSurfaceIfNeeded(winHint, t, inputT);
     }
 
     void layoutLetterboxIfNeeded(WindowState w) {
-        if (shouldNotLayoutLetterbox(w)) {
-            return;
-        }
-        updateRoundedCornersIfNeeded(w);
-        updateWallpaperForLetterbox(w);
-        if (shouldShowLetterboxUi(w)) {
-            if (mLetterbox == null) {
-                mLetterbox = new Letterbox(() -> mActivityRecord.makeChildSurface(null),
-                        mActivityRecord.mWmService.mTransactionFactory,
-                        this::shouldLetterboxHaveRoundedCorners,
-                        this::getLetterboxBackgroundColor,
-                        this::hasWallpaperBackgroundForLetterbox,
-                        this::getLetterboxWallpaperBlurRadiusPx,
-                        this::getLetterboxWallpaperDarkScrimAlpha,
-                        mAppCompatReachabilityPolicy,
-                        this::getLetterboxParentSurface);
-                mLetterbox.attachInput(w);
-            }
-
-            if (mActivityRecord.isInLetterboxAnimation()) {
-                // In this case we attach the letterbox to the task instead of the activity.
-                mActivityRecord.getTask().getPosition(mTmpPoint);
-            } else {
-                mActivityRecord.getPosition(mTmpPoint);
-            }
-
-            // Get the bounds of the "space-to-fill". The transformed bounds have the highest
-            // priority because the activity is launched in a rotated environment. In multi-window
-            // mode, the taskFragment-level represents this for both split-screen
-            // and activity-embedding. In fullscreen-mode, the task container does
-            // (since the orientation letterbox is also applied to the task).
-            final Rect transformedBounds = mActivityRecord.getFixedRotationTransformDisplayBounds();
-            final Rect spaceToFill = transformedBounds != null
-                    ? transformedBounds
-                    : mActivityRecord.inMultiWindowMode()
-                            ? mActivityRecord.getTaskFragment().getBounds()
-                            : mActivityRecord.getRootTask().getParent().getBounds();
-            // In case of translucent activities an option is to use the WindowState#getFrame() of
-            // the first opaque activity beneath. In some cases (e.g. an opaque activity is using
-            // non MATCH_PARENT layouts or a Dialog theme) this might not provide the correct
-            // information and in particular it might provide a value for a smaller area making
-            // the letterbox overlap with the translucent activity's frame.
-            // If we use WindowState#getFrame() for the translucent activity's letterbox inner
-            // frame, the letterbox will then be overlapped with the translucent activity's frame.
-            // Because the surface layer of letterbox is lower than an activity window, this
-            // won't crop the content, but it may affect other features that rely on values stored
-            // in mLetterbox, e.g. transitions, a status bar scrim and recents preview in Launcher
-            // For this reason we use ActivityRecord#getBounds() that the translucent activity
-            // inherits from the first opaque activity beneath and also takes care of the scaling
-            // in case of activities in size compat mode.
-            final Rect innerFrame =
-                    mTransparentPolicy.isRunning() ? mActivityRecord.getBounds() : w.getFrame();
-            mLetterbox.layout(spaceToFill, innerFrame, mTmpPoint);
-            if (mAppCompatReachabilityOverrides.isDoubleTapEvent()) {
-                // We need to notify Shell that letterbox position has changed.
-                mActivityRecord.getTask().dispatchTaskInfoChangedIfNeeded(true /* force */);
-            }
-        } else if (mLetterbox != null) {
-            mLetterbox.hide();
-        }
-    }
-
-    SurfaceControl getLetterboxParentSurface() {
-        if (mActivityRecord.isInLetterboxAnimation()) {
-            return mActivityRecord.getTask().getSurfaceControl();
-        }
-        return mActivityRecord.getSurfaceControl();
-    }
-
-    private static boolean shouldNotLayoutLetterbox(WindowState w) {
-        if (w == null) {
-            return true;
-        }
-        final int type = w.mAttrs.type;
-        // Allow letterbox to be displayed early for base application or application starting
-        // windows even if it is not on the top z order to prevent flickering when the
-        // letterboxed window is brought to the top
-        return (type != TYPE_BASE_APPLICATION && type != TYPE_APPLICATION_STARTING)
-                || w.mAnimatingExit;
-    }
-
-    private boolean shouldLetterboxHaveRoundedCorners() {
-        // TODO(b/214030873): remove once background is drawn for transparent activities
-        // Letterbox shouldn't have rounded corners if the activity is transparent
-        return mAppCompatConfiguration.isLetterboxActivityCornersRounded()
-                && mActivityRecord.fillsParent();
+        mAppCompatLetterboxPolicy.start(w);
     }
 
     boolean isLetterboxEducationEnabled() {
-        return mAppCompatConfiguration.getIsEducationEnabled();
+        return mAppCompatLetterboxOverrides.isLetterboxEducationEnabled();
     }
 
     @VisibleForTesting
     boolean shouldShowLetterboxUi(WindowState mainWindow) {
-        if (mAppCompatOrientationOverrides.getIsRelaunchingAfterRequestedOrientationChanged()) {
-            return mLastShouldShowLetterboxUi;
-        }
-
-        final boolean shouldShowLetterboxUi =
-                (mActivityRecord.isInLetterboxAnimation() || mActivityRecord.isVisible()
-                        || mActivityRecord.isVisibleRequested())
-                && mainWindow.areAppWindowBoundsLetterboxed()
-                // Check for FLAG_SHOW_WALLPAPER explicitly instead of using
-                // WindowContainer#showWallpaper because the later will return true when this
-                // activity is using blurred wallpaper for letterbox background.
-                && (mainWindow.getAttrs().flags & FLAG_SHOW_WALLPAPER) == 0;
-
-        mLastShouldShowLetterboxUi = shouldShowLetterboxUi;
-
-        return shouldShowLetterboxUi;
+        return mAppCompatLetterboxPolicy.shouldShowLetterboxUi(mainWindow);
     }
 
     Color getLetterboxBackgroundColor() {
-        final WindowState w = mActivityRecord.findMainWindow();
-        if (w == null || w.isLetterboxedForDisplayCutout()) {
-            return Color.valueOf(Color.BLACK);
-        }
-        @LetterboxBackgroundType int letterboxBackgroundType =
-                mAppCompatConfiguration.getLetterboxBackgroundType();
-        TaskDescription taskDescription = mActivityRecord.taskDescription;
-        switch (letterboxBackgroundType) {
-            case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING:
-                if (taskDescription != null && taskDescription.getBackgroundColorFloating() != 0) {
-                    return Color.valueOf(taskDescription.getBackgroundColorFloating());
-                }
-                break;
-            case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND:
-                if (taskDescription != null && taskDescription.getBackgroundColor() != 0) {
-                    return Color.valueOf(taskDescription.getBackgroundColor());
-                }
-                break;
-            case LETTERBOX_BACKGROUND_WALLPAPER:
-                if (hasWallpaperBackgroundForLetterbox()) {
-                    // Color is used for translucent scrim that dims wallpaper.
-                    return mAppCompatConfiguration.getLetterboxBackgroundColor();
-                }
-                Slog.w(TAG, "Wallpaper option is selected for letterbox background but "
-                        + "blur is not supported by a device or not supported in the current "
-                        + "window configuration or both alpha scrim and blur radius aren't "
-                        + "provided so using solid color background");
-                break;
-            case LETTERBOX_BACKGROUND_SOLID_COLOR:
-                return mAppCompatConfiguration.getLetterboxBackgroundColor();
-            default:
-                throw new AssertionError(
-                    "Unexpected letterbox background type: " + letterboxBackgroundType);
-        }
-        // If picked option configured incorrectly or not supported then default to a solid color
-        // background.
-        return mAppCompatConfiguration.getLetterboxBackgroundColor();
-    }
-
-    private void updateRoundedCornersIfNeeded(final WindowState mainWindow) {
-        final SurfaceControl windowSurface = mainWindow.getSurfaceControl();
-        if (windowSurface == null || !windowSurface.isValid()) {
-            return;
-        }
-
-        // cropBounds must be non-null for the cornerRadius to be ever applied.
-        mActivityRecord.getSyncTransaction()
-                .setCrop(windowSurface, getCropBoundsIfNeeded(mainWindow))
-                .setCornerRadius(windowSurface, getRoundedCornersRadius(mainWindow));
+        return mAppCompatLetterboxOverrides.getLetterboxBackgroundColor();
     }
 
     @VisibleForTesting
     @Nullable
     Rect getCropBoundsIfNeeded(final WindowState mainWindow) {
-        if (!requiresRoundedCorners(mainWindow) || mActivityRecord.isInLetterboxAnimation()) {
-            // We don't want corner radius on the window.
-            // In the case the ActivityRecord requires a letterboxed animation we never want
-            // rounded corners on the window because rounded corners are applied at the
-            // animation-bounds surface level and rounded corners on the window would interfere
-            // with that leading to unexpected rounded corner positioning during the animation.
-            return null;
-        }
-
-        final Rect cropBounds = new Rect(mActivityRecord.getBounds());
-
-        // In case of translucent activities we check if the requested size is different from
-        // the size provided using inherited bounds. In that case we decide to not apply rounded
-        // corners because we assume the specific layout would. This is the case when the layout
-        // of the translucent activity uses only a part of all the bounds because of the use of
-        // LayoutParams.WRAP_CONTENT.
-        if (mTransparentPolicy.isRunning() && (cropBounds.width() != mainWindow.mRequestedWidth
-                || cropBounds.height() != mainWindow.mRequestedHeight)) {
-            return null;
-        }
-
-        // It is important to call {@link #adjustBoundsIfNeeded} before {@link cropBounds.offsetTo}
-        // because taskbar bounds used in {@link #adjustBoundsIfNeeded}
-        // are in screen coordinates
-        adjustBoundsForTaskbar(mainWindow, cropBounds);
-
-        final float scale = mainWindow.mInvGlobalScale;
-        if (scale != 1f && scale > 0f) {
-            cropBounds.scale(scale);
-        }
-
-        // ActivityRecord bounds are in screen coordinates while (0,0) for activity's surface
-        // control is in the top left corner of an app window so offsetting bounds
-        // accordingly.
-        cropBounds.offsetTo(0, 0);
-        return cropBounds;
-    }
-
-    private boolean requiresRoundedCorners(final WindowState mainWindow) {
-        return isLetterboxedNotForDisplayCutout(mainWindow)
-                && mAppCompatConfiguration.isLetterboxActivityCornersRounded();
+        return mAppCompatLetterboxPolicy.getCropBoundsIfNeeded(mainWindow);
     }
 
     // Returns rounded corners radius the letterboxed activity should have based on override in
@@ -388,102 +136,12 @@
     // for all corners for consistency and pick a minimal bottom one for consistency with a
     // taskbar rounded corners.
     int getRoundedCornersRadius(final WindowState mainWindow) {
-        if (!requiresRoundedCorners(mainWindow)) {
-            return 0;
-        }
-
-        final int radius;
-        if (mAppCompatConfiguration.getLetterboxActivityCornersRadius() >= 0) {
-            radius = mAppCompatConfiguration.getLetterboxActivityCornersRadius();
-        } else {
-            final InsetsState insetsState = mainWindow.getInsetsState();
-            radius = Math.min(
-                    getInsetsStateCornerRadius(insetsState, RoundedCorner.POSITION_BOTTOM_LEFT),
-                    getInsetsStateCornerRadius(insetsState, RoundedCorner.POSITION_BOTTOM_RIGHT));
-        }
-
-        final float scale = mainWindow.mInvGlobalScale;
-        return (scale != 1f && scale > 0f) ? (int) (scale * radius) : radius;
+        return mAppCompatLetterboxPolicy.getRoundedCornersRadius(mainWindow);
     }
 
-    /**
-     * Returns the taskbar in case it is visible and expanded in height, otherwise returns null.
-     */
-    @VisibleForTesting
     @Nullable
-    InsetsSource getExpandedTaskbarOrNull(final WindowState mainWindow) {
-        final InsetsState state = mainWindow.getInsetsState();
-        for (int i = state.sourceSize() - 1; i >= 0; i--) {
-            final InsetsSource source = state.sourceAt(i);
-            if (source.getType() == WindowInsets.Type.navigationBars()
-                    && source.hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER)
-                    && source.isVisible()) {
-                return source;
-            }
-        }
-        return null;
-    }
-
-    private void adjustBoundsForTaskbar(final WindowState mainWindow, final Rect bounds) {
-        // Rounded corners should be displayed above the taskbar. When taskbar is hidden,
-        // an insets frame is equal to a navigation bar which shouldn't affect position of
-        // rounded corners since apps are expected to handle navigation bar inset.
-        // This condition checks whether the taskbar is visible.
-        // Do not crop the taskbar inset if the window is in immersive mode - the user can
-        // swipe to show/hide the taskbar as an overlay.
-        // Adjust the bounds only in case there is an expanded taskbar,
-        // otherwise the rounded corners will be shown behind the navbar.
-        final InsetsSource expandedTaskbarOrNull = getExpandedTaskbarOrNull(mainWindow);
-        if (expandedTaskbarOrNull != null) {
-            // Rounded corners should be displayed above the expanded taskbar.
-            bounds.bottom = Math.min(bounds.bottom, expandedTaskbarOrNull.getFrame().top);
-        }
-    }
-
-    private int getInsetsStateCornerRadius(
-                InsetsState insetsState, @RoundedCorner.Position int position) {
-        RoundedCorner corner = insetsState.getRoundedCorners().getRoundedCorner(position);
-        return corner == null ? 0 : corner.getRadius();
-    }
-
-    private boolean isLetterboxedNotForDisplayCutout(WindowState mainWindow) {
-        return shouldShowLetterboxUi(mainWindow)
-                && !mainWindow.isLetterboxedForDisplayCutout();
-    }
-
-    private void updateWallpaperForLetterbox(WindowState mainWindow) {
-        @LetterboxBackgroundType int letterboxBackgroundType =
-                mAppCompatConfiguration.getLetterboxBackgroundType();
-        boolean wallpaperShouldBeShown =
-                letterboxBackgroundType == LETTERBOX_BACKGROUND_WALLPAPER
-                        // Don't use wallpaper as a background if letterboxed for display cutout.
-                        && isLetterboxedNotForDisplayCutout(mainWindow)
-                        // Check that dark scrim alpha or blur radius are provided
-                        && (getLetterboxWallpaperBlurRadiusPx() > 0
-                                || getLetterboxWallpaperDarkScrimAlpha() > 0)
-                        // Check that blur is supported by a device if blur radius is provided.
-                        && (getLetterboxWallpaperBlurRadiusPx() <= 0
-                                || isLetterboxWallpaperBlurSupported());
-        if (mShowWallpaperForLetterboxBackground != wallpaperShouldBeShown) {
-            mShowWallpaperForLetterboxBackground = wallpaperShouldBeShown;
-            mActivityRecord.requestUpdateWallpaperIfNeeded();
-        }
-    }
-
-    private int getLetterboxWallpaperBlurRadiusPx() {
-        int blurRadius = mAppCompatConfiguration.getLetterboxBackgroundWallpaperBlurRadiusPx();
-        return Math.max(blurRadius, 0);
-    }
-
-    private float getLetterboxWallpaperDarkScrimAlpha() {
-        float alpha = mAppCompatConfiguration.getLetterboxBackgroundWallpaperDarkScrimAlpha();
-        // No scrim by default.
-        return (alpha < 0 || alpha >= 1) ? 0.0f : alpha;
-    }
-
-    private boolean isLetterboxWallpaperBlurSupported() {
-        return mAppCompatConfiguration.mContext.getSystemService(WindowManager.class)
-                .isCrossWindowBlurEnabled();
+    LetterboxDetails getLetterboxDetails() {
+        return mAppCompatLetterboxPolicy.getLetterboxDetails();
     }
 
     void dump(PrintWriter pw, String prefix) {
@@ -492,6 +150,9 @@
             return;
         }
 
+        pw.println(prefix + "isTransparentPolicyRunning="
+                + mActivityRecord.mAppCompatController.getTransparentPolicy().isRunning());
+
         boolean areBoundsLetterboxed = mainWin.areAppWindowBoundsLetterboxed();
         pw.println(prefix + "areBoundsLetterboxed=" + areBoundsLetterboxed);
         if (!areBoundsLetterboxed) {
@@ -523,11 +184,11 @@
         if (mAppCompatConfiguration.getLetterboxBackgroundType()
                 == LETTERBOX_BACKGROUND_WALLPAPER) {
             pw.println(prefix + "  isLetterboxWallpaperBlurSupported="
-                    + isLetterboxWallpaperBlurSupported());
+                    + mAppCompatLetterboxOverrides.isLetterboxWallpaperBlurSupported());
             pw.println(prefix + "  letterboxBackgroundWallpaperDarkScrimAlpha="
-                    + getLetterboxWallpaperDarkScrimAlpha());
+                    + mAppCompatLetterboxOverrides.getLetterboxWallpaperDarkScrimAlpha());
             pw.println(prefix + "  letterboxBackgroundWallpaperBlurRadius="
-                    + getLetterboxWallpaperBlurRadiusPx());
+                    + mAppCompatLetterboxOverrides.getLetterboxWallpaperBlurRadiusPx());
         }
         final AppCompatReachabilityOverrides reachabilityOverrides = mActivityRecord
                 .mAppCompatController.getAppCompatReachabilityOverrides();
@@ -557,26 +218,4 @@
                 + mAppCompatConfiguration
                 .getIsDisplayAspectRatioEnabledForFixedOrientationLetterbox());
     }
-
-    @Nullable
-    LetterboxDetails getLetterboxDetails() {
-        final WindowState w = mActivityRecord.findMainWindow();
-        if (mLetterbox == null || w == null || w.isLetterboxedForDisplayCutout()) {
-            return null;
-        }
-        Rect letterboxInnerBounds = new Rect();
-        Rect letterboxOuterBounds = new Rect();
-        getLetterboxInnerBounds(letterboxInnerBounds);
-        getLetterboxOuterBounds(letterboxOuterBounds);
-
-        if (letterboxInnerBounds.isEmpty() || letterboxOuterBounds.isEmpty()) {
-            return null;
-        }
-
-        return new LetterboxDetails(
-                letterboxInnerBounds,
-                letterboxOuterBounds,
-                w.mAttrs.insetsFlags.appearance
-        );
-    }
 }
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 32ec020..7c875c1 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -324,22 +324,6 @@
         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
     }
 
-    @Override
-    public boolean performHapticFeedback(int effectId, int flags, int privFlags) {
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            return mService.mPolicy.performHapticFeedback(mUid, mPackageName, effectId, null, flags,
-                    privFlags);
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-    }
-
-    @Override
-    public void performHapticFeedbackAsync(int effectId, int flags, int privFlags) {
-        performHapticFeedback(effectId, flags, privFlags);
-    }
-
     /* Drag/drop */
 
     @Override
diff --git a/services/core/java/com/android/server/wm/SnapshotController.java b/services/core/java/com/android/server/wm/SnapshotController.java
index 99e1e8b..0f9c001 100644
--- a/services/core/java/com/android/server/wm/SnapshotController.java
+++ b/services/core/java/com/android/server/wm/SnapshotController.java
@@ -19,8 +19,10 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION;
 import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM;
 import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 
@@ -202,10 +204,12 @@
     }
 
     private static boolean isTransitionOpen(int type) {
-        return type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT;
+        return type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT
+                || type == TRANSIT_PREPARE_BACK_NAVIGATION;
     }
     private static boolean isTransitionClose(int type) {
-        return type == TRANSIT_CLOSE || type == TRANSIT_TO_BACK;
+        return type == TRANSIT_CLOSE || type == TRANSIT_TO_BACK
+                || type == TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION;
     }
 
     void dump(PrintWriter pw, String prefix) {
diff --git a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
index 16fcb09..1c8c245 100644
--- a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
+++ b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
@@ -313,6 +313,7 @@
             proto.appearance = mSnapshot.getAppearance();
             proto.isTranslucent = mSnapshot.isTranslucent();
             proto.topActivityComponent = mSnapshot.getTopActivityComponent().flattenToString();
+            proto.uiMode = mSnapshot.getUiMode();
             proto.id = mSnapshot.getId();
             final byte[] bytes = TaskSnapshotProto.toByteArray(proto);
             final File file = mPersistInfoProvider.getProtoFile(mId, mUserId);
diff --git a/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java b/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
index b7944d3..a83e8c7 100644
--- a/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
+++ b/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
@@ -116,7 +116,7 @@
 
         final Display display = DisplayManagerGlobal.getInstance()
                 .getRealDisplay(Display.DEFAULT_DISPLAY);
-        final DisplayCutout displayCutout = display.getCutout();
+        final DisplayCutout displayCutout = display != null ? display.getCutout() : null;
         if (displayCutout != null) {
             // Expand swipe start threshold such that we can catch touches that just start beyond
             // the notch area
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index d3df5fd..12e91ad 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3507,7 +3507,7 @@
                         | StartingWindowInfo.TYPE_PARAMETER_WINDOWLESS);
         if ((info.startingWindowTypeParameter
                 & StartingWindowInfo.TYPE_PARAMETER_ACTIVITY_CREATED) != 0) {
-            final WindowState topMainWin = getWindow(w -> w.mAttrs.type == TYPE_BASE_APPLICATION);
+            final WindowState topMainWin = getTopFullscreenMainWindow();
             if (topMainWin != null) {
                 info.mainWindowLayoutParams = topMainWin.getAttrs();
                 info.requestedVisibleTypes = topMainWin.getRequestedVisibleTypes();
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index eaf3012..dba1c36 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -1079,8 +1079,11 @@
         // Use launch-adjacent-flag-root if launching with launch-adjacent flag.
         if ((launchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0
                 && mLaunchAdjacentFlagRootTask != null) {
-            if (sourceTask != null && sourceTask == candidateTask) {
-                // Do nothing when task that is getting opened is same as the source.
+            if (sourceTask != null && (sourceTask == candidateTask
+                    || sourceTask.topRunningActivity() == null)) {
+                // Do nothing when task that is getting opened is same as the source or when
+                // the source is no-longer valid.
+                Slog.w(TAG_WM, "Ignoring LAUNCH_ADJACENT because adjacent source is gone.");
             } else if (sourceTask != null
                     && mLaunchAdjacentFlagRootTask.getAdjacentTask() != null
                     && (sourceTask == mLaunchAdjacentFlagRootTask
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 561ff7d..7212d37 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -407,8 +407,7 @@
                 change.setTaskFragmentToken(lastParentTfToken);
             }
             // Only pass the activity token to the client if it belongs to the same process.
-            if (Flags.fixPipRestoreToOverlay() && nextFillTaskActivity != null
-                    && nextFillTaskActivity.getPid() == mOrganizerPid) {
+            if (nextFillTaskActivity != null && nextFillTaskActivity.getPid() == mOrganizerPid) {
                 change.setOtherActivityToken(nextFillTaskActivity.token);
             }
             return change;
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 5698750..486a61b 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -358,8 +358,12 @@
         return mToken;
     }
 
-    void addFlag(int flag) {
-        mFlags |= flag;
+    void addFlag(@TransitionFlags int flags) {
+        mFlags |= flags;
+    }
+
+    void removeFlag(@TransitionFlags int flags) {
+        mFlags &= ~flags;
     }
 
     void calcParallelCollectType(WindowContainerTransaction wct) {
@@ -3350,6 +3354,15 @@
         return chg.hasChanged();
     }
 
+    boolean hasChanges() {
+        for (int i = 0; i < mParticipants.size(); ++i) {
+            if (mChanges.get(mParticipants.valueAt(i)).hasChanged()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     @VisibleForTesting
     static class ChangeInfo {
         private static final int FLAG_NONE = 0;
diff --git a/services/core/java/com/android/server/wm/TransparentPolicy.java b/services/core/java/com/android/server/wm/TransparentPolicy.java
index 2f46103..39b2635 100644
--- a/services/core/java/com/android/server/wm/TransparentPolicy.java
+++ b/services/core/java/com/android/server/wm/TransparentPolicy.java
@@ -92,6 +92,7 @@
         if (parent == null) {
             return;
         }
+        final boolean wasStarted = mTransparentPolicyState.isRunning();
         mTransparentPolicyState.reset();
         // In case mActivityRecord.hasCompatDisplayInsetsWithoutOverride() we don't apply the
         // opaque activity constraints because we're expecting the activity is already letterboxed.
@@ -102,6 +103,9 @@
         // We check if we need for some reason to skip the policy gievn the specific first
         // opaque activity
         if (shouldSkipTransparentPolicy(firstOpaqueActivity)) {
+            if (wasStarted) {
+                mActivityRecord.recomputeConfiguration();
+            }
             return;
         }
         mTransparentPolicyState.start(firstOpaqueActivity);
@@ -190,7 +194,6 @@
             // We skip letterboxing if the translucent activity doesn't have any
             // opaque activities beneath or the activity below is embedded which
             // never has letterbox.
-            mActivityRecord.recomputeConfiguration();
             return true;
         }
         if (mActivityRecord.getTask() == null || mActivityRecord.fillsParent()
@@ -260,6 +263,10 @@
             mLetterboxConfigListener = WindowContainer.overrideConfigurationPropagation(
                     mActivityRecord, mFirstOpaqueActivity,
                     (opaqueConfig, transparentOverrideConfig) -> {
+                        if (!isPolicyEnabled()) {
+                            transparentOverrideConfig.unset();
+                            return transparentOverrideConfig;
+                        }
                         resetTranslucentOverrideConfig(transparentOverrideConfig);
                         final Rect parentBounds = parent.getWindowConfiguration().getBounds();
                         final Rect bounds = transparentOverrideConfig
@@ -313,7 +320,17 @@
         }
 
         private boolean isRunning() {
-            return mLetterboxConfigListener != null;
+            return mLetterboxConfigListener != null && isPolicyEnabled();
+        }
+
+        private boolean isPolicyEnabled() {
+            if (!mActivityRecord.mWmService.mFlags.mRespectNonTopVisibleFixedOrientation) {
+                return true;
+            }
+            // Do not enable the policy if the activity can affect display orientation.
+            final int orientation = mActivityRecord.getOverrideOrientation();
+            return orientation == SCREEN_ORIENTATION_UNSPECIFIED
+                    || !mActivityRecord.handlesOrientationChangeFromDescendant(orientation);
         }
 
         private void clearInheritedCompatDisplayInsets() {
diff --git a/services/core/java/com/android/server/wm/WindowAnimationSpec.java b/services/core/java/com/android/server/wm/WindowAnimationSpec.java
index 34b9913..2c58c61 100644
--- a/services/core/java/com/android/server/wm/WindowAnimationSpec.java
+++ b/services/core/java/com/android/server/wm/WindowAnimationSpec.java
@@ -97,10 +97,10 @@
 
     /**
      * @return If a window animation has outsets applied to it.
-     * @see Animation#hasExtension()
+     * @see Animation#getExtensionEdges()
      */
     public boolean hasExtension() {
-        return mAnimation.hasExtension();
+        return mAnimation.getExtensionEdges() != 0;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 0093e9d..c6d0b72 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -22,6 +22,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS;
 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;
 import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_OR_MOVE_TASK_FRAGMENT_DECOR_SURFACE;
 import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT;
@@ -59,6 +60,7 @@
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_TASK;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_RESTORE_BACK_NAVIGATION;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_RESTORE_TRANSIENT_ORDER;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP;
@@ -436,6 +438,11 @@
             // the same transition instead of relying on this possible racing condition.
             return;
         }
+        if (transition.mType == TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION
+                && mService.mBackNavigationController.restoreBackNavigationSetTransitionReady(
+                        transition)) {
+            return;
+        }
 
         transition.setAllReady();
     }
@@ -1187,7 +1194,13 @@
                         }
                     }
                 }
-                effects |= sanitizeAndApplyHierarchyOp(wc, hop);
+                if (wc.asTask() != null) {
+                    effects |= sanitizeAndApplyHierarchyOpForTask(wc.asTask(), hop);
+                } else if (wc.asDisplayArea() != null) {
+                    effects |= sanitizeAndApplyHierarchyOpForDisplayArea(wc.asDisplayArea(), hop);
+                } else {
+                    throw new IllegalArgumentException("Invalid container in hierarchy op");
+                }
                 break;
             }
             case HIERARCHY_OP_TYPE_ADD_TASK_FRAGMENT_OPERATION: {
@@ -1386,6 +1399,12 @@
                 task.setTrimmableFromRecents(hop.isTrimmableFromRecents());
                 break;
             }
+            case HIERARCHY_OP_TYPE_RESTORE_BACK_NAVIGATION: {
+                if (mService.mBackNavigationController.restoreBackNavigation()) {
+                    effects |= TRANSACT_EFFECTS_LIFECYCLE;
+                }
+                break;
+            }
         }
         return effects;
     }
@@ -1837,12 +1856,22 @@
         return starterResult[0];
     }
 
-    private int sanitizeAndApplyHierarchyOp(WindowContainer container,
-            WindowContainerTransaction.HierarchyOp hop) {
-        final Task task = container.asTask();
-        if (task == null) {
-            throw new IllegalArgumentException("Invalid container in hierarchy op");
+    private int sanitizeAndApplyHierarchyOpForDisplayArea(@NonNull DisplayArea displayArea,
+            @NonNull WindowContainerTransaction.HierarchyOp hop) {
+        if (hop.getType() != HIERARCHY_OP_TYPE_REORDER) {
+            throw new UnsupportedOperationException("DisplayArea only supports reordering");
         }
+        if (displayArea.getParent() == null) {
+            return TRANSACT_EFFECTS_NONE;
+        }
+        displayArea.getParent().positionChildAt(
+                hop.getToTop() ? POSITION_TOP : POSITION_BOTTOM,
+                displayArea, hop.includingParents());
+        return TRANSACT_EFFECTS_LIFECYCLE;
+    }
+
+    private int sanitizeAndApplyHierarchyOpForTask(@NonNull Task task,
+            @NonNull WindowContainerTransaction.HierarchyOp hop) {
         final DisplayContent dc = task.getDisplayContent();
         if (dc == null) {
             Slog.w(TAG, "Container is no longer attached: " + task);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index e646752..ec2fd3f 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -55,6 +55,7 @@
 import static android.view.WindowManager.LayoutParams.MATCH_PARENT;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NOT_MAGNIFIABLE;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPT_OUT_EDGE_TO_EDGE;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
@@ -2989,6 +2990,25 @@
         return (mAttrs.flags & FLAG_SHOW_WHEN_LOCKED) != 0;
     }
 
+    @Override
+    void resolveOverrideConfiguration(Configuration newParentConfig) {
+        super.resolveOverrideConfiguration(newParentConfig);
+        if (mActivityRecord != null) {
+            // Let the activity decide whether to apply the size override.
+            return;
+        }
+        final Configuration resolvedConfig = getResolvedOverrideConfiguration();
+        resolvedConfig.seq = newParentConfig.seq;
+        applySizeOverrideIfNeeded(
+                getDisplayContent(),
+                mSession.mProcess.mInfo,
+                newParentConfig,
+                resolvedConfig,
+                (mAttrs.privateFlags & PRIVATE_FLAG_OPT_OUT_EDGE_TO_EDGE) != 0,
+                false /* hasFixedRotationTransform */,
+                false /* hasCompatDisplayInsets */);
+    }
+
     /**
      * @return {@code true} if this window can receive touches based on among other things,
      * windowing state and recents animation state.
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index f1e94de..a5085fc 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -211,7 +211,6 @@
         "android.system.suspend-V1-ndk",
         "server_configurable_flags",
         "service.incremental",
-        "android.companion.virtualdevice.flags-aconfig-cc",
     ],
 
     static_libs: [
diff --git a/services/core/jni/com_android_server_companion_virtual_InputController.cpp b/services/core/jni/com_android_server_companion_virtual_InputController.cpp
index 9ec5ae5..6f72760 100644
--- a/services/core/jni/com_android_server_companion_virtual_InputController.cpp
+++ b/services/core/jni/com_android_server_companion_virtual_InputController.cpp
@@ -19,273 +19,22 @@
 #include <android-base/unique_fd.h>
 #include <android/input.h>
 #include <android/keycodes.h>
-#include <android_companion_virtualdevice_flags.h>
-#include <errno.h>
-#include <fcntl.h>
 #include <input/Input.h>
 #include <input/VirtualInputDevice.h>
-#include <linux/uinput.h>
-#include <math.h>
 #include <nativehelper/JNIHelp.h>
 #include <nativehelper/ScopedUtfChars.h>
-#include <utils/Log.h>
 
-#include <map>
-#include <set>
 #include <string>
 
 using android::base::unique_fd;
 
 namespace android {
 
-namespace vd_flags = android::companion::virtualdevice::flags;
-
 static constexpr jlong INVALID_PTR = 0;
 
-enum class DeviceType {
-    KEYBOARD,
-    MOUSE,
-    TOUCHSCREEN,
-    DPAD,
-    STYLUS,
-    ROTARY_ENCODER,
-};
-
-static unique_fd invalidFd() {
-    return unique_fd(-1);
-}
-
-/** Creates a new uinput device and assigns a file descriptor. */
-static unique_fd openUinput(const char* readableName, jint vendorId, jint productId,
-                            const char* phys, DeviceType deviceType, jint screenHeight,
-                            jint screenWidth) {
-    unique_fd fd(TEMP_FAILURE_RETRY(::open("/dev/uinput", O_WRONLY | O_NONBLOCK)));
-    if (fd < 0) {
-        ALOGE("Error creating uinput device: %s", strerror(errno));
-        return invalidFd();
-    }
-
-    ioctl(fd, UI_SET_PHYS, phys);
-
-    ioctl(fd, UI_SET_EVBIT, EV_KEY);
-    ioctl(fd, UI_SET_EVBIT, EV_SYN);
-    switch (deviceType) {
-        case DeviceType::DPAD:
-            for (const auto& [_, keyCode] : VirtualDpad::DPAD_KEY_CODE_MAPPING) {
-                ioctl(fd, UI_SET_KEYBIT, keyCode);
-            }
-            break;
-        case DeviceType::KEYBOARD:
-            for (const auto& [_, keyCode] : VirtualKeyboard::KEY_CODE_MAPPING) {
-                ioctl(fd, UI_SET_KEYBIT, keyCode);
-            }
-            break;
-        case DeviceType::MOUSE:
-            ioctl(fd, UI_SET_EVBIT, EV_REL);
-            ioctl(fd, UI_SET_KEYBIT, BTN_LEFT);
-            ioctl(fd, UI_SET_KEYBIT, BTN_RIGHT);
-            ioctl(fd, UI_SET_KEYBIT, BTN_MIDDLE);
-            ioctl(fd, UI_SET_KEYBIT, BTN_BACK);
-            ioctl(fd, UI_SET_KEYBIT, BTN_FORWARD);
-            ioctl(fd, UI_SET_RELBIT, REL_X);
-            ioctl(fd, UI_SET_RELBIT, REL_Y);
-            ioctl(fd, UI_SET_RELBIT, REL_WHEEL);
-            ioctl(fd, UI_SET_RELBIT, REL_HWHEEL);
-            if (vd_flags::high_resolution_scroll()) {
-                ioctl(fd, UI_SET_RELBIT, REL_WHEEL_HI_RES);
-                ioctl(fd, UI_SET_RELBIT, REL_HWHEEL_HI_RES);
-            }
-            break;
-        case DeviceType::TOUCHSCREEN:
-            ioctl(fd, UI_SET_EVBIT, EV_ABS);
-            ioctl(fd, UI_SET_KEYBIT, BTN_TOUCH);
-            ioctl(fd, UI_SET_ABSBIT, ABS_MT_SLOT);
-            ioctl(fd, UI_SET_ABSBIT, ABS_MT_POSITION_X);
-            ioctl(fd, UI_SET_ABSBIT, ABS_MT_POSITION_Y);
-            ioctl(fd, UI_SET_ABSBIT, ABS_MT_TRACKING_ID);
-            ioctl(fd, UI_SET_ABSBIT, ABS_MT_TOOL_TYPE);
-            ioctl(fd, UI_SET_ABSBIT, ABS_MT_TOUCH_MAJOR);
-            ioctl(fd, UI_SET_ABSBIT, ABS_MT_PRESSURE);
-            ioctl(fd, UI_SET_PROPBIT, INPUT_PROP_DIRECT);
-            break;
-        case DeviceType::STYLUS:
-            ioctl(fd, UI_SET_EVBIT, EV_ABS);
-            ioctl(fd, UI_SET_KEYBIT, BTN_TOUCH);
-            ioctl(fd, UI_SET_KEYBIT, BTN_STYLUS);
-            ioctl(fd, UI_SET_KEYBIT, BTN_STYLUS2);
-            ioctl(fd, UI_SET_KEYBIT, BTN_TOOL_PEN);
-            ioctl(fd, UI_SET_KEYBIT, BTN_TOOL_RUBBER);
-            ioctl(fd, UI_SET_ABSBIT, ABS_X);
-            ioctl(fd, UI_SET_ABSBIT, ABS_Y);
-            ioctl(fd, UI_SET_ABSBIT, ABS_TILT_X);
-            ioctl(fd, UI_SET_ABSBIT, ABS_TILT_Y);
-            ioctl(fd, UI_SET_ABSBIT, ABS_PRESSURE);
-            ioctl(fd, UI_SET_PROPBIT, INPUT_PROP_DIRECT);
-            break;
-        case DeviceType::ROTARY_ENCODER:
-            ioctl(fd, UI_SET_EVBIT, EV_REL);
-            ioctl(fd, UI_SET_RELBIT, REL_WHEEL);
-            if (vd_flags::high_resolution_scroll()) {
-                ioctl(fd, UI_SET_RELBIT, REL_WHEEL_HI_RES);
-            }
-            break;
-        default:
-            ALOGE("Invalid input device type %d", static_cast<int32_t>(deviceType));
-            return invalidFd();
-    }
-
-    int version;
-    if (ioctl(fd, UI_GET_VERSION, &version) == 0 && version >= 5) {
-        uinput_setup setup;
-        memset(&setup, 0, sizeof(setup));
-        strlcpy(setup.name, readableName, UINPUT_MAX_NAME_SIZE);
-        setup.id.version = 1;
-        setup.id.bustype = BUS_VIRTUAL;
-        setup.id.vendor = vendorId;
-        setup.id.product = productId;
-        if (deviceType == DeviceType::TOUCHSCREEN) {
-            uinput_abs_setup xAbsSetup;
-            xAbsSetup.code = ABS_MT_POSITION_X;
-            xAbsSetup.absinfo.maximum = screenWidth - 1;
-            xAbsSetup.absinfo.minimum = 0;
-            if (ioctl(fd, UI_ABS_SETUP, &xAbsSetup) != 0) {
-                ALOGE("Error creating touchscreen uinput x axis: %s", strerror(errno));
-                return invalidFd();
-            }
-            uinput_abs_setup yAbsSetup;
-            yAbsSetup.code = ABS_MT_POSITION_Y;
-            yAbsSetup.absinfo.maximum = screenHeight - 1;
-            yAbsSetup.absinfo.minimum = 0;
-            if (ioctl(fd, UI_ABS_SETUP, &yAbsSetup) != 0) {
-                ALOGE("Error creating touchscreen uinput y axis: %s", strerror(errno));
-                return invalidFd();
-            }
-            uinput_abs_setup majorAbsSetup;
-            majorAbsSetup.code = ABS_MT_TOUCH_MAJOR;
-            majorAbsSetup.absinfo.maximum = screenWidth - 1;
-            majorAbsSetup.absinfo.minimum = 0;
-            if (ioctl(fd, UI_ABS_SETUP, &majorAbsSetup) != 0) {
-                ALOGE("Error creating touchscreen uinput major axis: %s", strerror(errno));
-                return invalidFd();
-            }
-            uinput_abs_setup pressureAbsSetup;
-            pressureAbsSetup.code = ABS_MT_PRESSURE;
-            pressureAbsSetup.absinfo.maximum = 255;
-            pressureAbsSetup.absinfo.minimum = 0;
-            if (ioctl(fd, UI_ABS_SETUP, &pressureAbsSetup) != 0) {
-                ALOGE("Error creating touchscreen uinput pressure axis: %s", strerror(errno));
-                return invalidFd();
-            }
-            uinput_abs_setup slotAbsSetup;
-            slotAbsSetup.code = ABS_MT_SLOT;
-            slotAbsSetup.absinfo.maximum = MAX_POINTERS - 1;
-            slotAbsSetup.absinfo.minimum = 0;
-            if (ioctl(fd, UI_ABS_SETUP, &slotAbsSetup) != 0) {
-                ALOGE("Error creating touchscreen uinput slots: %s", strerror(errno));
-                return invalidFd();
-            }
-            uinput_abs_setup trackingIdAbsSetup;
-            trackingIdAbsSetup.code = ABS_MT_TRACKING_ID;
-            trackingIdAbsSetup.absinfo.maximum = MAX_POINTERS - 1;
-            trackingIdAbsSetup.absinfo.minimum = 0;
-            if (ioctl(fd, UI_ABS_SETUP, &trackingIdAbsSetup) != 0) {
-                ALOGE("Error creating touchscreen uinput tracking ids: %s", strerror(errno));
-                return invalidFd();
-            }
-        } else if (deviceType == DeviceType::STYLUS) {
-            uinput_abs_setup xAbsSetup;
-            xAbsSetup.code = ABS_X;
-            xAbsSetup.absinfo.maximum = screenWidth - 1;
-            xAbsSetup.absinfo.minimum = 0;
-            if (ioctl(fd, UI_ABS_SETUP, &xAbsSetup) != 0) {
-                ALOGE("Error creating stylus uinput x axis: %s", strerror(errno));
-                return invalidFd();
-            }
-            uinput_abs_setup yAbsSetup;
-            yAbsSetup.code = ABS_Y;
-            yAbsSetup.absinfo.maximum = screenHeight - 1;
-            yAbsSetup.absinfo.minimum = 0;
-            if (ioctl(fd, UI_ABS_SETUP, &yAbsSetup) != 0) {
-                ALOGE("Error creating stylus uinput y axis: %s", strerror(errno));
-                return invalidFd();
-            }
-            uinput_abs_setup tiltXAbsSetup;
-            tiltXAbsSetup.code = ABS_TILT_X;
-            tiltXAbsSetup.absinfo.maximum = 90;
-            tiltXAbsSetup.absinfo.minimum = -90;
-            if (ioctl(fd, UI_ABS_SETUP, &tiltXAbsSetup) != 0) {
-                ALOGE("Error creating stylus uinput tilt x axis: %s", strerror(errno));
-                return invalidFd();
-            }
-            uinput_abs_setup tiltYAbsSetup;
-            tiltYAbsSetup.code = ABS_TILT_Y;
-            tiltYAbsSetup.absinfo.maximum = 90;
-            tiltYAbsSetup.absinfo.minimum = -90;
-            if (ioctl(fd, UI_ABS_SETUP, &tiltYAbsSetup) != 0) {
-                ALOGE("Error creating stylus uinput tilt y axis: %s", strerror(errno));
-                return invalidFd();
-            }
-            uinput_abs_setup pressureAbsSetup;
-            pressureAbsSetup.code = ABS_PRESSURE;
-            pressureAbsSetup.absinfo.maximum = 255;
-            pressureAbsSetup.absinfo.minimum = 0;
-            if (ioctl(fd, UI_ABS_SETUP, &pressureAbsSetup) != 0) {
-                ALOGE("Error creating touchscreen uinput pressure axis: %s", strerror(errno));
-                return invalidFd();
-            }
-        }
-        if (ioctl(fd, UI_DEV_SETUP, &setup) != 0) {
-            ALOGE("Error creating uinput device: %s", strerror(errno));
-            return invalidFd();
-        }
-    } else {
-        // UI_DEV_SETUP was not introduced until version 5. Try setting up manually.
-        ALOGI("Falling back to version %d manual setup", version);
-        uinput_user_dev fallback;
-        memset(&fallback, 0, sizeof(fallback));
-        strlcpy(fallback.name, readableName, UINPUT_MAX_NAME_SIZE);
-        fallback.id.version = 1;
-        fallback.id.bustype = BUS_VIRTUAL;
-        fallback.id.vendor = vendorId;
-        fallback.id.product = productId;
-        if (deviceType == DeviceType::TOUCHSCREEN) {
-            fallback.absmin[ABS_MT_POSITION_X] = 0;
-            fallback.absmax[ABS_MT_POSITION_X] = screenWidth - 1;
-            fallback.absmin[ABS_MT_POSITION_Y] = 0;
-            fallback.absmax[ABS_MT_POSITION_Y] = screenHeight - 1;
-            fallback.absmin[ABS_MT_TOUCH_MAJOR] = 0;
-            fallback.absmax[ABS_MT_TOUCH_MAJOR] = screenWidth - 1;
-            fallback.absmin[ABS_MT_PRESSURE] = 0;
-            fallback.absmax[ABS_MT_PRESSURE] = 255;
-        } else if (deviceType == DeviceType::STYLUS) {
-            fallback.absmin[ABS_X] = 0;
-            fallback.absmax[ABS_X] = screenWidth - 1;
-            fallback.absmin[ABS_Y] = 0;
-            fallback.absmax[ABS_Y] = screenHeight - 1;
-            fallback.absmin[ABS_TILT_X] = -90;
-            fallback.absmax[ABS_TILT_X] = 90;
-            fallback.absmin[ABS_TILT_Y] = -90;
-            fallback.absmax[ABS_TILT_Y] = 90;
-            fallback.absmin[ABS_PRESSURE] = 0;
-            fallback.absmax[ABS_PRESSURE] = 255;
-        }
-        if (TEMP_FAILURE_RETRY(write(fd, &fallback, sizeof(fallback))) != sizeof(fallback)) {
-            ALOGE("Error creating uinput device: %s", strerror(errno));
-            return invalidFd();
-        }
-    }
-
-    if (ioctl(fd, UI_DEV_CREATE) != 0) {
-        ALOGE("Error creating uinput device: %s", strerror(errno));
-        return invalidFd();
-    }
-
-    return fd;
-}
-
 static unique_fd openUinputJni(JNIEnv* env, jstring name, jint vendorId, jint productId,
-                               jstring phys, DeviceType deviceType, int screenHeight,
-                               int screenWidth) {
+                               jstring phys, DeviceType deviceType, jint screenHeight,
+                               jint screenWidth) {
     ScopedUtfChars readableName(env, name);
     ScopedUtfChars readablePhys(env, phys);
     return openUinput(readableName.c_str(), vendorId, productId, readablePhys.c_str(), deviceType,
diff --git a/services/core/jni/com_android_server_utils_AnrTimer.cpp b/services/core/jni/com_android_server_utils_AnrTimer.cpp
index 2edf129..cf96114 100644
--- a/services/core/jni/com_android_server_utils_AnrTimer.cpp
+++ b/services/core/jni/com_android_server_utils_AnrTimer.cpp
@@ -934,7 +934,6 @@
 }
 
 // Hold the lock in order to manage the running list.
-// the listener.
 void AnrTimerService::expire(timer_id_t timerId) {
     // Save the timer attributes for the notification
     int pid = 0;
@@ -967,7 +966,6 @@
     // Deliver the notification outside of the lock.
     if (expired) {
         if (!notifier_(timerId, pid, uid, elapsed, notifierCookie_, notifierObject_)) {
-            AutoMutex _l(lock_);
             // Notification failed, which means the listener will never call accept() or
             // discard().  Do not reinsert the timer.
             discard(timerId);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index 669a999..a08af72 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -2080,10 +2080,14 @@
                 String tag = parser.getName();
                 switch (tag) {
                     case TAG_LOCAL_POLICY_ENTRY:
-                        readLocalPoliciesInner(parser);
+                        int userId = parser.getAttributeInt(/* namespace= */ null, ATTR_USER_ID);
+                        if (!mLocalPolicies.contains(userId)) {
+                            mLocalPolicies.put(userId, new HashMap<>());
+                        }
+                        readPoliciesInner(parser, mLocalPolicies.get(userId));
                         break;
                     case TAG_GLOBAL_POLICY_ENTRY:
-                        readGlobalPoliciesInner(parser);
+                        readPoliciesInner(parser, mGlobalPolicies);
                         break;
                     case TAG_ENFORCING_ADMINS_ENTRY:
                         readEnforcingAdminsInner(parser);
@@ -2100,64 +2104,45 @@
             }
         }
 
-        private void readLocalPoliciesInner(TypedXmlPullParser parser)
-                throws XmlPullParserException, IOException {
-            int userId = parser.getAttributeInt(/* namespace= */ null, ATTR_USER_ID);
-            PolicyKey policyKey = null;
-            PolicyState<?> policyState = null;
-            int outerDepth = parser.getDepth();
-            while (XmlUtils.nextElementWithin(parser, outerDepth)) {
-                String tag = parser.getName();
-                switch (tag) {
-                    case TAG_POLICY_KEY_ENTRY:
-                        policyKey = PolicyDefinition.readPolicyKeyFromXml(parser);
-                        break;
-                    case TAG_POLICY_STATE_ENTRY:
-                        policyState = PolicyState.readFromXml(parser);
-                        break;
-                    default:
-                        Slogf.wtf(TAG, "Unknown tag for local policy entry" + tag);
-                }
-            }
-
-            if (policyKey != null && policyState != null) {
-                if (!mLocalPolicies.contains(userId)) {
-                    mLocalPolicies.put(userId, new HashMap<>());
-                }
-                mLocalPolicies.get(userId).put(policyKey, policyState);
-            } else {
-                Slogf.wtf(TAG, "Error parsing local policy, policyKey is "
-                        + (policyKey == null ? "null" : policyKey) + ", and policyState is "
-                        + (policyState == null ? "null" : policyState) + ".");
-            }
-        }
-
-        private void readGlobalPoliciesInner(TypedXmlPullParser parser)
+        private static void readPoliciesInner(
+                TypedXmlPullParser parser, Map<PolicyKey, PolicyState<?>> policyStateMap)
                 throws IOException, XmlPullParserException {
             PolicyKey policyKey = null;
+            PolicyDefinition<?> policyDefinition = null;
             PolicyState<?> policyState = null;
             int outerDepth = parser.getDepth();
             while (XmlUtils.nextElementWithin(parser, outerDepth)) {
                 String tag = parser.getName();
                 switch (tag) {
                     case TAG_POLICY_KEY_ENTRY:
-                        policyKey = PolicyDefinition.readPolicyKeyFromXml(parser);
+                        if (Flags.dontReadPolicyDefinition()) {
+                            policyDefinition = PolicyDefinition.readFromXml(parser);
+                            if (policyDefinition != null) {
+                                policyKey = policyDefinition.getPolicyKey();
+                            }
+                        } else {
+                            policyKey = PolicyDefinition.readPolicyKeyFromXml(parser);
+                        }
                         break;
                     case TAG_POLICY_STATE_ENTRY:
-                        policyState = PolicyState.readFromXml(parser);
+                        if (Flags.dontReadPolicyDefinition() && policyDefinition == null) {
+                            Slogf.w(TAG, "Skipping policy state - unknown policy definition");
+                        } else {
+                            policyState = PolicyState.readFromXml(policyDefinition, parser);
+                        }
                         break;
                     default:
-                        Slogf.wtf(TAG, "Unknown tag for local policy entry" + tag);
+                        Slogf.wtf(TAG, "Unknown tag for policy entry" + tag);
                 }
             }
 
-            if (policyKey != null && policyState != null) {
-                mGlobalPolicies.put(policyKey, policyState);
-            } else {
-                Slogf.wtf(TAG, "Error parsing global policy, policyKey is "
-                        + (policyKey == null ? "null" : policyKey) + ", and policyState is "
-                        + (policyState == null ? "null" : policyState) + ".");
+            if (policyKey == null || policyState == null) {
+                Slogf.wtf(TAG, "Error parsing policy, policyKey is %s, and policyState is %s.",
+                        policyKey, policyState);
+                return;
             }
+
+            policyStateMap.put(policyKey, policyState);
         }
 
         private void readEnforcingAdminsInner(TypedXmlPullParser parser)
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index 8e3248e..19a942c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -129,9 +129,8 @@
      */
     static PolicyDefinition<Integer> PERMISSION_GRANT(
             @NonNull String packageName, @NonNull String permissionName) {
-        if (packageName == null || permissionName == null) {
-            return GENERIC_PERMISSION_GRANT;
-        }
+        Objects.requireNonNull(packageName, "packageName must not be null");
+        Objects.requireNonNull(permissionName, "permissionName must not be null");
         return GENERIC_PERMISSION_GRANT.createPolicyDefinition(
                 new PackagePermissionPolicyKey(
                         DevicePolicyIdentifiers.PERMISSION_GRANT_POLICY,
@@ -190,10 +189,8 @@
      * {@link #GENERIC_PERSISTENT_PREFERRED_ACTIVITY}.
      */
     static PolicyDefinition<ComponentName> PERSISTENT_PREFERRED_ACTIVITY(
-            IntentFilter intentFilter) {
-        if (intentFilter == null) {
-            return GENERIC_PERSISTENT_PREFERRED_ACTIVITY;
-        }
+            @NonNull IntentFilter intentFilter) {
+        Objects.requireNonNull(intentFilter, "intentFilter must not be null");
         return GENERIC_PERSISTENT_PREFERRED_ACTIVITY.createPolicyDefinition(
                 new IntentFilterPolicyKey(
                         DevicePolicyIdentifiers.PERSISTENT_PREFERRED_ACTIVITY_POLICY,
@@ -216,11 +213,8 @@
      * Passing in {@code null} for {@code packageName} will return
      * {@link #GENERIC_PACKAGE_UNINSTALL_BLOCKED}.
      */
-    static PolicyDefinition<Boolean> PACKAGE_UNINSTALL_BLOCKED(
-            String packageName) {
-        if (packageName == null) {
-            return GENERIC_PACKAGE_UNINSTALL_BLOCKED;
-        }
+    static PolicyDefinition<Boolean> PACKAGE_UNINSTALL_BLOCKED(@NonNull String packageName) {
+        Objects.requireNonNull(packageName, "packageName must not be null");
         return GENERIC_PACKAGE_UNINSTALL_BLOCKED.createPolicyDefinition(
                 new PackagePolicyKey(
                         DevicePolicyIdentifiers.PACKAGE_UNINSTALL_BLOCKED_POLICY, packageName));
@@ -247,10 +241,8 @@
      * Passing in {@code null} for {@code packageName} will return
      * {@link #GENERIC_APPLICATION_RESTRICTIONS}.
      */
-    static PolicyDefinition<Bundle> APPLICATION_RESTRICTIONS(String packageName) {
-        if (packageName == null) {
-            return GENERIC_APPLICATION_RESTRICTIONS;
-        }
+    static PolicyDefinition<Bundle> APPLICATION_RESTRICTIONS(@NonNull String packageName) {
+        Objects.requireNonNull(packageName, "packageName must not be null");
         return GENERIC_APPLICATION_RESTRICTIONS.createPolicyDefinition(
                 new PackagePolicyKey(
                         DevicePolicyIdentifiers.APPLICATION_RESTRICTIONS_POLICY, packageName));
@@ -293,10 +285,8 @@
      * Passing in {@code null} for {@code packageName} will return
      * {@link #GENERIC_APPLICATION_HIDDEN}.
      */
-    static PolicyDefinition<Boolean> APPLICATION_HIDDEN(String packageName) {
-        if (packageName == null) {
-            return GENERIC_APPLICATION_HIDDEN;
-        }
+    static PolicyDefinition<Boolean> APPLICATION_HIDDEN(@NonNull String packageName) {
+        Objects.requireNonNull(packageName, "packageName must not be null");
         return GENERIC_APPLICATION_HIDDEN.createPolicyDefinition(
                 new PackagePolicyKey(
                         DevicePolicyIdentifiers.APPLICATION_HIDDEN_POLICY, packageName));
@@ -319,10 +309,8 @@
      * Passing in {@code null} for {@code accountType} will return
      * {@link #GENERIC_ACCOUNT_MANAGEMENT_DISABLED}.
      */
-    static PolicyDefinition<Boolean> ACCOUNT_MANAGEMENT_DISABLED(String accountType) {
-        if (accountType == null) {
-            return GENERIC_ACCOUNT_MANAGEMENT_DISABLED;
-        }
+    static PolicyDefinition<Boolean> ACCOUNT_MANAGEMENT_DISABLED(@NonNull String accountType) {
+        Objects.requireNonNull(accountType, "accountType must not be null");
         return GENERIC_ACCOUNT_MANAGEMENT_DISABLED.createPolicyDefinition(
                 new AccountTypePolicyKey(
                         DevicePolicyIdentifiers.ACCOUNT_MANAGEMENT_DISABLED_POLICY, accountType));
@@ -708,17 +696,15 @@
     }
 
     @Nullable
-    static <V> PolicyKey readPolicyKeyFromXml(TypedXmlPullParser parser)
+    static PolicyKey readPolicyKeyFromXml(TypedXmlPullParser parser)
             throws XmlPullParserException, IOException {
-        // TODO: can we avoid casting?
         PolicyKey policyKey = PolicyKey.readGenericPolicyKeyFromXml(parser);
         if (policyKey == null) {
             Slogf.wtf(TAG, "Error parsing PolicyKey, GenericPolicyKey is null");
             return null;
         }
-        PolicyDefinition<PolicyValue<V>> genericPolicyDefinition =
-                (PolicyDefinition<PolicyValue<V>>) POLICY_DEFINITIONS.get(
-                        policyKey.getIdentifier());
+        PolicyDefinition<?> genericPolicyDefinition =
+                POLICY_DEFINITIONS.get(policyKey.getIdentifier());
         if (genericPolicyDefinition == null) {
             Slogf.wtf(TAG, "Error parsing PolicyKey, Unknown generic policy key: " + policyKey);
             return null;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
index 245c438..b813489 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.admin.PolicyValue;
+import android.app.admin.flags.Flags;
 import android.util.IndentingPrintWriter;
 
 import com.android.internal.util.XmlUtils;
@@ -254,11 +255,9 @@
     }
 
     @Nullable
-    static <V> PolicyState<V> readFromXml(TypedXmlPullParser parser)
+    static <V> PolicyState<V> readFromXml(
+            PolicyDefinition<V> policyDefinition, TypedXmlPullParser parser)
             throws IOException, XmlPullParserException {
-
-        PolicyDefinition<V> policyDefinition = null;
-
         PolicyValue<V> currentResolvedPolicy = null;
 
         LinkedHashMap<EnforcingAdmin, PolicyValue<V>> policiesSetByAdmins = new LinkedHashMap<>();
@@ -300,10 +299,15 @@
                     }
                     break;
                 case TAG_POLICY_DEFINITION_ENTRY:
-                    policyDefinition = PolicyDefinition.readFromXml(parser);
-                    if (policyDefinition == null) {
-                        Slogf.wtf(TAG, "Error Parsing TAG_POLICY_DEFINITION_ENTRY, "
-                                + "PolicyDefinition is null");
+                    if (Flags.dontReadPolicyDefinition()) {
+                        // Should be passed by the caller.
+                        Objects.requireNonNull(policyDefinition);
+                    } else {
+                        policyDefinition = PolicyDefinition.readFromXml(parser);
+                        if (policyDefinition == null) {
+                            Slogf.wtf(TAG, "Error Parsing TAG_POLICY_DEFINITION_ENTRY, "
+                                    + "PolicyDefinition is null");
+                        }
                     }
                     break;
 
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 9e8811f..09c54cb 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -16,6 +16,7 @@
 
 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;
@@ -105,6 +106,7 @@
 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.util.ConcurrentUtils;
 import com.android.internal.util.EmergencyAffordanceManager;
 import com.android.internal.util.FrameworkStatsLog;
@@ -119,6 +121,7 @@
 import com.android.server.ambientcontext.AmbientContextManagerService;
 import com.android.server.app.GameManagerService;
 import com.android.server.appbinding.AppBindingService;
+import com.android.server.appfunctions.AppFunctionManagerService;
 import com.android.server.apphibernation.AppHibernationService;
 import com.android.server.appop.AppOpMigrationHelper;
 import com.android.server.appop.AppOpMigrationHelperImpl;
@@ -1087,6 +1090,13 @@
         SystemServerInitThreadPool.submit(SystemConfig::getInstance, TAG_SYSTEM_CONFIG);
         t.traceEnd();
 
+        // Orchestrates some ProtoLogging functionality.
+        if (android.tracing.Flags.clientSideProtoLogging()) {
+            t.traceBegin("StartProtoLogService");
+            ServiceManager.addService(Context.PROTOLOG_SERVICE, new ProtoLogService());
+            t.traceEnd();
+        }
+
         // Platform compat service is used by ActivityManagerService, PackageManagerService, and
         // possibly others in the future. b/135010838.
         t.traceBegin("PlatformCompat");
@@ -1719,6 +1729,12 @@
             mSystemServiceManager.startService(LogcatManagerService.class);
             t.traceEnd();
 
+            t.traceBegin("StartAppFunctionManager");
+            if (enableAppFunctionManager()) {
+                mSystemServiceManager.startService(AppFunctionManagerService.class);
+            }
+            t.traceEnd();
+
         } catch (Throwable e) {
             Slog.e("System", "******************************************");
             Slog.e("System", "************ Failure starting core service");
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
index 8ae4f9a..6afcae7 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
@@ -45,7 +45,11 @@
 import android.os.Binder;
 import android.os.IBinder;
 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.Display;
+import android.view.inputmethod.Flags;
 import android.view.inputmethod.ImeTracker;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -58,6 +62,7 @@
 import com.android.internal.inputmethod.StartInputReason;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -70,6 +75,9 @@
  */
 @RunWith(AndroidJUnit4.class)
 public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTestBase {
+
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
     private DefaultImeVisibilityApplier mVisibilityApplier;
 
     @Before
@@ -112,6 +120,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
     public void testApplyImeVisibility_showIme() {
         final var statsToken = ImeTracker.Token.empty();
         synchronized (ImfLock.class) {
@@ -122,6 +131,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
     public void testApplyImeVisibility_hideIme() {
         final var statsToken = ImeTracker.Token.empty();
         synchronized (ImfLock.class) {
@@ -141,7 +151,12 @@
             mVisibilityApplier.applyImeVisibility(mWindowToken, ImeTracker.Token.empty(),
                     STATE_HIDE_IME_EXPLICIT, eq(SoftInputShowHideReason.NOT_SET), mUserId);
         }
-        verifyHideSoftInput(true, true);
+        if (Flags.refactorInsetsController()) {
+            verifySetImeVisibility(true /* setVisible */, false /* invoked */);
+            verifySetImeVisibility(false /* setVisible */, true /* invoked */);
+        } else {
+            verifyHideSoftInput(true, true);
+        }
     }
 
     @Test
@@ -153,7 +168,12 @@
             mVisibilityApplier.applyImeVisibility(mWindowToken, ImeTracker.Token.empty(),
                     STATE_HIDE_IME_NOT_ALWAYS, eq(SoftInputShowHideReason.NOT_SET), mUserId);
         }
-        verifyHideSoftInput(true, true);
+        if (Flags.refactorInsetsController()) {
+            verifySetImeVisibility(true /* setVisible */, false /* invoked */);
+            verifySetImeVisibility(false /* setVisible */, true /* invoked */);
+        } else {
+            verifyHideSoftInput(true, true);
+        }
     }
 
     @Test
@@ -162,10 +182,16 @@
             mVisibilityApplier.applyImeVisibility(mWindowToken, ImeTracker.Token.empty(),
                     STATE_SHOW_IME_IMPLICIT, eq(SoftInputShowHideReason.NOT_SET), mUserId);
         }
-        verifyShowSoftInput(true, true, 0 /* showFlags */);
+        if (Flags.refactorInsetsController()) {
+            verifySetImeVisibility(true /* setVisible */, true /* invoked */);
+            verifySetImeVisibility(false /* setVisible */, false /* invoked */);
+        } else {
+            verifyShowSoftInput(true, true, 0 /* showFlags */);
+        }
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
     public void testApplyImeVisibility_hideImeFromTargetOnSecondaryDisplay() {
         // Init a IME target client on the secondary display to show IME.
         mInputMethodManagerService.addClient(mMockInputMethodClient, mMockRemoteInputConnection,
@@ -234,8 +260,10 @@
             verify(mVisibilityApplier).applyImeVisibility(
                     eq(mWindowToken), any(), eq(STATE_HIDE_IME),
                     eq(SoftInputShowHideReason.NOT_SET), eq(mUserId) /* userId */);
-            verify(mInputMethodManagerService.mWindowManagerInternal).hideIme(
-                    eq(mWindowToken), eq(displayIdToShowIme), and(not(eq(statsToken)), notNull()));
+            if (!Flags.refactorInsetsController()) {
+                verify(mInputMethodManagerService.mWindowManagerInternal).hideIme(eq(mWindowToken),
+                        eq(displayIdToShowIme), and(not(eq(statsToken)), notNull()));
+            }
         }
     }
 
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
index dc03732..770451c 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
@@ -20,6 +20,7 @@
 import static com.android.server.inputmethod.InputMethodSubtypeSwitchingController.MODE_RECENT;
 import static com.android.server.inputmethod.InputMethodSubtypeSwitchingController.MODE_STATIC;
 import static com.android.server.inputmethod.InputMethodSubtypeSwitchingController.SwitchMode;
+import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_INDEX;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -43,7 +44,6 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ControllerImpl;
 import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem;
 
 import org.junit.Rule;
@@ -61,7 +61,6 @@
     private static final boolean TEST_IS_VR_IME = false;
     private static final int TEST_IS_DEFAULT_RES_ID = 0;
     private static final String SYSTEM_LOCALE = "en_US";
-    private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID;
 
     @Rule
     public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@@ -103,7 +102,7 @@
                 TEST_FORCE_DEFAULT, supportsSwitchingToNextInputMethod, TEST_IS_VR_IME);
         if (subtypes == null) {
             items.add(new ImeSubtypeListItem(imeName, null /* variableName */, imi,
-                    NOT_A_SUBTYPE_ID, null, SYSTEM_LOCALE));
+                    NOT_A_SUBTYPE_INDEX, null, SYSTEM_LOCALE));
         } else {
             for (int i = 0; i < subtypes.size(); ++i) {
                 final String subtypeLocale = subtypeLocales.get(i);
@@ -178,19 +177,20 @@
         return items;
     }
 
-    private void assertNextInputMethod(@NonNull ControllerImpl controller, boolean onlyCurrentIme,
-            @NonNull ImeSubtypeListItem currentItem, @Nullable ImeSubtypeListItem nextItem) {
+    private void assertNextInputMethod(@NonNull InputMethodSubtypeSwitchingController controller,
+            boolean onlyCurrentIme, @NonNull ImeSubtypeListItem currentItem,
+            @Nullable ImeSubtypeListItem nextItem) {
         InputMethodSubtype subtype = null;
         if (currentItem.mSubtypeName != null) {
             subtype = createTestSubtype(currentItem.mSubtypeName.toString());
         }
-        final ImeSubtypeListItem nextIme = controller.getNextInputMethod(onlyCurrentIme,
+        final ImeSubtypeListItem nextIme = controller.getNextInputMethodLocked(onlyCurrentIme,
                 currentItem.mImi, subtype, MODE_STATIC, true /* forward */);
         assertEquals(nextItem, nextIme);
     }
 
-    private void assertRotationOrder(@NonNull ControllerImpl controller, boolean onlyCurrentIme,
-            ImeSubtypeListItem... expectedRotationOrderOfImeSubtypeList) {
+    private void assertRotationOrder(@NonNull InputMethodSubtypeSwitchingController controller,
+            boolean onlyCurrentIme, ImeSubtypeListItem... expectedRotationOrderOfImeSubtypeList) {
         final int numItems = expectedRotationOrderOfImeSubtypeList.length;
         for (int i = 0; i < numItems; i++) {
             final int nextIndex = (i + 1) % numItems;
@@ -200,7 +200,7 @@
         }
     }
 
-    private boolean onUserAction(@NonNull ControllerImpl controller,
+    private boolean onUserAction(@NonNull InputMethodSubtypeSwitchingController controller,
             @NonNull ImeSubtypeListItem subtypeListItem) {
         InputMethodSubtype subtype = null;
         if (subtypeListItem.mSubtypeName != null) {
@@ -228,8 +228,8 @@
         final ImeSubtypeListItem japaneseIme_ja_jp = enabledItems.get(6);
         final ImeSubtypeListItem switchUnawareJapaneseIme_ja_jp = enabledItems.get(7);
 
-        final ControllerImpl controller = ControllerImpl.createFrom(
-                null /* currentInstance */, enabledItems, new ArrayList<>());
+        final var controller = new InputMethodSubtypeSwitchingController();
+        controller.update(enabledItems, new ArrayList<>());
 
         // switching-aware loop
         assertRotationOrder(controller, false /* onlyCurrentIme */,
@@ -286,8 +286,8 @@
         final ImeSubtypeListItem japaneseIme_ja_jp = enabledItems.get(6);
         final ImeSubtypeListItem switchUnawareJapaneseIme_ja_jp = enabledItems.get(7);
 
-        final ControllerImpl controller = ControllerImpl.createFrom(
-                null /* currentInstance */, enabledItems, new ArrayList<>());
+        final var controller = new InputMethodSubtypeSwitchingController();
+        controller.update(enabledItems, new ArrayList<>());
 
         // === switching-aware loop ===
         assertRotationOrder(controller, false /* onlyCurrentIme */,
@@ -336,11 +336,10 @@
 
         // Rotation order should be preserved when created with the same subtype list.
         final List<ImeSubtypeListItem> sameEnabledItems = createEnabledImeSubtypes();
-        final ControllerImpl newController = ControllerImpl.createFrom(controller,
-                sameEnabledItems, new ArrayList<>());
-        assertRotationOrder(newController, false /* onlyCurrentIme */,
+        controller.update(sameEnabledItems, new ArrayList<>());
+        assertRotationOrder(controller, false /* onlyCurrentIme */,
                 subtypeAwareIme, latinIme_fr, latinIme_en_us, japaneseIme_ja_jp);
-        assertRotationOrder(newController, false /* onlyCurrentIme */,
+        assertRotationOrder(controller, false /* onlyCurrentIme */,
                 switchingUnawareLatinIme_en_uk, switchingUnawareLatinIme_hi, subtypeUnawareIme,
                 switchUnawareJapaneseIme_ja_jp);
 
@@ -348,11 +347,10 @@
         final List<ImeSubtypeListItem> differentEnabledItems = List.of(
                 latinIme_en_us, latinIme_fr, subtypeAwareIme, switchingUnawareLatinIme_en_uk,
                 switchUnawareJapaneseIme_ja_jp, subtypeUnawareIme);
-        final ControllerImpl anotherController = ControllerImpl.createFrom(controller,
-                differentEnabledItems, new ArrayList<>());
-        assertRotationOrder(anotherController, false /* onlyCurrentIme */,
+        controller.update(differentEnabledItems, new ArrayList<>());
+        assertRotationOrder(controller, false /* onlyCurrentIme */,
                 latinIme_en_us, latinIme_fr, subtypeAwareIme);
-        assertRotationOrder(anotherController, false /* onlyCurrentIme */,
+        assertRotationOrder(controller, false /* onlyCurrentIme */,
                 switchingUnawareLatinIme_en_uk, switchUnawareJapaneseIme_ja_jp, subtypeUnawareIme);
     }
 
@@ -520,8 +518,8 @@
         final var hardwareLatinIme = List.of(hardwareEnglish, hardwareFrench, hardwareItalian);
         final var hardwareSimpleIme = List.of(hardwareSimple);
 
-        final var controller = ControllerImpl.createFrom(null /* currentInstance */, items,
-                hardwareItems);
+        final var controller = new InputMethodSubtypeSwitchingController();
+        controller.update(items, hardwareItems);
 
         final int mode = MODE_STATIC;
 
@@ -583,8 +581,8 @@
         final var hardwareLatinIme = List.of(hardwareEnglish, hardwareFrench, hardwareItalian);
         final var hardwareSimpleIme = List.of(hardwareSimple);
 
-        final var controller = ControllerImpl.createFrom(null /* currentInstance */, items,
-                hardwareItems);
+        final var controller = new InputMethodSubtypeSwitchingController();
+        controller.update(items, hardwareItems);
 
         final int mode = MODE_RECENT;
 
@@ -666,8 +664,8 @@
         final var hardwareLatinIme = List.of(hardwareEnglish, hardwareFrench, hardwareItalian);
         final var hardwareSimpleIme = List.of(hardwareSimple);
 
-        final var controller = ControllerImpl.createFrom(null /* currentInstance */, items,
-                hardwareItems);
+        final var controller = new InputMethodSubtypeSwitchingController();
+        controller.update(items, hardwareItems);
 
         final int mode = MODE_AUTO;
 
@@ -777,92 +775,73 @@
         final var hardwareLatinIme = List.of(hardwareEnglish, hardwareFrench, hardwareItalian);
         final var hardwareSimpleIme = List.of(hardwareSimple);
 
-        final var controller = ControllerImpl.createFrom(null /* currentInstance */, items,
-                hardwareItems);
+        final var controller = new InputMethodSubtypeSwitchingController();
+        controller.update(items, hardwareItems);
 
         final int mode = MODE_RECENT;
 
         // Recency order is initialized to static order.
         assertNextOrder(controller, false /* forHardware */, mode,
                 items, List.of(latinIme, simpleIme));
-
         assertNextOrder(controller, true /* forHardware */, mode,
                 hardwareItems, List.of(hardwareLatinIme, hardwareSimpleIme));
 
         // User action on french IME.
         assertTrue("Recency updated for french IME", onUserAction(controller, french));
 
-        final var equalItems = new ArrayList<>(items);
-        final var otherItems = new ArrayList<>(items);
-        otherItems.remove(simple);
-
-        final var equalController = ControllerImpl.createFrom(controller, equalItems,
-                hardwareItems);
-        final var otherController = ControllerImpl.createFrom(controller, otherItems,
-                hardwareItems);
-
         final var recencyItems = List.of(french, english, italian, simple);
         final var recencyLatinIme = List.of(french, english, italian);
         final var recencySimpleIme = List.of(simple);
 
+        final var equalItems = new ArrayList<>(items);
+        controller.update(equalItems, hardwareItems);
+
+        // The order of non-hardware items remains unchanged when updated with equal items.
         assertNextOrder(controller, false /* forHardware */, mode,
                 recencyItems, List.of(recencyLatinIme, recencySimpleIme));
-
-        // The order of equal non-hardware items is unchanged.
-        assertNextOrder(equalController, false /* forHardware */, mode,
-                recencyItems, List.of(recencyLatinIme, recencySimpleIme));
-
-        // The order of other hardware items is reset.
-        assertNextOrder(otherController, false /* forHardware */, mode,
-                latinIme, List.of(latinIme));
-
-        // The order of hardware remains unchanged.
+        // The order of hardware items remains unchanged when only non-hardware items are updated.
         assertNextOrder(controller, true /* forHardware */, mode,
                 hardwareItems, List.of(hardwareLatinIme, hardwareSimpleIme));
 
-        assertNextOrder(equalController, true /* forHardware */, mode,
-                hardwareItems, List.of(hardwareLatinIme, hardwareSimpleIme));
+        final var otherItems = new ArrayList<>(items);
+        otherItems.remove(simple);
+        controller.update(otherItems, hardwareItems);
 
-        assertNextOrder(otherController, true /* forHardware */, mode,
+        // The order of non-hardware items is reset when updated with other items.
+        assertNextOrder(controller, false /* forHardware */, mode,
+                latinIme, List.of(latinIme));
+        // The order of hardware items remains unchanged when only non-hardware items are updated.
+        assertNextOrder(controller, true /* forHardware */, mode,
                 hardwareItems, List.of(hardwareLatinIme, hardwareSimpleIme));
 
         assertTrue("Recency updated for french hardware IME",
                 onUserAction(controller, hardwareFrench));
 
-        final var equalHardwareItems = new ArrayList<>(hardwareItems);
-        final var otherHardwareItems = new ArrayList<>(hardwareItems);
-        otherHardwareItems.remove(hardwareSimple);
-
-        final var equalHardwareController = ControllerImpl.createFrom(controller, items,
-                equalHardwareItems);
-        final var otherHardwareController = ControllerImpl.createFrom(controller, items,
-                otherHardwareItems);
-
         final var recencyHardwareItems =
                 List.of(hardwareFrench, hardwareEnglish, hardwareItalian, hardwareSimple);
         final var recencyHardwareLatinIme =
                 List.of(hardwareFrench, hardwareEnglish, hardwareItalian);
         final var recencyHardwareSimpleIme = List.of(hardwareSimple);
 
-        // The order of non-hardware items remains unchanged.
+        final var equalHardwareItems = new ArrayList<>(hardwareItems);
+        controller.update(otherItems, equalHardwareItems);
+
+        // The order of non-hardware items remains unchanged when only hardware items are updated.
         assertNextOrder(controller, false /* forHardware */, mode,
-                recencyItems, List.of(recencyLatinIme, recencySimpleIme));
-
-        assertNextOrder(equalHardwareController, false /* forHardware */, mode,
-                recencyItems, List.of(recencyLatinIme, recencySimpleIme));
-
-        assertNextOrder(otherHardwareController, false /* forHardware */, mode,
-                recencyItems, List.of(recencyLatinIme, recencySimpleIme));
-
+                latinIme, List.of(latinIme));
+        // The order of hardware items remains unchanged when updated with equal items.
         assertNextOrder(controller, true /* forHardware */, mode,
                 recencyHardwareItems, List.of(recencyHardwareLatinIme, recencyHardwareSimpleIme));
 
-        // The order of equal hardware items is unchanged.
-        assertNextOrder(equalHardwareController, true /* forHardware */, mode,
-                recencyHardwareItems, List.of(recencyHardwareLatinIme, recencyHardwareSimpleIme));
+        final var otherHardwareItems = new ArrayList<>(hardwareItems);
+        otherHardwareItems.remove(hardwareSimple);
+        controller.update(otherItems, otherHardwareItems);
 
-        // The order of other hardware items is reset.
-        assertNextOrder(otherHardwareController, true /* forHardware */, mode,
+        // The order of non-hardware items remains unchanged when only hardware items are updated.
+        assertNextOrder(controller, false /* forHardware */, mode,
+                latinIme, List.of(latinIme));
+        // The order of hardware items is reset when updated with other items.
+        assertNextOrder(controller, true /* forHardware */, mode,
                 hardwareLatinIme, List.of(hardwareLatinIme));
     }
 
@@ -882,8 +861,8 @@
         addTestImeSubtypeListItems(hardwareItems, "hardwareSwitchUnaware", "hardwareSwitchUnaware",
                 null, false /* supportsSwitchingToNextInputMethod*/);
 
-        final var controller = ControllerImpl.createFrom(null /* currentInstance */, items,
-                hardwareItems);
+        final var controller = new InputMethodSubtypeSwitchingController();
+        controller.update(items, hardwareItems);
 
         for (int mode = MODE_STATIC; mode <= MODE_AUTO; mode++) {
             assertNextOrder(controller, false /* forHardware */, false /* onlyCurrentIme */,
@@ -910,55 +889,102 @@
         addTestImeSubtypeListItems(hardwareItems, "HardwareIme", "HardwareIme",
                 List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */);
 
-        final var controller = ControllerImpl.createFrom(null /* currentInstance */, List.of(),
-                List.of());
+        final var controller = new InputMethodSubtypeSwitchingController();
 
-        assertNoAction(controller, false /* forHardware */, items);
-        assertNoAction(controller, true /* forHardware */, hardwareItems);
+        assertNextItemNoAction(controller, false /* forHardware */, items,
+                null /* expectedNext */);
+        assertNextItemNoAction(controller, true /* forHardware */, hardwareItems,
+                null /* expectedNext */);
     }
 
-    /** Verifies that a controller with a single item can't take any actions. */
+    /**
+     * Verifies that a controller with a single item can't update the recency, and cannot switch
+     * away from the item, but allows switching from unknown items to the single item.
+     */
     @RequiresFlagsEnabled(Flags.FLAG_IME_SWITCHER_REVAMP)
     @Test
     public void testSingleItemList() {
         final var items = new ArrayList<ImeSubtypeListItem>();
         addTestImeSubtypeListItems(items, "LatinIme", "LatinIme",
-                List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */);
+                null, true /* supportsSwitchingToNextInputMethod */);
+        final var unknownItems = new ArrayList<ImeSubtypeListItem>();
+        addTestImeSubtypeListItems(unknownItems, "UnknownIme", "UnknownIme",
+                List.of("en", "fr", "it"), true /* supportsSwitchingToNextInputMethod */);
 
         final var hardwareItems = new ArrayList<ImeSubtypeListItem>();
         addTestImeSubtypeListItems(hardwareItems, "HardwareIme", "HardwareIme",
-                List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */);
+                null, true /* supportsSwitchingToNextInputMethod */);
+        final var unknownHardwareItems = new ArrayList<ImeSubtypeListItem>();
+        addTestImeSubtypeListItems(unknownHardwareItems, "HardwareUnknownIme", "HardwareUnknownIme",
+                List.of("en", "fr", "it"), true /* supportsSwitchingToNextInputMethod */);
 
-        final var controller = ControllerImpl.createFrom(null /* currentInstance */,
-                List.of(items.get(0)), List.of(hardwareItems.get(0)));
+        final var controller = new InputMethodSubtypeSwitchingController();
+        controller.update(items, hardwareItems);
 
-        assertNoAction(controller, false /* forHardware */, items);
-        assertNoAction(controller, true /* forHardware */, hardwareItems);
+        assertNextItemNoAction(controller, false /* forHardware */, items,
+                null /* expectedNext */);
+        assertNextItemNoAction(controller, false /* forHardware */, unknownItems,
+                items.get(0));
+        assertNextItemNoAction(controller, true /* forHardware */, hardwareItems,
+                null /* expectedNext */);
+        assertNextItemNoAction(controller, true /* forHardware */, unknownHardwareItems,
+                hardwareItems.get(0));
     }
 
-    /** Verifies that a controller can't take any actions for unknown items. */
+    /**
+     * Verifies that the recency cannot be updated for unknown items, but switching from unknown
+     * items reaches the most recent known item.
+     */
     @RequiresFlagsEnabled(Flags.FLAG_IME_SWITCHER_REVAMP)
     @Test
     public void testUnknownItems() {
         final var items = new ArrayList<ImeSubtypeListItem>();
         addTestImeSubtypeListItems(items, "LatinIme", "LatinIme",
-                List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */);
+                List.of("en", "fr", "it"), true /* supportsSwitchingToNextInputMethod */);
+
+        final var english = items.get(0);
+        final var french = items.get(1);
+        final var italian = items.get(2);
+
         final var unknownItems = new ArrayList<ImeSubtypeListItem>();
         addTestImeSubtypeListItems(unknownItems, "UnknownIme", "UnknownIme",
-                List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */);
+                List.of("en", "fr", "it"), true /* supportsSwitchingToNextInputMethod */);
 
         final var hardwareItems = new ArrayList<ImeSubtypeListItem>();
         addTestImeSubtypeListItems(hardwareItems, "HardwareIme", "HardwareIme",
-                List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */);
+                List.of("en", "fr", "it"), true /* supportsSwitchingToNextInputMethod */);
         final var unknownHardwareItems = new ArrayList<ImeSubtypeListItem>();
         addTestImeSubtypeListItems(unknownHardwareItems, "HardwareUnknownIme", "HardwareUnknownIme",
-                List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */);
+                List.of("en", "fr", "it"), true /* supportsSwitchingToNextInputMethod */);
 
-        final var controller = ControllerImpl.createFrom(null /* currentInstance */, items,
-                hardwareItems);
+        final var controller = new InputMethodSubtypeSwitchingController();
+        controller.update(items, hardwareItems);
 
-        assertNoAction(controller, false /* forHardware */, unknownItems);
-        assertNoAction(controller, true /* forHardware */, unknownHardwareItems);
+        assertTrue("Recency updated for french IME", onUserAction(controller, french));
+
+        final var recencyItems = List.of(french, english, italian);
+
+        assertNextItemNoAction(controller, false /* forHardware */, unknownItems,
+                french);
+        assertNextItemNoAction(controller, true /* forHardware */, unknownHardwareItems,
+                hardwareItems.get(0));
+
+        // Known items must not be able to switch to unknown items.
+        assertNextOrder(controller, false /* forHardware */, MODE_STATIC, items,
+                List.of(items));
+        assertNextOrder(controller, false /* forHardware */, MODE_RECENT, recencyItems,
+                List.of(recencyItems));
+        assertNextOrder(controller, false /* forHardware */, MODE_AUTO, true /* forward */,
+                recencyItems, List.of(recencyItems));
+        assertNextOrder(controller, false /* forHardware */, MODE_AUTO, false /* forward */,
+                items.reversed(), List.of(items.reversed()));
+
+        assertNextOrder(controller, true /* forHardware */, MODE_STATIC, hardwareItems,
+                List.of(hardwareItems));
+        assertNextOrder(controller, true /* forHardware */, MODE_RECENT, hardwareItems,
+                List.of(hardwareItems));
+        assertNextOrder(controller, true /* forHardware */, MODE_AUTO, hardwareItems,
+                List.of(hardwareItems));
     }
 
     /** Verifies that the IME name does influence the comparison order. */
@@ -1070,8 +1096,9 @@
      * @param allItems    the list of items across all IMEs.
      * @param perImeItems the list of lists of items per IME.
      */
-    private static void assertNextOrder(@NonNull ControllerImpl controller, boolean forHardware,
-            @SwitchMode int mode, boolean forward, @NonNull List<ImeSubtypeListItem> allItems,
+    private static void assertNextOrder(@NonNull InputMethodSubtypeSwitchingController controller,
+            boolean forHardware, @SwitchMode int mode, boolean forward,
+            @NonNull List<ImeSubtypeListItem> allItems,
             @NonNull List<List<ImeSubtypeListItem>> perImeItems) {
         assertNextOrder(controller, forHardware, false /* onlyCurrentIme */, mode,
                 forward, allItems);
@@ -1094,8 +1121,8 @@
      * @param allItems    the list of items across all IMEs.
      * @param perImeItems the list of lists of items per IME.
      */
-    private static void assertNextOrder(@NonNull ControllerImpl controller, boolean forHardware,
-            @SwitchMode int mode, @NonNull List<ImeSubtypeListItem> allItems,
+    private static void assertNextOrder(@NonNull InputMethodSubtypeSwitchingController controller,
+            boolean forHardware, @SwitchMode int mode, @NonNull List<ImeSubtypeListItem> allItems,
             @NonNull List<List<ImeSubtypeListItem>> perImeItems) {
         assertNextOrder(controller, forHardware, false /* onlyCurrentIme */, mode,
                 true /* forward */, allItems);
@@ -1122,7 +1149,7 @@
      * @param forward        whether to search forwards or backwards in the list.
      * @param items          the list of items to verify, in the expected order.
      */
-    private static void assertNextOrder(@NonNull ControllerImpl controller,
+    private static void assertNextOrder(@NonNull InputMethodSubtypeSwitchingController controller,
             boolean forHardware, boolean onlyCurrentIme, @SwitchMode int mode, boolean forward,
             @NonNull List<ImeSubtypeListItem> items) {
         final int numItems = items.size();
@@ -1166,7 +1193,7 @@
      * @param item           the item to find the next value from.
      * @param expectedNext   the expected next value.
      */
-    private static void assertNextItem(@NonNull ControllerImpl controller,
+    private static void assertNextItem(@NonNull InputMethodSubtypeSwitchingController controller,
             boolean forHardware, boolean onlyCurrentIme, @SwitchMode int mode, boolean forward,
             @NonNull ImeSubtypeListItem item, @Nullable ImeSubtypeListItem expectedNext) {
         final var nextItem = getNextItem(controller, forHardware, onlyCurrentIme, mode, forward,
@@ -1186,38 +1213,41 @@
      * @return the next item found, otherwise {@code null}.
      */
     @Nullable
-    private static ImeSubtypeListItem getNextItem(@NonNull ControllerImpl controller,
-            boolean forHardware, boolean onlyCurrentIme, @SwitchMode int mode, boolean forward,
+    private static ImeSubtypeListItem getNextItem(
+            @NonNull InputMethodSubtypeSwitchingController controller, boolean forHardware,
+            boolean onlyCurrentIme, @SwitchMode int mode, boolean forward,
             @NonNull ImeSubtypeListItem item) {
         final var subtype = item.mSubtypeName != null
                 ? createTestSubtype(item.mSubtypeName.toString()) : null;
         return forHardware
                 ? controller.getNextInputMethodForHardware(
                         onlyCurrentIme, item.mImi, subtype, mode, forward)
-                : controller.getNextInputMethod(
+                : controller.getNextInputMethodLocked(
                         onlyCurrentIme, item.mImi, subtype, mode, forward);
     }
 
     /**
-     * Verifies that no next items can be found, and the recency cannot be updated for the
+     * Verifies that the expected next item is returned, and the recency cannot be updated for the
      * given items.
      *
-     * @param controller  the controller to verify the items on.
-     * @param forHardware whether to try finding the next hardware item, or software item.
-     * @param items       the list of items to verify.
+     * @param controller   the controller to verify the items on.
+     * @param forHardware  whether to try finding the next hardware item, or software item.
+     * @param items        the list of items to verify.
+     * @param expectedNext the expected next item.
      */
-    private void assertNoAction(@NonNull ControllerImpl controller, boolean forHardware,
-            @NonNull List<ImeSubtypeListItem> items) {
+    private void assertNextItemNoAction(@NonNull InputMethodSubtypeSwitchingController controller,
+            boolean forHardware, @NonNull List<ImeSubtypeListItem> items,
+            @Nullable ImeSubtypeListItem expectedNext) {
         for (var item : items) {
             for (int mode = MODE_STATIC; mode <= MODE_AUTO; mode++) {
                 assertNextItem(controller, forHardware, false /* onlyCurrentIme */, mode,
-                        false /* forward */, item, null /* expectedNext */);
+                        false /* forward */, item, expectedNext);
                 assertNextItem(controller, forHardware, false /* onlyCurrentIme */, mode,
-                        true /* forward */, item, null /* expectedNext */);
+                        true /* forward */, item, expectedNext);
                 assertNextItem(controller, forHardware, true /* onlyCurrentIme */, mode,
-                        false /* forward */, item, null /* expectedNext */);
+                        false /* forward */, item, expectedNext);
                 assertNextItem(controller, forHardware, true /* onlyCurrentIme */, mode,
-                        true /* forward */, item, null /* expectedNext */);
+                        true /* forward */, item, expectedNext);
             }
 
             assertFalse("User action shouldn't have updated the recency.",
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
index d7af443..c272430 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -924,6 +924,54 @@
     }
 
     @Test
+    public void testSameVersions_writeReadUsesStaticLibraries() {
+        Settings settings = makeSettings();
+        PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1);
+        packageSetting.setAppId(Process.FIRST_APPLICATION_UID);
+
+        final String libOne = "one";
+        final String libTwo = "two";
+        final long versionOne = 311;
+        packageSetting.setUsesStaticLibraries(new String[] { libOne, libTwo });
+        packageSetting.setUsesStaticLibrariesVersions(new long[] { versionOne, versionOne });
+        settings.mPackages.put(PACKAGE_NAME_1, packageSetting);
+
+        settings.writeLPr(computer, /* sync= */ true);
+        settings.mPackages.clear();
+
+        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+        PackageSetting resultSetting = settings.getPackageLPr(PACKAGE_NAME_1);
+        assertThat(resultSetting.getUsesStaticLibraries()[0], is(libOne));
+        assertThat(resultSetting.getUsesStaticLibraries()[1], is(libTwo));
+        assertThat(resultSetting.getUsesStaticLibrariesVersions()[0], is(versionOne));
+        assertThat(resultSetting.getUsesStaticLibrariesVersions()[1], is(versionOne));
+    }
+
+    @Test
+    public void testSameLibNames_writeReadUsesStaticLibraries() {
+        Settings settings = makeSettings();
+        PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1);
+        packageSetting.setAppId(Process.FIRST_APPLICATION_UID);
+
+        final String libOne = "one";
+        final long versionOne = 311;
+        final long versionTwo = 330;
+        packageSetting.setUsesStaticLibraries(new String[] { libOne, libOne});
+        packageSetting.setUsesStaticLibrariesVersions(new long[] { versionOne, versionTwo });
+        settings.mPackages.put(PACKAGE_NAME_1, packageSetting);
+
+        settings.writeLPr(computer, /* sync= */ true);
+        settings.mPackages.clear();
+
+        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+        PackageSetting resultSetting = settings.getPackageLPr(PACKAGE_NAME_1);
+        assertThat(resultSetting.getUsesStaticLibraries().length, is(1));
+        assertThat(resultSetting.getUsesStaticLibrariesVersions().length, is(1));
+        assertThat(resultSetting.getUsesStaticLibraries()[0], is(libOne));
+        assertThat(resultSetting.getUsesStaticLibrariesVersions()[0], is(versionTwo));
+    }
+
+    @Test
     public void testWriteReadUsesSdkLibraries() {
         final Settings settingsUnderTest = makeSettings();
         final PackageSetting ps1 = createPackageSetting(PACKAGE_NAME_1);
@@ -1008,6 +1056,65 @@
     }
 
     @Test
+    public void testSameVersions_writeReadUsesSdkLibraries() {
+        Settings settings = makeSettings();
+        PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1);
+        packageSetting.setAppId(Process.FIRST_APPLICATION_UID);
+
+        final String libOne = "one";
+        final String libTwo = "two";
+        final long versionOne = 311;
+        final boolean optional = false;
+        packageSetting.setUsesSdkLibraries(new String[] { libOne, libTwo });
+        packageSetting.setUsesSdkLibrariesVersionsMajor(new long[] { versionOne, versionOne });
+        packageSetting.setUsesSdkLibrariesOptional(new boolean[] { optional, optional });
+        settings.mPackages.put(PACKAGE_NAME_1, packageSetting);
+
+        settings.writeLPr(computer, /* sync= */ true);
+        settings.mPackages.clear();
+
+        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+        PackageSetting resultSetting = settings.getPackageLPr(PACKAGE_NAME_1);
+
+        assertThat(resultSetting.getUsesSdkLibraries()[0], is(libOne));
+        assertThat(resultSetting.getUsesSdkLibraries()[1], is(libTwo));
+        assertThat(resultSetting.getUsesSdkLibrariesVersionsMajor()[0], is(versionOne));
+        assertThat(resultSetting.getUsesSdkLibrariesVersionsMajor()[1], is(versionOne));
+        assertThat(resultSetting.getUsesSdkLibrariesOptional()[0], is(optional));
+        assertThat(resultSetting.getUsesSdkLibrariesOptional()[1], is(optional));
+    }
+
+    @Test
+    public void testSameLibNames_writeReadUsesSdkLibraries() {
+        Settings settings = makeSettings();
+        PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1);
+        packageSetting.setAppId(Process.FIRST_APPLICATION_UID);
+
+        final String libOne = "one";
+        final long versionOne = 311;
+        final long versionTwo = 330;
+        final boolean optionalOne = false;
+        final boolean optionalTwo = true;
+        packageSetting.setUsesSdkLibraries(new String[] { libOne, libOne });
+        packageSetting.setUsesSdkLibrariesVersionsMajor(new long[] { versionOne, versionTwo });
+        packageSetting.setUsesSdkLibrariesOptional(new boolean[] { optionalOne, optionalTwo });
+        settings.mPackages.put(PACKAGE_NAME_1, packageSetting);
+
+        settings.writeLPr(computer, /* sync= */ true);
+        settings.mPackages.clear();
+
+        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+        PackageSetting resultSetting = settings.getPackageLPr(PACKAGE_NAME_1);
+
+        assertThat(resultSetting.getUsesSdkLibraries().length, is(1));
+        assertThat(resultSetting.getUsesSdkLibrariesVersionsMajor().length, is(1));
+        assertThat(resultSetting.getUsesSdkLibrariesOptional().length, is(1));
+        assertThat(resultSetting.getUsesSdkLibraries()[0], is(libOne));
+        assertThat(resultSetting.getUsesSdkLibrariesVersionsMajor()[0], is(versionTwo));
+        assertThat(resultSetting.getUsesSdkLibrariesOptional()[0], is(optionalTwo));
+    }
+
+    @Test
     public void testWriteReadPendingRestore() {
         Settings settings = makeSettings();
         PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1);
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index b980ca0..30de0e8 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -512,6 +512,9 @@
         when(mPermissionManagerInternal.getAppOpPermissionPackages(
                 SCHEDULE_EXACT_ALARM)).thenReturn(EmptyArray.STRING);
 
+        // Initialize timestamps with arbitrary values of time
+        mNowElapsedTest = 12;
+        mNowRtcTest = 345;
         mInjector = new Injector(mMockContext);
         mService = new AlarmManagerService(mMockContext, mInjector);
         spyOn(mService);
@@ -774,6 +777,61 @@
     }
 
     @Test
+    public void timeChangeBroadcastForward() throws Exception {
+        final long timeDelta = 12345;
+        // AlarmManagerService sends the broadcast if real time clock proceeds 1000ms more than boot
+        // time clock.
+        mNowRtcTest += timeDelta + 1001;
+        mNowElapsedTest += timeDelta;
+        mTestTimer.expire(TIME_CHANGED_MASK);
+
+        verify(mMockContext)
+                .sendBroadcastAsUser(
+                        argThat((intent) -> intent.getAction() == Intent.ACTION_TIME_CHANGED),
+                        eq(UserHandle.ALL),
+                        isNull(),
+                        any());
+    }
+
+    @Test
+    public void timeChangeBroadcastBackward() throws Exception {
+        final long timeDelta = 12345;
+        // AlarmManagerService sends the broadcast if real time clock proceeds 1000ms less than boot
+        // time clock.
+        mNowRtcTest += timeDelta - 1001;
+        mNowElapsedTest += timeDelta;
+        mTestTimer.expire(TIME_CHANGED_MASK);
+
+        verify(mMockContext)
+                .sendBroadcastAsUser(
+                        argThat((intent) -> intent.getAction() == Intent.ACTION_TIME_CHANGED),
+                        eq(UserHandle.ALL),
+                        isNull(),
+                        any());
+    }
+
+    @Test
+    public void timeChangeFilterMinorAdjustment() throws Exception {
+        final long timeDelta = 12345;
+        // AlarmManagerService does not send the broadcast if real time clock proceeds within 1000ms
+        // than boot time clock.
+        mNowRtcTest += timeDelta + 1000;
+        mNowElapsedTest += timeDelta;
+        mTestTimer.expire(TIME_CHANGED_MASK);
+
+        mNowRtcTest += timeDelta - 1000;
+        mNowElapsedTest += timeDelta;
+        mTestTimer.expire(TIME_CHANGED_MASK);
+
+        verify(mMockContext, never())
+                .sendBroadcastAsUser(
+                        argThat((intent) -> intent.getAction() == Intent.ACTION_TIME_CHANGED),
+                        any(),
+                        any(),
+                        any());
+    }
+
+    @Test
     public void testSingleAlarmExpiration() throws Exception {
         final long triggerTime = mNowElapsedTest + 5000;
         final PendingIntent alarmPi = getNewMockPendingIntent();
diff --git a/services/tests/mockingservicestests/src/com/android/server/utils/quota/CountQuotaTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/utils/quota/CountQuotaTrackerTest.java
index 0d14c9f..1b9f8d1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/utils/quota/CountQuotaTrackerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/utils/quota/CountQuotaTrackerTest.java
@@ -40,6 +40,8 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.AlarmManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -224,6 +226,45 @@
         }
     }
 
+    private LongArrayQueue getEvents(int userId, String packageName, String tag) {
+        synchronized (mQuotaTracker.mLock) {
+            return mQuotaTracker.getEvents(userId, packageName, tag);
+        }
+    }
+
+    private void updateExecutionStats(final int userId, @NonNull final String packageName,
+            @Nullable final String tag, @NonNull ExecutionStats stats) {
+        synchronized (mQuotaTracker.mLock) {
+            mQuotaTracker.updateExecutionStatsLocked(userId, packageName, tag, stats);
+        }
+    }
+
+    private ExecutionStats getExecutionStats(final int userId, @NonNull final String packageName,
+            @Nullable final String tag) {
+        synchronized (mQuotaTracker.mLock) {
+            return mQuotaTracker.getExecutionStatsLocked(userId, packageName, tag);
+        }
+    }
+
+    private void maybeScheduleStartAlarm(final int userId, @NonNull final String packageName,
+            @Nullable final String tag) {
+        synchronized (mQuotaTracker.mLock) {
+            mQuotaTracker.maybeScheduleStartAlarmLocked(userId, packageName, tag);
+        }
+    }
+
+    private void maybeScheduleCleanupAlarm() {
+        synchronized (mQuotaTracker.mLock) {
+            mQuotaTracker.maybeScheduleCleanupAlarmLocked();
+        }
+    }
+
+    private void deleteObsoleteEvents() {
+        synchronized (mQuotaTracker.mLock) {
+            mQuotaTracker.deleteObsoleteEventsLocked();
+        }
+    }
+
     @Test
     public void testDeleteObsoleteEventsLocked() {
         // Count window size should only apply to event list.
@@ -243,9 +284,9 @@
         expectedEvents.addLast(now - HOUR_IN_MILLIS);
         expectedEvents.addLast(now - 1);
 
-        mQuotaTracker.deleteObsoleteEventsLocked();
+        deleteObsoleteEvents();
 
-        LongArrayQueue remainingEvents = mQuotaTracker.getEvents(TEST_USER_ID, TEST_PACKAGE,
+        LongArrayQueue remainingEvents = getEvents(TEST_USER_ID, TEST_PACKAGE,
                 TEST_TAG);
         assertTrue(longArrayQueueEquals(expectedEvents, remainingEvents));
     }
@@ -270,15 +311,15 @@
         removal.putExtra(Intent.EXTRA_UID, TEST_UID);
         mReceiver.onReceive(mContext, removal);
         assertNull(
-                mQuotaTracker.getEvents(TEST_USER_ID, "com.android.test.remove", "tag1"));
+                getEvents(TEST_USER_ID, "com.android.test.remove", "tag1"));
         assertNull(
-                mQuotaTracker.getEvents(TEST_USER_ID, "com.android.test.remove", "tag2"));
+                getEvents(TEST_USER_ID, "com.android.test.remove", "tag2"));
         assertNull(
-                mQuotaTracker.getEvents(TEST_USER_ID, "com.android.test.remove", "tag3"));
+                getEvents(TEST_USER_ID, "com.android.test.remove", "tag3"));
         assertTrue(longArrayQueueEquals(expected1,
-                mQuotaTracker.getEvents(TEST_USER_ID, "com.android.test.stay", "tag1")));
+                getEvents(TEST_USER_ID, "com.android.test.stay", "tag1")));
         assertTrue(longArrayQueueEquals(expected2,
-                mQuotaTracker.getEvents(TEST_USER_ID, "com.android.test.stay", "tag2")));
+                getEvents(TEST_USER_ID, "com.android.test.stay", "tag2")));
     }
 
     @Test
@@ -298,10 +339,10 @@
         Intent removal = new Intent(Intent.ACTION_USER_REMOVED);
         removal.putExtra(Intent.EXTRA_USER_HANDLE, TEST_USER_ID);
         mReceiver.onReceive(mContext, removal);
-        assertNull(mQuotaTracker.getEvents(TEST_USER_ID, TEST_PACKAGE, "tag1"));
-        assertNull(mQuotaTracker.getEvents(TEST_USER_ID, TEST_PACKAGE, "tag2"));
-        assertNull(mQuotaTracker.getEvents(TEST_USER_ID, TEST_PACKAGE, "tag3"));
-        longArrayQueueEquals(expected, mQuotaTracker.getEvents(10, TEST_PACKAGE, "tag4"));
+        assertNull(getEvents(TEST_USER_ID, TEST_PACKAGE, "tag1"));
+        assertNull(getEvents(TEST_USER_ID, TEST_PACKAGE, "tag2"));
+        assertNull(getEvents(TEST_USER_ID, TEST_PACKAGE, "tag3"));
+        longArrayQueueEquals(expected, getEvents(10, TEST_PACKAGE, "tag4"));
     }
 
     @Test
@@ -323,7 +364,7 @@
         inputStats.countLimit = expectedStats.countLimit = 3;
         // Invalid time is now +24 hours since there are no sessions at all for the app.
         expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS;
-        mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, "com.android.test.not.run", TEST_TAG,
+        updateExecutionStats(TEST_USER_ID, "com.android.test.not.run", TEST_TAG,
                 inputStats);
         assertEquals(expectedStats, inputStats);
 
@@ -333,19 +374,19 @@
         // Invalid time is now since there was an event exactly windowSizeMs ago.
         expectedStats.expirationTimeElapsed = now;
         expectedStats.countInWindow = 1;
-        mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats);
+        updateExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats);
         assertEquals(expectedStats, inputStats);
 
         inputStats.windowSizeMs = expectedStats.windowSizeMs = 3 * MINUTE_IN_MILLIS;
         expectedStats.expirationTimeElapsed = now + 2 * MINUTE_IN_MILLIS;
         expectedStats.countInWindow = 1;
-        mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats);
+        updateExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats);
         assertEquals(expectedStats, inputStats);
 
         inputStats.windowSizeMs = expectedStats.windowSizeMs = 4 * MINUTE_IN_MILLIS;
         expectedStats.expirationTimeElapsed = now + 3 * MINUTE_IN_MILLIS;
         expectedStats.countInWindow = 1;
-        mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats);
+        updateExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats);
         assertEquals(expectedStats, inputStats);
 
         inputStats.windowSizeMs = expectedStats.windowSizeMs = 49 * MINUTE_IN_MILLIS;
@@ -353,13 +394,13 @@
         // minutes.
         expectedStats.expirationTimeElapsed = now + 44 * MINUTE_IN_MILLIS;
         expectedStats.countInWindow = 2;
-        mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats);
+        updateExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats);
         assertEquals(expectedStats, inputStats);
 
         inputStats.windowSizeMs = expectedStats.windowSizeMs = 50 * MINUTE_IN_MILLIS;
         expectedStats.expirationTimeElapsed = now + 45 * MINUTE_IN_MILLIS;
         expectedStats.countInWindow = 2;
-        mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats);
+        updateExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats);
         assertEquals(expectedStats, inputStats);
 
         inputStats.windowSizeMs = expectedStats.windowSizeMs = HOUR_IN_MILLIS;
@@ -370,28 +411,28 @@
         // App is at event count limit but the oldest session is at the edge of the window, so
         // in quota time is now.
         expectedStats.inQuotaTimeElapsed = now;
-        mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats);
+        updateExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats);
         assertEquals(expectedStats, inputStats);
 
         inputStats.windowSizeMs = expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
         expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
         expectedStats.countInWindow = 3;
         expectedStats.inQuotaTimeElapsed = now + HOUR_IN_MILLIS;
-        mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats);
+        updateExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats);
         assertEquals(expectedStats, inputStats);
 
         inputStats.windowSizeMs = expectedStats.windowSizeMs = 5 * HOUR_IN_MILLIS;
         expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
         expectedStats.countInWindow = 4;
         expectedStats.inQuotaTimeElapsed = now + 4 * HOUR_IN_MILLIS;
-        mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats);
+        updateExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats);
         assertEquals(expectedStats, inputStats);
 
         inputStats.windowSizeMs = expectedStats.windowSizeMs = 6 * HOUR_IN_MILLIS;
         expectedStats.expirationTimeElapsed = now + 2 * HOUR_IN_MILLIS;
         expectedStats.countInWindow = 4;
         expectedStats.inQuotaTimeElapsed = now + 5 * HOUR_IN_MILLIS;
-        mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats);
+        updateExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats);
         assertEquals(expectedStats, inputStats);
     }
 
@@ -428,7 +469,7 @@
         expectedStats.countInWindow = 1;
         mCategorizer.mCategoryToUse = ACTIVE_BUCKET_CATEGORY;
         assertEquals(expectedStats,
-                mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
+                getExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
 
         // Working
         expectedStats.expirationTimeElapsed = now;
@@ -437,7 +478,7 @@
         expectedStats.countInWindow = 2;
         mCategorizer.mCategoryToUse = WORKING_SET_BUCKET_CATEGORY;
         assertEquals(expectedStats,
-                mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
+                getExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
 
         // Frequent
         expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
@@ -447,7 +488,7 @@
         expectedStats.inQuotaTimeElapsed = now + HOUR_IN_MILLIS;
         mCategorizer.mCategoryToUse = FREQUENT_BUCKET_CATEGORY;
         assertEquals(expectedStats,
-                mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
+                getExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
 
         // Rare
         expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
@@ -457,7 +498,7 @@
         expectedStats.inQuotaTimeElapsed = now + 19 * HOUR_IN_MILLIS;
         mCategorizer.mCategoryToUse = RARE_BUCKET_CATEGORY;
         assertEquals(expectedStats,
-                mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
+                getExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
     }
 
     /**
@@ -481,7 +522,7 @@
         expectedStats.countInWindow = 3;
         expectedStats.expirationTimeElapsed = 2 * HOUR_IN_MILLIS + 30_000;
         assertEquals(expectedStats,
-                mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
+                getExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
     }
 
     @Test
@@ -556,20 +597,20 @@
         mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 5, 24 * HOUR_IN_MILLIS);
 
         // No sessions saved yet.
-        mQuotaTracker.maybeScheduleCleanupAlarmLocked();
+        maybeScheduleCleanupAlarm();
         verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_CLEANUP), any(), any());
 
         // Test with only one timing session saved.
         final long now = mInjector.getElapsedRealtime();
         logEventAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 6 * HOUR_IN_MILLIS);
-        mQuotaTracker.maybeScheduleCleanupAlarmLocked();
+        maybeScheduleCleanupAlarm();
         verify(mAlarmManager, timeout(1000).times(1))
                 .set(anyInt(), eq(now + 18 * HOUR_IN_MILLIS), eq(TAG_CLEANUP), any(), any());
 
         // Test with new (more recent) timing sessions saved. AlarmManger shouldn't be called again.
         logEventAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 3 * HOUR_IN_MILLIS);
         logEventAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - HOUR_IN_MILLIS);
-        mQuotaTracker.maybeScheduleCleanupAlarmLocked();
+        maybeScheduleCleanupAlarm();
         verify(mAlarmManager, times(1))
                 .set(anyInt(), eq(now + 18 * HOUR_IN_MILLIS), eq(TAG_CLEANUP), any(), any());
     }
@@ -587,14 +628,14 @@
         mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 8 * HOUR_IN_MILLIS);
 
         // No sessions saved yet.
-        mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+        maybeScheduleStartAlarm(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
         verify(mAlarmManager, never()).setWindow(
                 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         // Test with timing sessions out of window.
         final long now = mInjector.getElapsedRealtime();
         logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 10 * HOUR_IN_MILLIS, 20);
-        mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+        maybeScheduleStartAlarm(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
         verify(mAlarmManager, never()).setWindow(
                 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
@@ -602,26 +643,26 @@
         final long start = now - (6 * HOUR_IN_MILLIS);
         final long expectedAlarmTime = start + 8 * HOUR_IN_MILLIS;
         logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, start, 5);
-        mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+        maybeScheduleStartAlarm(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
         verify(mAlarmManager, never()).setWindow(
                 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         // Add some more sessions, but still in quota.
         logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 3 * HOUR_IN_MILLIS, 1);
         logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - HOUR_IN_MILLIS, 3);
-        mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+        maybeScheduleStartAlarm(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
         verify(mAlarmManager, never()).setWindow(
                 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         // Test when out of quota.
         logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - HOUR_IN_MILLIS, 1);
-        mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+        maybeScheduleStartAlarm(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
         verify(mAlarmManager, timeout(1000).times(1)).setWindow(
                 anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(),
                 any(Handler.class));
 
         // Alarm already scheduled, so make sure it's not scheduled again.
-        mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+        maybeScheduleStartAlarm(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
         verify(mAlarmManager, times(1)).setWindow(
                 anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(),
                 any(Handler.class));
@@ -656,7 +697,7 @@
 
         // Start in ACTIVE bucket.
         mCategorizer.mCategoryToUse = ACTIVE_BUCKET_CATEGORY;
-        mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+        maybeScheduleStartAlarm(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
         inOrder.verify(mAlarmManager, never())
                 .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(),
                         any(Handler.class));
@@ -665,40 +706,40 @@
         // And down from there.
         final long expectedWorkingAlarmTime = outOfQuotaTime + (2 * HOUR_IN_MILLIS);
         mCategorizer.mCategoryToUse = WORKING_SET_BUCKET_CATEGORY;
-        mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+        maybeScheduleStartAlarm(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
         inOrder.verify(mAlarmManager, timeout(1000).times(1))
                 .setWindow(anyInt(), eq(expectedWorkingAlarmTime), anyLong(),
                         eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         final long expectedFrequentAlarmTime = outOfQuotaTime + (8 * HOUR_IN_MILLIS);
         mCategorizer.mCategoryToUse = FREQUENT_BUCKET_CATEGORY;
-        mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+        maybeScheduleStartAlarm(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
         inOrder.verify(mAlarmManager, timeout(1000).times(1))
                 .setWindow(anyInt(), eq(expectedFrequentAlarmTime), anyLong(),
                         eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         final long expectedRareAlarmTime = outOfQuotaTime + (24 * HOUR_IN_MILLIS);
         mCategorizer.mCategoryToUse = RARE_BUCKET_CATEGORY;
-        mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+        maybeScheduleStartAlarm(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
         inOrder.verify(mAlarmManager, timeout(1000).times(1))
                 .setWindow(anyInt(), eq(expectedRareAlarmTime), anyLong(),
                         eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         // And back up again.
         mCategorizer.mCategoryToUse = FREQUENT_BUCKET_CATEGORY;
-        mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+        maybeScheduleStartAlarm(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
         inOrder.verify(mAlarmManager, timeout(1000).times(1))
                 .setWindow(anyInt(), eq(expectedFrequentAlarmTime), anyLong(),
                         eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         mCategorizer.mCategoryToUse = WORKING_SET_BUCKET_CATEGORY;
-        mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+        maybeScheduleStartAlarm(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
         inOrder.verify(mAlarmManager, timeout(1000).times(1))
                 .setWindow(anyInt(), eq(expectedWorkingAlarmTime), anyLong(),
                         eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         mCategorizer.mCategoryToUse = ACTIVE_BUCKET_CATEGORY;
-        mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+        maybeScheduleStartAlarm(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
         inOrder.verify(mAlarmManager, timeout(1000).times(1))
                 .cancel(any(AlarmManager.OnAlarmListener.class));
         inOrder.verify(mAlarmManager, timeout(1000).times(0))
@@ -745,14 +786,14 @@
         mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS);
 
         ExecutionStats stats =
-                mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+                getExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
         assertEquals(0, stats.countInWindow);
 
         for (int i = 0; i < 10; ++i) {
             mQuotaTracker.noteEvent(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
             advanceElapsedClock(10 * SECOND_IN_MILLIS);
 
-            mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, stats);
+            updateExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, stats);
             assertEquals(0, stats.countInWindow);
         }
     }
@@ -766,14 +807,14 @@
         mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS);
 
         ExecutionStats stats =
-                mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+                getExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
         assertEquals(0, stats.countInWindow);
 
         for (int i = 0; i < 10; ++i) {
             mQuotaTracker.noteEvent(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
             advanceElapsedClock(10 * SECOND_IN_MILLIS);
 
-            mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, stats);
+            updateExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, stats);
             assertEquals(i + 1, stats.countInWindow);
         }
     }
@@ -785,14 +826,14 @@
         mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS);
 
         ExecutionStats stats =
-                mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+                getExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
         assertEquals(0, stats.countInWindow);
 
         for (int i = 0; i < 10; ++i) {
             mQuotaTracker.noteEvent(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
             advanceElapsedClock(10 * SECOND_IN_MILLIS);
 
-            mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, stats);
+            updateExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, stats);
             assertEquals(0, stats.countInWindow);
         }
     }
@@ -806,14 +847,14 @@
         mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS);
 
         ExecutionStats stats =
-                mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+                getExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
         assertEquals(0, stats.countInWindow);
 
         for (int i = 0; i < 10; ++i) {
             mQuotaTracker.noteEvent(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
             advanceElapsedClock(10 * SECOND_IN_MILLIS);
 
-            mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, stats);
+            updateExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, stats);
             assertEquals(i + 1, stats.countInWindow);
         }
     }
diff --git a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
index d45e312..fc4d8d8 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
@@ -61,7 +61,6 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mock;
-import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 import java.util.concurrent.Executor;
@@ -264,8 +263,8 @@
                 BatteryStats.WAKE_TYPE_PARTIAL, false);
 
         verifyNoMoreInteractions(mWakeLockLog, mBatteryStats);
-        WorkSource worksourceOld = Mockito.mock(WorkSource.class);
-        WorkSource worksourceNew = Mockito.mock(WorkSource.class);
+        WorkSource worksourceOld = new WorkSource(/*uid=*/ 1);
+        WorkSource worksourceNew = new WorkSource(/*uid=*/ 2);
 
         mNotifier.onWakeLockChanging(PowerManager.PARTIAL_WAKE_LOCK, "wakelockTag",
                 "my.package.name", uid, pid, worksourceOld, /* historyTag= */ null,
@@ -309,6 +308,40 @@
         verify(mWakeLockLog).onWakeLockReleased("wakelockTag", uid, -1);
     }
 
+    @Test
+    public void
+            test_notifierProcessesWorkSourceDeepCopy_OnWakelockChanging() throws RemoteException {
+        when(mPowerManagerFlags.improveWakelockLatency()).thenReturn(true);
+        createNotifier();
+        clearInvocations(mWakeLockLog, mBatteryStats, mAppOpsManager);
+        IWakeLockCallback exceptingCallback = new IWakeLockCallback.Stub() {
+            @Override public void onStateChanged(boolean enabled) throws RemoteException {
+                throw new RemoteException("Just testing");
+            }
+        };
+
+        final int uid = 1234;
+        final int pid = 5678;
+        mTestLooper.dispatchAll();
+        WorkSource worksourceOld = new WorkSource(/*uid=*/ 1);
+        WorkSource worksourceNew =  new WorkSource(/*uid=*/ 2);
+
+        mNotifier.onWakeLockChanging(PowerManager.PARTIAL_WAKE_LOCK, "wakelockTag",
+                "my.package.name", uid, pid, worksourceOld, /* historyTag= */ null,
+                exceptingCallback,
+                PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "wakelockTag",
+                "my.package.name", uid, pid, worksourceNew, /* newHistoryTag= */ null,
+                exceptingCallback);
+        // The newWorksource is modified before notifier could process it.
+        worksourceNew.set(/*uid=*/ 3);
+
+        mTestLooper.dispatchAll();
+        verify(mBatteryStats).noteChangeWakelockFromSource(worksourceOld, pid,
+                "wakelockTag", null, BatteryStats.WAKE_TYPE_PARTIAL,
+                new WorkSource(/*uid=*/ 2), pid, "wakelockTag", null,
+                BatteryStats.WAKE_TYPE_FULL, false);
+    }
+
 
     @Test
     public void testOnWakeLockListener_FullWakeLock_ProcessesOnHandler() throws RemoteException {
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 0def516..8753b25 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt
@@ -45,7 +45,6 @@
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.MockitoAnnotations
-import java.util.concurrent.TimeUnit
 import java.util.LinkedList
 import java.util.Queue
 import android.util.ArraySet
@@ -117,9 +116,6 @@
         Mockito.`when`(mockAms.traceManager).thenReturn(mockTraceManager)
 
         mouseKeysInterceptor = MouseKeysInterceptor(mockAms, testLooper.looper, DISPLAY_ID)
-        // VirtualMouse is created on a separate thread.
-        // Wait for VirtualMouse to be created before running tests
-        TimeUnit.MILLISECONDS.sleep(20L)
         mouseKeysInterceptor.next = nextInterceptor
     }
 
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 dbab54b..1db46bf 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -19,6 +19,8 @@
 import static android.Manifest.permission.INTERACT_ACROSS_PROFILES;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.app.ActivityManager.STOP_USER_ON_SWITCH_TRUE;
+import static android.app.ActivityManager.STOP_USER_ON_SWITCH_FALSE;
 import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
 import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
 import static android.app.ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE;
@@ -103,6 +105,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.widget.LockPatternUtils;
+import com.android.server.AlarmManagerInternal;
 import com.android.server.FgThread;
 import com.android.server.SystemService;
 import com.android.server.am.UserState.KeyEvictedCallback;
@@ -122,6 +125,7 @@
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 
+import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -201,7 +205,8 @@
             doNothing().when(mInjector).systemServiceManagerOnUserStopped(anyInt());
             doNothing().when(mInjector).systemServiceManagerOnUserCompletedEvent(
                     anyInt(), anyInt());
-            doNothing().when(mInjector).activityManagerForceStopPackage(anyInt(), anyString());
+            doNothing().when(mInjector).activityManagerForceStopUserPackages(anyInt(),
+                    anyString(), anyBoolean());
             doNothing().when(mInjector).activityManagerOnUserStopped(anyInt());
             doNothing().when(mInjector).clearBroadcastQueueForUser(anyInt());
             doNothing().when(mInjector).taskSupervisorRemoveUser(anyInt());
@@ -727,6 +732,39 @@
                 mUserController.getRunningUsersLU());
     }
 
+    /** Test scheduling stopping of background users - reschedule if user with a scheduled alarm. */
+    @Test
+    public void testScheduleStopOfBackgroundUser_rescheduleIfAlarm() throws Exception {
+        mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SCHEDULE_STOP_OF_BACKGROUND_USER);
+
+        mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+                /* maxRunningUsers= */ 10, /* delayUserDataLocking= */ false,
+                /* backgroundUserScheduledStopTimeSecs= */ 2);
+
+        setUpAndStartUserInBackground(TEST_USER_ID);
+        assertEquals(newHashSet(SYSTEM_USER_ID, TEST_USER_ID),
+                new HashSet<>(mUserController.getRunningUsersLU()));
+
+        // Initially, the background user has an alarm that will fire soon. So don't stop the user.
+        when(mInjector.mAlarmManagerInternal.getNextAlarmTriggerTimeForUser(eq(TEST_USER_ID)))
+                .thenReturn(System.currentTimeMillis() + Duration.ofMinutes(2).toMillis());
+        assertAndProcessScheduledStopBackgroundUser(true, TEST_USER_ID);
+        assertEquals(newHashSet(SYSTEM_USER_ID, TEST_USER_ID),
+                new HashSet<>(mUserController.getRunningUsersLU()));
+
+        // Now, that alarm is gone and the next alarm isn't for a long time. Do stop the user.
+        when(mInjector.mAlarmManagerInternal.getNextAlarmTriggerTimeForUser(eq(TEST_USER_ID)))
+                .thenReturn(System.currentTimeMillis() + Duration.ofDays(1).toMillis());
+        assertAndProcessScheduledStopBackgroundUser(true, TEST_USER_ID);
+        assertEquals(newHashSet(SYSTEM_USER_ID),
+                new HashSet<>(mUserController.getRunningUsersLU()));
+
+        // No-one is scheduled to stop anymore.
+        assertAndProcessScheduledStopBackgroundUser(false, null);
+        verify(mInjector.mAlarmManagerInternal, never())
+                .getNextAlarmTriggerTimeForUser(eq(SYSTEM_USER_ID));
+    }
+
     /**
      * Process queued SCHEDULED_STOP_BACKGROUND_USER_MSG message, if expected.
      * @param userId the user we are checking to see whether it is scheduled.
@@ -936,6 +974,61 @@
                 new HashSet<>(mUserController.getRunningUsersLU()));
     }
 
+    @Test
+    public void testEarlyPackageKillEnabledForUserSwitch_enabled() {
+        mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+                /* maxRunningUsers= */ 4, /* delayUserDataLocking= */ true,
+                /* backgroundUserScheduledStopTimeSecs= */ -1);
+
+        assertTrue(mUserController
+                .isEarlyPackageKillEnabledForUserSwitch(TEST_USER_ID, TEST_USER_ID1));
+    }
+
+    @Test
+    public void testEarlyPackageKillEnabledForUserSwitch_withoutDelayUserDataLocking() {
+        mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+                /* maxRunningUsers= */ 4, /* delayUserDataLocking= */ false,
+                /* backgroundUserScheduledStopTimeSecs= */ -1);
+
+        assertFalse(mUserController
+                .isEarlyPackageKillEnabledForUserSwitch(TEST_USER_ID, TEST_USER_ID1));
+    }
+
+    @Test
+    public void testEarlyPackageKillEnabledForUserSwitch_withPrevSystemUser() {
+        mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+                /* maxRunningUsers= */ 4, /* delayUserDataLocking= */ true,
+                /* backgroundUserScheduledStopTimeSecs= */ -1);
+
+        assertFalse(mUserController
+                .isEarlyPackageKillEnabledForUserSwitch(SYSTEM_USER_ID, TEST_USER_ID1));
+    }
+
+    @Test
+    public void testEarlyPackageKillEnabledForUserSwitch_stopUserOnSwitchModeOn() {
+        mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+                /* maxRunningUsers= */ 4, /* delayUserDataLocking= */ false,
+                /* backgroundUserScheduledStopTimeSecs= */ -1);
+
+        mUserController.setStopUserOnSwitch(STOP_USER_ON_SWITCH_TRUE);
+
+        assertTrue(mUserController
+                .isEarlyPackageKillEnabledForUserSwitch(TEST_USER_ID, TEST_USER_ID1));
+    }
+
+    @Test
+    public void testEarlyPackageKillEnabledForUserSwitch_stopUserOnSwitchModeOff() {
+        mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+                /* maxRunningUsers= */ 4, /* delayUserDataLocking= */ true,
+                /* backgroundUserScheduledStopTimeSecs= */ -1);
+
+        mUserController.setStopUserOnSwitch(STOP_USER_ON_SWITCH_FALSE);
+
+        assertFalse(mUserController
+                .isEarlyPackageKillEnabledForUserSwitch(TEST_USER_ID, TEST_USER_ID1));
+    }
+
+
     /**
      * Test that, in getRunningUsersLU, parents come after their profile, even if the profile was
      * started afterwards.
@@ -1689,6 +1782,7 @@
         private final WindowManagerService mWindowManagerMock;
         private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
         private final PowerManagerInternal mPowerManagerInternal;
+        private final AlarmManagerInternal mAlarmManagerInternal;
         private final KeyguardManager mKeyguardManagerMock;
         private final LockPatternUtils mLockPatternUtilsMock;
 
@@ -1711,6 +1805,7 @@
             mActivityTaskManagerInternal = mock(ActivityTaskManagerInternal.class);
             mStorageManagerMock = mock(IStorageManager.class);
             mPowerManagerInternal = mock(PowerManagerInternal.class);
+            mAlarmManagerInternal = mock(AlarmManagerInternal.class);
             mKeyguardManagerMock = mock(KeyguardManager.class);
             when(mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(true);
             mLockPatternUtilsMock = mock(LockPatternUtils.class);
@@ -1781,6 +1876,11 @@
         }
 
         @Override
+        AlarmManagerInternal getAlarmManagerInternal() {
+            return mAlarmManagerInternal;
+        }
+
+        @Override
         KeyguardManager getKeyguardManager() {
             return mKeyguardManagerMock;
         }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index f98bbf9..2b93ccb 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -2120,6 +2120,34 @@
         assertThat(mNativeWrapper.getResultMessages()).doesNotContain(giveOsdName);
     }
 
+    @Test
+    public void onOneTouchPlay_wakeUp_addCecDevice() {
+        assertThat(mHdmiControlService.getHdmiCecNetwork().getDeviceInfoList(false))
+                .isEmpty();
+        mHdmiCecLocalDeviceTv.mService.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY,
+                HdmiControlManager.TV_WAKE_ON_ONE_TOUCH_PLAY_ENABLED);
+        mPowerManager.setInteractive(false);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage textViewOn = HdmiCecMessageBuilder.buildTextViewOn(ADDR_PLAYBACK_1,
+                mTvLogicalAddress);
+        HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource(ADDR_PLAYBACK_1,
+                0x1000);
+        assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(textViewOn)).isEqualTo(Constants.HANDLED);
+        assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(activeSource)).isEqualTo(
+                Constants.HANDLED);
+        mTestLooper.dispatchAll();
+        assertThat(mPowerManager.isInteractive()).isTrue();
+
+        // FakePowerManagerWrapper#wakeUp() doesn't broadcast Intent.ACTION_SCREEN_ON so we have to
+        // manually call this method.
+        mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON);
+        mTestLooper.dispatchAll();
+        assertThat(mHdmiControlService.getHdmiCecNetwork().getDeviceInfoList(false))
+                .hasSize(1);
+    }
+
     protected static class MockTvDevice extends HdmiCecLocalDeviceTv {
         MockTvDevice(HdmiControlService service) {
             super(service);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
index 473d1dc..6577e09 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
@@ -240,6 +240,9 @@
     public void isValid_setAnalogueTimer_clearAnalogueTimer() {
         assertMessageValidity("04:33:0C:08:10:1E:04:30:08:00:13:AD:06").isEqualTo(OK);
         assertMessageValidity("04:34:04:0C:16:0F:08:37:00:02:EA:60:03:34").isEqualTo(OK);
+        // Allow [Recording Sequence] set multiple days of the week.
+        // e.g. Monday (0x02) | Tuesday (0x04) -> Monday or Tuesday (0x06)
+        assertMessageValidity("04:34:04:0C:16:0F:08:37:06:02:EA:60:03:34").isEqualTo(OK);
 
         assertMessageValidity("0F:33:0C:08:10:1E:04:30:08:00:13:AD:06")
                 .isEqualTo(ERROR_DESTINATION);
@@ -247,120 +250,120 @@
         assertMessageValidity("04:33:0C:08:10:1E:04:30:08:13:AD:06")
                 .isEqualTo(ERROR_PARAMETER_SHORT);
         // Out of range Day of Month
-        assertMessageValidity("04:34:20:0C:16:0F:08:37:00:02:EA:60:03").isEqualTo(ERROR_PARAMETER);
+        assertMessageValidity("04:34:20:0C:22:15:08:55:00:02:EA:60:03").isEqualTo(ERROR_PARAMETER);
         // Out of range Month of Year
-        assertMessageValidity("04:33:0C:00:10:1E:04:30:08:00:13:AD:06").isEqualTo(ERROR_PARAMETER);
+        assertMessageValidity("04:33:0C:00:16:30:04:48:08:00:13:AD:06").isEqualTo(ERROR_PARAMETER);
         // Out of range Start Time - Hour
-        assertMessageValidity("04:34:04:0C:18:0F:08:37:00:02:EA:60:03").isEqualTo(ERROR_PARAMETER);
+        assertMessageValidity("04:34:04:0C:24:15:08:55:00:02:EA:60:03").isEqualTo(ERROR_PARAMETER);
         // Out of range Start Time - Minute
-        assertMessageValidity("04:33:0C:08:10:50:04:30:08:00:13:AD:06").isEqualTo(ERROR_PARAMETER);
+        assertMessageValidity("04:33:0C:08:16:60:04:48:08:00:13:AD:06").isEqualTo(ERROR_PARAMETER);
         // Out of range Duration - Duration Hours
-        assertMessageValidity("04:34:04:0C:16:0F:64:37:00:02:EA:60:03").isEqualTo(ERROR_PARAMETER);
+        assertMessageValidity("04:34:04:0C:22:15:9A:55:00:02:EA:60:03").isEqualTo(ERROR_PARAMETER);
         // Out of range Duration - Minute
-        assertMessageValidity("04:33:0C:08:10:1E:04:64:08:00:13:AD:06").isEqualTo(ERROR_PARAMETER);
+        assertMessageValidity("04:33:0C:08:16:30:04:60:08:00:13:AD:06").isEqualTo(ERROR_PARAMETER);
         // Invalid Recording Sequence
-        assertMessageValidity("04:34:04:0C:16:0F:08:37:88:02:EA:60:03").isEqualTo(ERROR_PARAMETER);
+        assertMessageValidity("04:34:04:0C:22:15:08:55:88:02:EA:60:03").isEqualTo(ERROR_PARAMETER);
         // Invalid Recording Sequence
-        assertMessageValidity("04:33:0C:08:10:1E:04:30:A2:00:13:AD:06").isEqualTo(ERROR_PARAMETER);
+        assertMessageValidity("04:33:0C:08:16:30:04:48:A2:00:13:AD:06").isEqualTo(ERROR_PARAMETER);
         // Out of range Analogue Broadcast Type
-        assertMessageValidity("04:34:04:0C:16:0F:08:37:00:03:EA:60:03").isEqualTo(ERROR_PARAMETER);
+        assertMessageValidity("04:34:04:0C:22:15:08:55:00:03:EA:60:03").isEqualTo(ERROR_PARAMETER);
         // Out of range Analogue Frequency
-        assertMessageValidity("04:33:0C:08:10:1E:04:30:08:00:FF:FF:06").isEqualTo(ERROR_PARAMETER);
+        assertMessageValidity("04:33:0C:08:16:30:04:48:08:00:FF:FF:06").isEqualTo(ERROR_PARAMETER);
         // Out of range Broadcast System
-        assertMessageValidity("04:34:04:0C:16:0F:08:37:00:02:EA:60:20").isEqualTo(ERROR_PARAMETER);
+        assertMessageValidity("04:34:04:0C:22:15:08:55:00:02:EA:60:20").isEqualTo(ERROR_PARAMETER);
     }
 
     @Test
     public void isValid_setDigitalTimer_clearDigitalTimer() {
         // Services identified by Digital IDs - ARIB Broadcast System
-        assertMessageValidity("04:99:0C:08:15:05:04:1E:00:00:C4:C2:11:D8:75:30").isEqualTo(OK);
+        assertMessageValidity("04:99:0C:08:21:05:04:30:00:00:C4:C2:11:D8:75:30").isEqualTo(OK);
         // Service identified by Digital IDs - ATSC Broadcast System
-        assertMessageValidity("04:97:1E:07:12:20:50:28:01:01:8B:5E:39:5A").isEqualTo(OK);
+        assertMessageValidity("04:97:1E:07:18:32:80:40:01:01:8B:5E:39:5A").isEqualTo(OK);
         // Service identified by Digital IDs - DVB Broadcast System
-        assertMessageValidity("04:99:05:0C:06:0A:19:3B:40:19:8B:44:03:11:04:FC").isEqualTo(OK);
+        assertMessageValidity("04:99:05:0C:06:10:25:59:40:19:8B:44:03:11:04:FC").isEqualTo(OK);
         // Service identified by Channel - 1 part channel number
-        assertMessageValidity("04:97:12:06:0C:2D:5A:19:08:91:04:00:B1").isEqualTo(OK);
+        assertMessageValidity("04:97:12:06:12:45:90:25:08:91:04:00:B1").isEqualTo(OK);
         // Service identified by Channel - 2 part channel number
-        assertMessageValidity("04:99:15:09:00:0F:00:2D:04:82:09:C8:72:C8").isEqualTo(OK);
+        assertMessageValidity("04:99:15:09:00:15:00:45:04:82:09:C8:72:C8").isEqualTo(OK);
 
-        assertMessageValidity("4F:97:0C:08:15:05:04:1E:00:00:C4:C2:11:D8:75:30")
+        assertMessageValidity("4F:97:0C:08:21:05:04:30:00:00:C4:C2:11:D8:75:30")
                 .isEqualTo(ERROR_DESTINATION);
-        assertMessageValidity("F0:99:15:09:00:0F:00:2D:04:82:09:C8:72:C8").isEqualTo(ERROR_SOURCE);
+        assertMessageValidity("F0:99:15:09:00:15:00:45:04:82:09:C8:72:C8").isEqualTo(ERROR_SOURCE);
         assertMessageValidity("04:97:1E:12:20:58:01:01:8B:5E:39:5A")
                 .isEqualTo(ERROR_PARAMETER_SHORT);
         // Out of range Day of Month
-        assertMessageValidity("04:99:24:0C:06:0A:19:3B:40:19:8B:44:03:11:04:FC")
+        assertMessageValidity("04:99:24:0C:06:10:25:59:40:19:8B:44:03:11:04:FC")
                 .isEqualTo(ERROR_PARAMETER);
         // Out of range Month of Year
-        assertMessageValidity("04:97:12:10:0C:2D:5A:19:08:91:04:00:B1").isEqualTo(ERROR_PARAMETER);
+        assertMessageValidity("04:97:12:10:12:45:90:25:08:91:04:00:B1").isEqualTo(ERROR_PARAMETER);
         // Out of range Start Time - Hour
-        assertMessageValidity("04:99:0C:08:20:05:04:1E:00:00:C4:C2:11:D8:75:30")
+        assertMessageValidity("04:99:0C:08:24:05:04:30:00:00:C4:C2:11:D8:75:30")
                 .isEqualTo(ERROR_PARAMETER);
         // Out of range Start Time - Minute
-        assertMessageValidity("04:97:15:09:00:4B:00:2D:04:82:09:C8:72:C8")
+        assertMessageValidity("04:97:15:09:00:60:00:45:04:82:09:C8:72:C8")
                 .isEqualTo(ERROR_PARAMETER);
         // Out of range Duration - Duration Hours
-        assertMessageValidity("04:99:1E:07:12:20:78:28:01:01:8B:5E:39:5A")
+        assertMessageValidity("04:99:1E:07:18:32:9A:40:01:01:8B:5E:39:5A")
                 .isEqualTo(ERROR_PARAMETER);
         // Out of range Duration - Minute
-        assertMessageValidity("04:97:05:0C:06:0A:19:48:40:19:8B:44:03:11:04:FC")
+        assertMessageValidity("04:97:05:0C:06:10:25:60:40:19:8B:44:03:11:04:FC")
                 .isEqualTo(ERROR_PARAMETER);
         // Invalid Recording Sequence
-        assertMessageValidity("04:99:12:06:0C:2D:5A:19:90:91:04:00:B1").isEqualTo(ERROR_PARAMETER);
+        assertMessageValidity("04:99:12:06:12:45:90:25:90:91:04:00:B1").isEqualTo(ERROR_PARAMETER);
         // Invalid Recording Sequence
-        assertMessageValidity("04:97:0C:08:15:05:04:1E:21:00:C4:C2:11:D8:75:30")
+        assertMessageValidity("04:97:0C:08:15:05:04:1E:A1:00:C4:C2:11:D8:75:30")
                 .isEqualTo(ERROR_PARAMETER);
 
         // Invalid Digital Broadcast System
-        assertMessageValidity("04:99:1E:07:12:20:50:28:01:04:8B:5E:39:5A")
+        assertMessageValidity("04:99:1E:07:18:32:80:40:01:04:8B:5E:39:5A")
                 .isEqualTo(ERROR_PARAMETER);
         // Invalid Digital Broadcast System
-        assertMessageValidity("04:97:05:0C:06:0A:19:3B:40:93:8B:44:03:11:04:FC")
+        assertMessageValidity("04:97:05:0C:06:10:25:59:40:93:8B:44:03:11:04:FC")
                 .isEqualTo(ERROR_PARAMETER);
         // Insufficient data for ARIB Broadcast system
-        assertMessageValidity("04:99:0C:08:15:05:04:1E:00:00:C4:C2:11:D8:75")
+        assertMessageValidity("04:99:0C:08:21:05:04:30:00:00:C4:C2:11:D8:75")
                 .isEqualTo(ERROR_PARAMETER);
         // Insufficient data for ATSC Broadcast system
-        assertMessageValidity("04:97:1E:07:12:20:50:28:01:01:8B:5E:39").isEqualTo(ERROR_PARAMETER);
+        assertMessageValidity("04:97:1E:07:18:32:80:40:01:01:8B:5E:39").isEqualTo(ERROR_PARAMETER);
         // Insufficient data for DVB Broadcast system
-        assertMessageValidity("04:99:05:0C:06:0A:19:3B:40:19:8B:44:03:11:04")
+        assertMessageValidity("04:99:05:0C:06:10:25:59:40:19:8B:44:03:11:04")
                 .isEqualTo(ERROR_PARAMETER);
         // Insufficient data for 2 part channel number
-        assertMessageValidity("04:97:15:09:00:0F:00:2D:04:82:09:C8:72").isEqualTo(ERROR_PARAMETER);
+        assertMessageValidity("04:97:15:09:00:15:00:45:04:82:09:C8:72").isEqualTo(ERROR_PARAMETER);
         // Invalid Channel Number format
-        assertMessageValidity("04:99:12:06:0C:2D:5A:19:08:91:0D:00:B1").isEqualTo(ERROR_PARAMETER);
+        assertMessageValidity("04:99:12:06:12:45:90:25:08:91:0D:00:B1").isEqualTo(ERROR_PARAMETER);
     }
 
     @Test
     public void isValid_setExternalTimer_clearExternalTimer() {
-        assertMessageValidity("40:A1:0C:08:15:05:04:1E:02:04:20").isEqualTo(OK);
-        assertMessageValidity("40:A2:14:09:12:28:4B:19:10:05:10:00").isEqualTo(OK);
+        assertMessageValidity("40:A1:0C:08:21:05:04:30:02:04:20").isEqualTo(OK);
+        assertMessageValidity("40:A2:14:09:18:40:75:25:10:05:10:00").isEqualTo(OK);
 
-        assertMessageValidity("4F:A1:0C:08:15:05:04:1E:02:04:20").isEqualTo(ERROR_DESTINATION);
-        assertMessageValidity("F4:A2:14:09:12:28:4B:19:10:05:10:00").isEqualTo(ERROR_SOURCE);
-        assertMessageValidity("40:A1:0C:08:15:05:04:1E:02:04").isEqualTo(ERROR_PARAMETER_SHORT);
+        assertMessageValidity("4F:A1:0C:08:21:05:04:30:02:04:20").isEqualTo(ERROR_DESTINATION);
+        assertMessageValidity("F4:A2:14:09:18:40:75:25:10:05:10:00").isEqualTo(ERROR_SOURCE);
+        assertMessageValidity("40:A1:0C:08:21:05:04:30:02:04").isEqualTo(ERROR_PARAMETER_SHORT);
         // Out of range Day of Month
-        assertMessageValidity("40:A2:28:09:12:28:4B:19:10:05:10:00").isEqualTo(ERROR_PARAMETER);
+        assertMessageValidity("40:A2:28:09:18:40:75:25:10:05:10:00").isEqualTo(ERROR_PARAMETER);
         // Out of range Month of Year
-        assertMessageValidity("40:A1:0C:0F:15:05:04:1E:02:04:20").isEqualTo(ERROR_PARAMETER);
+        assertMessageValidity("40:A1:0C:0F:21:05:04:30:02:04:20").isEqualTo(ERROR_PARAMETER);
         // Out of range Start Time - Hour
-        assertMessageValidity("40:A2:14:09:1A:28:4B:19:10:05:10:00").isEqualTo(ERROR_PARAMETER);
+        assertMessageValidity("40:A2:14:09:24:40:75:25:10:05:10:00").isEqualTo(ERROR_PARAMETER);
         // Out of range Start Time - Minute
-        assertMessageValidity("40:A1:0C:08:15:48:04:1E:02:04:20").isEqualTo(ERROR_PARAMETER);
+        assertMessageValidity("40:A1:0C:08:21:60:04:30:02:04:20").isEqualTo(ERROR_PARAMETER);
         // Out of range Duration - Duration Hours
-        assertMessageValidity("40:A2:14:09:12:28:66:19:10:05:10:00").isEqualTo(ERROR_PARAMETER);
+        assertMessageValidity("40:A2:14:09:18:40:9A:25:10:05:10:00").isEqualTo(ERROR_PARAMETER);
         // Out of range Duration - Minute
-        assertMessageValidity("40:A1:0C:08:15:05:04:3F:02:04:20").isEqualTo(ERROR_PARAMETER);
+        assertMessageValidity("40:A1:0C:08:21:05:04:60:02:04:20").isEqualTo(ERROR_PARAMETER);
         // Invalid Recording Sequence
-        assertMessageValidity("40:A2:14:09:12:28:4B:19:84:05:10:00").isEqualTo(ERROR_PARAMETER);
+        assertMessageValidity("40:A2:14:09:18:40:75:25:84:05:10:00").isEqualTo(ERROR_PARAMETER);
         // Invalid Recording Sequence
-        assertMessageValidity("40:A1:0C:08:15:05:04:1E:14:04:20").isEqualTo(ERROR_PARAMETER);
+        assertMessageValidity("40:A1:0C:08:15:05:04:1E:94:04:20").isEqualTo(ERROR_PARAMETER);
         // Invalid external source specifier
-        assertMessageValidity("40:A2:14:09:12:28:4B:19:10:08:10:00").isEqualTo(ERROR_PARAMETER);
+        assertMessageValidity("40:A2:14:09:18:40:75:25:10:08:10:00").isEqualTo(ERROR_PARAMETER);
         // Invalid External PLug
-        assertMessageValidity("04:A1:0C:08:15:05:04:1E:02:04:00").isEqualTo(ERROR_PARAMETER);
+        assertMessageValidity("04:A1:0C:08:21:05:04:30:02:04:00").isEqualTo(ERROR_PARAMETER);
         // Invalid Physical Address
-        assertMessageValidity("40:A2:14:09:12:28:4B:19:10:05:10:10").isEqualTo(ERROR_PARAMETER);
+        assertMessageValidity("40:A2:14:09:18:40:75:25:10:05:10:10").isEqualTo(ERROR_PARAMETER);
     }
 
     @Test
@@ -393,9 +396,9 @@
         // Non programmed - Invalid not programmed error info
         assertMessageValidity("40:35:DE").isEqualTo(ERROR_PARAMETER);
         // Programmed - Might not be enough space available - Invalid duration hours
-        assertMessageValidity("40:35:BB:96:1C").isEqualTo(ERROR_PARAMETER);
+        assertMessageValidity("40:35:BB:9A:28").isEqualTo(ERROR_PARAMETER);
         // Not programmed - Duplicate - Invalid duration minutes
-        assertMessageValidity("40:35:EE:52:4A").isEqualTo(ERROR_PARAMETER);
+        assertMessageValidity("40:35:EE:82:60").isEqualTo(ERROR_PARAMETER);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index 3d68849..dddab65 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -108,6 +108,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isA;
 import static org.mockito.Mockito.CALLS_REAL_METHODS;
@@ -165,6 +166,7 @@
 import android.os.PowerManager;
 import android.os.PowerManagerInternal;
 import android.os.PowerSaveState;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.SimpleClock;
 import android.os.SystemClock;
@@ -197,6 +199,7 @@
 import androidx.test.filters.MediumTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.test.BroadcastInterceptingContext;
 import com.android.internal.util.test.BroadcastInterceptingContext.FutureIntent;
 import com.android.internal.util.test.FsUtil;
@@ -2310,6 +2313,70 @@
         assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
     }
 
+    @SuppressWarnings("GuardedBy") // For not holding mUidRulesFirstLock
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_NEVER_APPLY_RULES_TO_CORE_UIDS)
+    public void testRulesNeverAppliedToCoreUids() throws Exception {
+        clearInvocations(mNetworkManager);
+
+        final int coreAppId = Process.FIRST_APPLICATION_UID - 102;
+        final int coreUid = UserHandle.getUid(USER_ID, coreAppId);
+
+        // Enable all restrictions and add this core uid to all allowlists.
+        mService.mDeviceIdleMode = true;
+        mService.mRestrictPower = true;
+        setRestrictBackground(true);
+        expectHasUseRestrictedNetworksPermission(coreUid, true);
+        enableRestrictedMode(true);
+        final NetworkPolicyManagerInternal internal = LocalServices.getService(
+                NetworkPolicyManagerInternal.class);
+        internal.setLowPowerStandbyActive(true);
+        internal.setLowPowerStandbyAllowlist(new int[]{coreUid});
+        internal.onTempPowerSaveWhitelistChange(coreAppId, true, REASON_OTHER, "testing");
+
+        when(mPowerExemptionManager.getAllowListedAppIds(anyBoolean()))
+                .thenReturn(new int[]{coreAppId});
+        mPowerAllowlistReceiver.onReceive(mServiceContext, null);
+
+        // A normal uid would undergo a rule change from denied to allowed on all chains, but we
+        // should not request any rule change for this core uid.
+        verify(mNetworkManager, never()).setFirewallUidRule(anyInt(), eq(coreUid), anyInt());
+        verify(mNetworkManager, never()).setFirewallUidRules(anyInt(),
+                argThat(ar -> ArrayUtils.contains(ar, coreUid)), any(int[].class));
+    }
+
+    @SuppressWarnings("GuardedBy") // For not holding mUidRulesFirstLock
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_NEVER_APPLY_RULES_TO_CORE_UIDS)
+    public void testRulesNeverAppliedToUidsWithoutInternetPermission() throws Exception {
+        clearInvocations(mNetworkManager);
+
+        mService.mInternetPermissionMap.clear();
+        expectHasInternetPermission(UID_A, false);
+
+        // Enable all restrictions and add this uid to all allowlists.
+        mService.mDeviceIdleMode = true;
+        mService.mRestrictPower = true;
+        setRestrictBackground(true);
+        expectHasUseRestrictedNetworksPermission(UID_A, true);
+        enableRestrictedMode(true);
+        final NetworkPolicyManagerInternal internal = LocalServices.getService(
+                NetworkPolicyManagerInternal.class);
+        internal.setLowPowerStandbyActive(true);
+        internal.setLowPowerStandbyAllowlist(new int[]{UID_A});
+        internal.onTempPowerSaveWhitelistChange(APP_ID_A, true, REASON_OTHER, "testing");
+
+        when(mPowerExemptionManager.getAllowListedAppIds(anyBoolean()))
+                .thenReturn(new int[]{APP_ID_A});
+        mPowerAllowlistReceiver.onReceive(mServiceContext, null);
+
+        // A normal uid would undergo a rule change from denied to allowed on all chains, but we
+        // should not request any rule this uid without the INTERNET permission.
+        verify(mNetworkManager, never()).setFirewallUidRule(anyInt(), eq(UID_A), anyInt());
+        verify(mNetworkManager, never()).setFirewallUidRules(anyInt(),
+                argThat(ar -> ArrayUtils.contains(ar, UID_A)), any(int[].class));
+    }
+
     private boolean isUidState(int uid, int procState, int procStateSeq, int capability) {
         final NetworkPolicyManager.UidState uidState = mService.getUidStateForTest(uid);
         if (uidState == null) {
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 d714db99..7912156 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -121,6 +121,9 @@
         // Making a copy of mUsersToRemove to avoid ConcurrentModificationException
         mUsersToRemove.stream().toList().forEach(this::removeUser);
         mUserRemovalWaiter.close();
+
+        mUserManager.setUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, false,
+                mContext.getUser());
     }
 
     private void removeExistingUsers() {
@@ -935,6 +938,35 @@
 
     @MediumTest
     @Test
+    @RequiresFlagsEnabled(android.multiuser.Flags.FLAG_UNICORN_MODE_REFACTORING_FOR_HSUM_READ_ONLY)
+    public void testSetUserAdminThrowsSecurityException() throws Exception {
+        UserInfo targetUser = createUser("SecondaryUser", /*flags=*/ 0);
+        assertThat(targetUser.isAdmin()).isFalse();
+
+        try {
+            // 1. Target User Restriction
+            mUserManager.setUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, true,
+                    targetUser.getUserHandle());
+            assertThrows(SecurityException.class, () -> mUserManager.setUserAdmin(targetUser.id));
+
+            // 2. Current User Restriction
+            mUserManager.setUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, false,
+                    targetUser.getUserHandle());
+            mUserManager.setUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, true,
+                    mContext.getUser());
+            assertThrows(SecurityException.class, () -> mUserManager.setUserAdmin(targetUser.id));
+
+        } finally {
+            // Ensure restriction is removed even if test fails
+            mUserManager.setUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, false,
+                    targetUser.getUserHandle());
+            mUserManager.setUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, false,
+                    mContext.getUser());
+        }
+    }
+
+    @MediumTest
+    @Test
     public void testRevokeUserAdmin() throws Exception {
         UserInfo userInfo = createUser("Admin", /*flags=*/ UserInfo.FLAG_ADMIN);
         assertThat(userInfo.isAdmin()).isTrue();
@@ -959,6 +991,37 @@
 
     @MediumTest
     @Test
+    @RequiresFlagsEnabled(android.multiuser.Flags.FLAG_UNICORN_MODE_REFACTORING_FOR_HSUM_READ_ONLY)
+    public void testRevokeUserAdminThrowsSecurityException() throws Exception {
+        UserInfo targetUser = createUser("SecondaryUser", /*flags=*/ 0);
+        assertThat(targetUser.isAdmin()).isFalse();
+
+        try {
+            // 1. Target User Restriction
+            mUserManager.setUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, true,
+                    targetUser.getUserHandle());
+            assertThrows(SecurityException.class, () -> mUserManager
+                    .revokeUserAdmin(targetUser.id));
+
+            // 2. Current User Restriction
+            mUserManager.setUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, false,
+                    targetUser.getUserHandle());
+            mUserManager.setUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, true,
+                    mContext.getUser());
+            assertThrows(SecurityException.class, () -> mUserManager
+                    .revokeUserAdmin(targetUser.id));
+
+        } finally {
+            // Ensure restriction is removed even if test fails
+            mUserManager.setUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, false,
+                    targetUser.getUserHandle());
+            mUserManager.setUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, false,
+                    mContext.getUser());
+        }
+    }
+
+    @MediumTest
+    @Test
     public void testGetProfileParent() throws Exception {
         assumeManagedUsersSupported();
         int mainUserId = mUserManager.getMainUser().getIdentifier();
@@ -1184,6 +1247,23 @@
         }
     }
 
+    // Make sure createUser for ADMIN would fail if we have DISALLOW_GRANT_ADMIN.
+    @MediumTest
+    @Test
+    @RequiresFlagsEnabled(android.multiuser.Flags.FLAG_UNICORN_MODE_REFACTORING_FOR_HSUM_READ_ONLY)
+    public void testCreateAdminUser_disallowGrantAdmin() throws Exception {
+        final int creatorId = ActivityManager.getCurrentUser();
+        final UserHandle creatorHandle = asHandle(creatorId);
+        mUserManager.setUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, true, creatorHandle);
+        try {
+            UserInfo createdInfo = createUser("SecondaryUser", /*flags=*/ UserInfo.FLAG_ADMIN);
+            assertThat(createdInfo).isNull();
+        } finally {
+            mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER, false,
+                    creatorHandle);
+        }
+    }
+
     // Make sure createProfile would fail if we have DISALLOW_ADD_CLONE_PROFILE.
     @MediumTest
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
index fad10f7..5518082 100644
--- a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
@@ -182,7 +182,7 @@
                 + "    <device-state>\n"
                 + "        <identifier>1</identifier>\n"
                 + "        <properties>\n"
-                + "            <property>PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS</property>\n"
+                + "            <property>com.android.server.policy.PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS</property>\n"
                 + "        </properties>\n"
                 + "        <conditions/>\n"
                 + "    </device-state>\n"
@@ -338,11 +338,9 @@
                 + "        <identifier>4</identifier>\n"
                 + "        <name>THERMAL_TEST</name>\n"
                 + "        <properties>\n"
-                + "            <property>PROPERTY_EMULATED_ONLY</property>\n"
-                + "            <property>PROPERTY_POLICY_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL"
-                + "</property>\n"
-                + "            <property>PROPERTY_POLICY_UNSUPPORTED_WHEN_POWER_SAVE_MODE"
-                + "</property>\n"
+                + "            <property>com.android.server.policy.PROPERTY_EMULATED_ONLY</property>\n"
+                + "            <property>com.android.server.policy.PROPERTY_POLICY_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL</property>\n"
+                + "            <property>com.android.server.policy.PROPERTY_POLICY_UNSUPPORTED_WHEN_POWER_SAVE_MODE</property>\n"
                 + "        </properties>\n"
                 + "    </device-state>\n"
                 + "</device-state-config>\n";
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java
index f6e1162..af7f703 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java
@@ -16,7 +16,6 @@
 
 package com.android.server.notification;
 
-import static android.service.notification.Condition.SOURCE_USER_ACTION;
 import static android.service.notification.Condition.STATE_FALSE;
 import static android.service.notification.Condition.STATE_TRUE;
 
@@ -31,13 +30,11 @@
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
-import android.app.Flags;
 import android.content.ComponentName;
 import android.content.ServiceConnection;
 import android.content.pm.IPackageManager;
 import android.net.Uri;
 import android.os.IInterface;
-import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.service.notification.Condition;
 
@@ -150,57 +147,6 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_MODES_UI)
-    public void notifyConditions_appCannotUndoUserEnablement() {
-        ManagedServices.ManagedServiceInfo msi = mProviders.new ManagedServiceInfo(
-                mock(IInterface.class), new ComponentName("package", "cls"), 0, false,
-                mock(ServiceConnection.class), 33, 100);
-        // First, user enabled mode
-        Condition[] userConditions = new Condition[] {
-                new Condition(Uri.parse("a"), "summary", STATE_TRUE, SOURCE_USER_ACTION)
-        };
-        mProviders.notifyConditions("package", msi, userConditions);
-        verify(mCallback).onConditionChanged(eq(Uri.parse("a")), eq(userConditions[0]));
-
-        // Second, app tries to disable it, but cannot
-        Condition[] appConditions = new Condition[] {
-                new Condition(Uri.parse("a"), "summary", STATE_FALSE)
-        };
-        mProviders.notifyConditions("package", msi, appConditions);
-        verify(mCallback).onConditionChanged(eq(Uri.parse("a")), eq(userConditions[0]));
-    }
-
-    @Test
-    @EnableFlags(Flags.FLAG_MODES_UI)
-    public void notifyConditions_appCanTakeoverUserEnablement() {
-        ManagedServices.ManagedServiceInfo msi = mProviders.new ManagedServiceInfo(
-                mock(IInterface.class), new ComponentName("package", "cls"), 0, false,
-                mock(ServiceConnection.class), 33, 100);
-        // First, user enabled mode
-        Condition[] userConditions = new Condition[] {
-                new Condition(Uri.parse("a"), "summary", STATE_TRUE, SOURCE_USER_ACTION)
-        };
-        mProviders.notifyConditions("package", msi, userConditions);
-        verify(mCallback).onConditionChanged(eq(Uri.parse("a")), eq(userConditions[0]));
-
-        // Second, app now thinks the rule should be on due it its intelligence
-        Condition[] appConditions = new Condition[] {
-                new Condition(Uri.parse("a"), "summary", STATE_TRUE)
-        };
-        mProviders.notifyConditions("package", msi, appConditions);
-        verify(mCallback).onConditionChanged(eq(Uri.parse("a")), eq(appConditions[0]));
-
-        // Lastly, app can turn rule off when its intelligence think it should be off
-        appConditions = new Condition[] {
-                new Condition(Uri.parse("a"), "summary", STATE_FALSE)
-        };
-        mProviders.notifyConditions("package", msi, appConditions);
-        verify(mCallback).onConditionChanged(eq(Uri.parse("a")), eq(appConditions[0]));
-
-        verifyNoMoreInteractions(mCallback);
-    }
-
-    @Test
     public void testRemoveDefaultFromConfig() {
         final int userId = 0;
         ComponentName oldDefaultComponent = ComponentName.unflattenFromString("package/Component1");
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 4bea95f..7933f7a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
@@ -18,8 +18,8 @@
 
 import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_NIGHT;
 import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
-import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_APP;
-import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER;
+import static android.service.notification.ZenModeConfig.ORIGIN_APP;
+import static android.service.notification.ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -48,7 +48,6 @@
 import android.os.PowerManager;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.service.notification.ZenDeviceEffects;
-import android.service.notification.ZenModeConfig;
 import android.testing.TestableContext;
 
 import androidx.test.InstrumentationRegistry;
@@ -78,27 +77,6 @@
     @Mock UiModeManager mUiModeManager;
     @Mock WallpaperManager mWallpaperManager;
 
-    private enum ChangeOrigin {
-        ORIGIN_UNKNOWN(ZenModeConfig.UPDATE_ORIGIN_UNKNOWN),
-        ORIGIN_INIT(ZenModeConfig.UPDATE_ORIGIN_INIT),
-        ORIGIN_INIT_USER(ZenModeConfig.UPDATE_ORIGIN_INIT_USER),
-        ORIGIN_USER(ZenModeConfig.UPDATE_ORIGIN_USER),
-        ORIGIN_APP(ZenModeConfig.UPDATE_ORIGIN_APP),
-        ORIGIN_SYSTEM_OR_SYSTEMUI(ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI),
-        ORIGIN_RESTORE_BACKUP(ZenModeConfig.UPDATE_ORIGIN_RESTORE_BACKUP);
-
-        private final int mValue;
-
-        ChangeOrigin(@ZenModeConfig.ConfigChangeOrigin int value) {
-            mValue = value;
-        }
-
-        @ZenModeConfig.ConfigChangeOrigin
-        public int value() {
-            return mValue;
-        }
-    }
-
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
@@ -123,7 +101,7 @@
                 .setShouldDisplayGrayscale(true)
                 .setShouldUseNightMode(true)
                 .build();
-        mApplier.apply(effects, UPDATE_ORIGIN_USER);
+        mApplier.apply(effects, ORIGIN_USER_IN_SYSTEMUI);
 
         verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true));
         verify(mColorDisplayManager).setSaturationLevel(eq(0));
@@ -141,14 +119,14 @@
                 .setShouldDisplayGrayscale(true)
                 .setShouldUseNightMode(true)
                 .build();
-        mApplier.apply(previousEffects, UPDATE_ORIGIN_USER);
+        mApplier.apply(previousEffects, ORIGIN_USER_IN_SYSTEMUI);
         verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true));
         verify(mColorDisplayManager).setSaturationLevel(eq(0));
         verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f));
         verify(mUiModeManager).setAttentionModeThemeOverlay(eq(MODE_ATTENTION_THEME_OVERLAY_NIGHT));
 
         ZenDeviceEffects noEffects = new ZenDeviceEffects.Builder().build();
-        mApplier.apply(noEffects, UPDATE_ORIGIN_USER);
+        mApplier.apply(noEffects, ORIGIN_USER_IN_SYSTEMUI);
 
         verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(false));
         verify(mColorDisplayManager).setSaturationLevel(eq(100));
@@ -163,11 +141,11 @@
         ZenDeviceEffects previousEffects = new ZenDeviceEffects.Builder()
                 .setShouldSuppressAmbientDisplay(true)
                 .build();
-        mApplier.apply(previousEffects, UPDATE_ORIGIN_USER);
+        mApplier.apply(previousEffects, ORIGIN_USER_IN_SYSTEMUI);
         verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true));
 
         ZenDeviceEffects noEffects = new ZenDeviceEffects.Builder().build();
-        mApplier.apply(noEffects, UPDATE_ORIGIN_USER);
+        mApplier.apply(noEffects, ORIGIN_USER_IN_SYSTEMUI);
 
         verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(false));
         verifyZeroInteractions(mColorDisplayManager, mWallpaperManager, mUiModeManager);
@@ -186,7 +164,7 @@
                 .setShouldDisplayGrayscale(true)
                 .setShouldUseNightMode(true)
                 .build();
-        mApplier.apply(effects, UPDATE_ORIGIN_USER);
+        mApplier.apply(effects, ORIGIN_USER_IN_SYSTEMUI);
 
         verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true));
         // (And no crash from missing services).
@@ -207,7 +185,7 @@
                 .setShouldDisplayGrayscale(true)
                 .setShouldUseNightMode(true)
                 .build();
-        mApplier.apply(effects, UPDATE_ORIGIN_USER);
+        mApplier.apply(effects, ORIGIN_USER_IN_SYSTEMUI);
 
         verifyNoMoreInteractions(mWallpaperManager);
     }
@@ -220,7 +198,7 @@
                 .setShouldDimWallpaper(true)
                 .setShouldDisplayGrayscale(true)
                 .build();
-        mApplier.apply(effects, UPDATE_ORIGIN_USER);
+        mApplier.apply(effects, ORIGIN_USER_IN_SYSTEMUI);
 
         verify(mColorDisplayManager).setSaturationLevel(eq(0));
         verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f));
@@ -234,12 +212,12 @@
         mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
 
         mApplier.apply(new ZenDeviceEffects.Builder().setShouldDimWallpaper(true).build(),
-                UPDATE_ORIGIN_USER);
+                ORIGIN_USER_IN_SYSTEMUI);
         verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f));
 
         // Apply a second effect and remove the first one.
         mApplier.apply(new ZenDeviceEffects.Builder().setShouldDisplayGrayscale(true).build(),
-                UPDATE_ORIGIN_USER);
+                ORIGIN_USER_IN_SYSTEMUI);
 
         // Wallpaper dimming was undone, Grayscale was applied, nothing else was touched.
         verify(mWallpaperManager).setWallpaperDimAmount(eq(0.0f));
@@ -259,7 +237,7 @@
         when(mPowerManager.isInteractive()).thenReturn(true);
 
         mApplier.apply(new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build(),
-                UPDATE_ORIGIN_APP);
+                ORIGIN_APP);
 
         // Effect was not yet applied, but a broadcast receiver was registered.
         verifyZeroInteractions(mUiModeManager);
@@ -278,7 +256,7 @@
 
     @Test
     public void apply_nightModeWithScreenOff_appliedImmediately(
-            @TestParameter ChangeOrigin origin) {
+            @TestParameter ZenChangeOrigin origin) {
         mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
 
         when(mPowerManager.isInteractive()).thenReturn(false);
@@ -292,9 +270,10 @@
     }
 
     @Test
-    @TestParameters({"{origin: ORIGIN_USER}", "{origin: ORIGIN_INIT}",
-            "{origin: ORIGIN_INIT_USER}"})
-    public void apply_nightModeWithScreenOn_appliedImmediatelyBasedOnOrigin(ChangeOrigin origin) {
+    @TestParameters({"{origin: ORIGIN_USER_IN_SYSTEMUI}", "{origin: ORIGIN_USER_IN_APP}",
+            "{origin: ORIGIN_INIT}", "{origin: ORIGIN_INIT_USER}"})
+    public void apply_nightModeWithScreenOn_appliedImmediatelyBasedOnOrigin(
+            ZenChangeOrigin origin) {
         mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
 
         when(mPowerManager.isInteractive()).thenReturn(true);
@@ -309,8 +288,9 @@
 
     @Test
     @TestParameters({"{origin: ORIGIN_APP}", "{origin: ORIGIN_RESTORE_BACKUP}",
-            "{origin: ORIGIN_SYSTEM_OR_SYSTEMUI}", "{origin: ORIGIN_UNKNOWN}"})
-    public void apply_nightModeWithScreenOn_willBeAppliedLaterBasedOnOrigin(ChangeOrigin origin) {
+            "{origin: ORIGIN_SYSTEM}", "{origin: ORIGIN_UNKNOWN}"})
+    public void apply_nightModeWithScreenOn_willBeAppliedLaterBasedOnOrigin(
+            ZenChangeOrigin origin) {
         mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
 
         when(mPowerManager.isInteractive()).thenReturn(true);
@@ -342,7 +322,7 @@
                 .setShouldDisplayGrayscale(true)
                 .setShouldUseNightMode(true)
                 .build();
-        mApplier.apply(effects, UPDATE_ORIGIN_USER);
+        mApplier.apply(effects, ORIGIN_USER_IN_SYSTEMUI);
 
         // No crashes
     }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
index de70280..643ee4a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
@@ -15,7 +15,9 @@
  */
 package com.android.server.notification;
 
+import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY;
 import static android.app.Notification.FLAG_BUBBLE;
+import static android.app.Notification.FLAG_GROUP_SUMMARY;
 import static android.app.Notification.GROUP_ALERT_ALL;
 import static android.app.Notification.GROUP_ALERT_CHILDREN;
 import static android.app.Notification.GROUP_ALERT_SUMMARY;
@@ -539,6 +541,36 @@
         return r;
     }
 
+    private NotificationRecord getAutogroupSummaryNotificationRecord(int id, String groupKey,
+            int groupAlertBehavior, UserHandle userHandle, String packageName) {
+        final Builder builder = new Builder(getContext())
+                .setContentTitle("foo")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setPriority(Notification.PRIORITY_HIGH)
+                .setFlag(FLAG_GROUP_SUMMARY | FLAG_AUTOGROUP_SUMMARY, true);
+
+        int defaults = 0;
+        defaults |= Notification.DEFAULT_SOUND;
+        mChannel.setSound(Settings.System.DEFAULT_NOTIFICATION_URI,
+                Notification.AUDIO_ATTRIBUTES_DEFAULT);
+
+        builder.setDefaults(defaults);
+        builder.setGroup(groupKey);
+        builder.setGroupAlertBehavior(groupAlertBehavior);
+        Notification n = builder.build();
+
+        Context context = spy(getContext());
+        PackageManager packageManager = spy(context.getPackageManager());
+        when(context.getPackageManager()).thenReturn(packageManager);
+        when(packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)).thenReturn(false);
+
+        StatusBarNotification sbn = new StatusBarNotification(packageName, packageName, id, mTag,
+                mUid, mPid, n, userHandle, null, System.currentTimeMillis());
+        NotificationRecord r = new NotificationRecord(context, sbn, mChannel);
+        mService.addNotification(r);
+        return r;
+    }
+
     //
     // Convenience functions for interacting with mocks
     //
@@ -2426,6 +2458,74 @@
     }
 
     @Test
+    public void testBeepVolume_politeNotif_AvalancheStrategy_mixedNotif() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+        mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS_ATTN_UPDATE);
+        TestableFlagResolver flagResolver = new TestableFlagResolver();
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+        initAttentionHelper(flagResolver);
+
+        // Trigger avalanche trigger intent
+        final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        intent.putExtra("state", false);
+        mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+
+        // Regular notification: should beep at 0% volume
+        NotificationRecord r = getBeepyNotification();
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+        verifyBeepVolume(0.0f);
+        assertEquals(-1, r.getLastAudiblyAlertedMs());
+        Mockito.reset(mRingtonePlayer);
+
+        // Conversation notification
+        mChannel = new NotificationChannel("test2", "test2", IMPORTANCE_DEFAULT);
+        NotificationRecord r2 = getConversationNotificationRecord(mId, false /* insistent */,
+                false /* once */, true /* noisy */, false /* buzzy*/, false /* lights */, true,
+                true, false, null, Notification.GROUP_ALERT_ALL, false, mUser, mPkg,
+                "shortcut");
+
+        // Should beep at 100% volume
+        mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
+        assertNotEquals(-1, r2.getLastAudiblyAlertedMs());
+        verifyBeepVolume(1.0f);
+
+        // Conversation notification on a different channel
+        mChannel = new NotificationChannel("test3", "test3", IMPORTANCE_DEFAULT);
+        NotificationRecord r3 = getConversationNotificationRecord(mId, false /* insistent */,
+                false /* once */, true /* noisy */, false /* buzzy*/, false /* lights */, true,
+                true, false, null, Notification.GROUP_ALERT_ALL, false, mUser, mPkg,
+                "shortcut");
+
+        // Should beep at 50% volume
+        Mockito.reset(mRingtonePlayer);
+        mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
+        assertNotEquals(-1, r3.getLastAudiblyAlertedMs());
+        verifyBeepVolume(0.5f);
+
+        // 2nd update should beep at 0% volume
+        Mockito.reset(mRingtonePlayer);
+        mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
+        verifyBeepVolume(0.0f);
+
+        // Set important conversation
+        mChannel.setImportantConversation(true);
+        r3 = getConversationNotificationRecord(mId, false /* insistent */,
+            false /* once */, true /* noisy */, false /* buzzy*/, false /* lights */, true,
+            true, false, null, Notification.GROUP_ALERT_ALL, false, mUser, mPkg,
+            "shortcut");
+
+        // important conversation should beep at 100% volume
+        Mockito.reset(mRingtonePlayer);
+        mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
+        verifyBeepVolume(1.0f);
+
+        verify(mAccessibilityService, times(5)).sendAccessibilityEvent(any(), anyInt());
+        assertNotEquals(-1, r3.getLastAudiblyAlertedMs());
+    }
+
+    @Test
     public void testBeepVolume_politeNotif_Avalanche_exemptEmergency() throws Exception {
         mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
         mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
@@ -2603,6 +2703,79 @@
     }
 
     @Test
+    public void testBeepVolume_politeNotif_justSummaries() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+        mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+        TestableFlagResolver flagResolver = new TestableFlagResolver();
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+        // NOTIFICATION_COOLDOWN_ALL setting is enabled
+        Settings.System.putInt(getContext().getContentResolver(),
+                Settings.System.NOTIFICATION_COOLDOWN_ALL, 1);
+        initAttentionHelper(flagResolver);
+
+        NotificationRecord summary = getBeepyNotificationRecord("a", GROUP_ALERT_ALL);
+        summary.getNotification().flags |= Notification.FLAG_GROUP_SUMMARY;
+
+        // first update at 100% volume
+        mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
+        assertNotEquals(-1, summary.getLastAudiblyAlertedMs());
+        verifyBeepVolume(1.0f);
+        Mockito.reset(mRingtonePlayer);
+
+        // update should beep at 50% volume
+        summary = getBeepyNotificationRecord("a", GROUP_ALERT_ALL);
+        summary.getNotification().flags |= Notification.FLAG_GROUP_SUMMARY;
+        mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
+        assertNotEquals(-1, summary.getLastAudiblyAlertedMs());
+        verifyBeepVolume(0.5f);
+        Mockito.reset(mRingtonePlayer);
+
+        // next update at 0% volume
+        mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
+        assertEquals(-1, summary.getLastAudiblyAlertedMs());
+        verifyBeepVolume(0.0f);
+
+        verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
+    }
+
+    @Test
+    public void testBeepVolume_politeNotif_autogroupSummary() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+        mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+        TestableFlagResolver flagResolver = new TestableFlagResolver();
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+        // NOTIFICATION_COOLDOWN_ALL setting is enabled
+        Settings.System.putInt(getContext().getContentResolver(),
+                Settings.System.NOTIFICATION_COOLDOWN_ALL, 1);
+        initAttentionHelper(flagResolver);
+
+        // child should beep at 100% volume
+        NotificationRecord child = getBeepyNotificationRecord("a", GROUP_ALERT_ALL);
+        mAttentionHelper.buzzBeepBlinkLocked(child, DEFAULT_SIGNALS);
+        assertNotEquals(-1, child.getLastAudiblyAlertedMs());
+        verifyBeepVolume(1.0f);
+        Mockito.reset(mRingtonePlayer);
+
+        // summary 0% volume (GROUP_ALERT_CHILDREN)
+        NotificationRecord summary = getAutogroupSummaryNotificationRecord(mId, "a",
+                GROUP_ALERT_CHILDREN, mUser, mPkg);
+        mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
+        verifyNeverBeep();
+        assertFalse(summary.isInterruptive());
+        assertEquals(-1, summary.getLastAudiblyAlertedMs());
+        Mockito.reset(mRingtonePlayer);
+
+        // next update at 50% volume because autogroup summary was ignored
+        mAttentionHelper.buzzBeepBlinkLocked(child, DEFAULT_SIGNALS);
+        assertNotEquals(-1, child.getLastAudiblyAlertedMs());
+        verifyBeepVolume(0.5f);
+
+        verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
+    }
+
+    @Test
     public void testBeepVolume_politeNotif_applyPerApp() throws Exception {
         mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
         mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
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 1b999e4..5a8de58 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -10064,7 +10064,7 @@
 
         // verify that zen mode helper gets passed in a package name of "android"
         verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule),
-                eq(ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI), anyString(), anyInt());
+                eq(ZenModeConfig.ORIGIN_SYSTEM), anyString(), anyInt());
     }
 
     @Test
@@ -10086,7 +10086,7 @@
 
         // verify that zen mode helper gets passed in a package name of "android"
         verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule),
-                eq(ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI), anyString(), anyInt());
+                eq(ZenModeConfig.ORIGIN_SYSTEM), anyString(), anyInt());
     }
 
     @Test
@@ -10106,7 +10106,7 @@
 
         // verify that zen mode helper gets passed in the package name from the arg, not the owner
         verify(mockZenModeHelper).addAutomaticZenRule(
-                eq("another.package"), eq(rule), eq(ZenModeConfig.UPDATE_ORIGIN_APP),
+                eq("another.package"), eq(rule), eq(ZenModeConfig.ORIGIN_APP),
                 anyString(), anyInt());  // doesn't count as a system/systemui call
     }
 
@@ -10221,7 +10221,7 @@
         mBinderService.addAutomaticZenRule(SOME_ZEN_RULE, "pkg", /* fromUser= */ true);
 
         verify(zenModeHelper).addAutomaticZenRule(eq("pkg"), eq(SOME_ZEN_RULE),
-                eq(ZenModeConfig.UPDATE_ORIGIN_USER), anyString(), anyInt());
+                eq(ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI), anyString(), anyInt());
     }
 
     @Test
@@ -10233,7 +10233,7 @@
         mBinderService.addAutomaticZenRule(SOME_ZEN_RULE, "pkg", /* fromUser= */ false);
 
         verify(zenModeHelper).addAutomaticZenRule(eq("pkg"), eq(SOME_ZEN_RULE),
-                eq(ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI), anyString(), anyInt());
+                eq(ZenModeConfig.ORIGIN_SYSTEM), anyString(), anyInt());
     }
 
     @Test
@@ -10245,7 +10245,7 @@
         mBinderService.addAutomaticZenRule(SOME_ZEN_RULE, "pkg", /* fromUser= */ false);
 
         verify(zenModeHelper).addAutomaticZenRule(eq("pkg"), eq(SOME_ZEN_RULE),
-                eq(ZenModeConfig.UPDATE_ORIGIN_APP), anyString(), anyInt());
+                eq(ZenModeConfig.ORIGIN_APP), anyString(), anyInt());
     }
 
     @Test
@@ -10267,7 +10267,7 @@
         mBinderService.updateAutomaticZenRule("id", SOME_ZEN_RULE, /* fromUser= */ true);
 
         verify(zenModeHelper).updateAutomaticZenRule(eq("id"), eq(SOME_ZEN_RULE),
-                eq(ZenModeConfig.UPDATE_ORIGIN_USER), anyString(), anyInt());
+                eq(ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI), anyString(), anyInt());
     }
 
     @Test
@@ -10289,7 +10289,7 @@
         mBinderService.removeAutomaticZenRule("id", /* fromUser= */ true);
 
         verify(zenModeHelper).removeAutomaticZenRule(eq("id"),
-                eq(ZenModeConfig.UPDATE_ORIGIN_USER), anyString(), anyInt());
+                eq(ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI), anyString(), anyInt());
     }
 
     @Test
@@ -10304,7 +10304,8 @@
 
     @Test
     @EnableFlags(android.app.Flags.FLAG_MODES_API)
-    public void setAutomaticZenRuleState_conditionFromUser_mappedToOriginUser() throws Exception {
+    public void setAutomaticZenRuleState_fromAppWithConditionFromUser_originUserInApp()
+            throws Exception {
         ZenModeHelper zenModeHelper = setUpMockZenTest();
         mService.setCallerIsNormalPackage();
 
@@ -10313,12 +10314,12 @@
         mBinderService.setAutomaticZenRuleState("id", withSourceUser);
 
         verify(zenModeHelper).setAutomaticZenRuleState(eq("id"), eq(withSourceUser),
-                eq(ZenModeConfig.UPDATE_ORIGIN_USER), anyInt());
+                eq(ZenModeConfig.ORIGIN_USER_IN_APP), anyInt());
     }
 
     @Test
     @EnableFlags(android.app.Flags.FLAG_MODES_API)
-    public void setAutomaticZenRuleState_fromAppWithConditionNotFromUser_mappedToOriginApp()
+    public void setAutomaticZenRuleState_fromAppWithConditionNotFromUser_originApp()
             throws Exception {
         ZenModeHelper zenModeHelper = setUpMockZenTest();
         mService.setCallerIsNormalPackage();
@@ -10328,12 +10329,26 @@
         mBinderService.setAutomaticZenRuleState("id", withSourceContext);
 
         verify(zenModeHelper).setAutomaticZenRuleState(eq("id"), eq(withSourceContext),
-                eq(ZenModeConfig.UPDATE_ORIGIN_APP), anyInt());
+                eq(ZenModeConfig.ORIGIN_APP), anyInt());
     }
 
     @Test
     @EnableFlags(android.app.Flags.FLAG_MODES_API)
-    public void setAutomaticZenRuleState_fromSystemWithConditionNotFromUser_mappedToOriginSystem()
+    public void setAutomaticZenRuleState_fromSystemWithConditionFromUser_originUserInSystemUi()
+            throws Exception {
+        ZenModeHelper zenModeHelper = setUpMockZenTest();
+        mService.isSystemUid = true;
+
+        Condition withSourceContext = new Condition(Uri.parse("uri"), "summary", STATE_TRUE,
+                SOURCE_USER_ACTION);
+        mBinderService.setAutomaticZenRuleState("id", withSourceContext);
+
+        verify(zenModeHelper).setAutomaticZenRuleState(eq("id"), eq(withSourceContext),
+                eq(ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI), anyInt());
+    }
+    @Test
+    @EnableFlags(android.app.Flags.FLAG_MODES_API)
+    public void setAutomaticZenRuleState_fromSystemWithConditionNotFromUser_originSystem()
             throws Exception {
         ZenModeHelper zenModeHelper = setUpMockZenTest();
         mService.isSystemUid = true;
@@ -10343,7 +10358,7 @@
         mBinderService.setAutomaticZenRuleState("id", withSourceContext);
 
         verify(zenModeHelper).setAutomaticZenRuleState(eq("id"), eq(withSourceContext),
-                eq(ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI), anyInt());
+                eq(ZenModeConfig.ORIGIN_SYSTEM), anyInt());
     }
 
     /** Prepares for a zen-related test that uses a mocked {@link ZenModeHelper}. */
@@ -13049,6 +13064,100 @@
 
     @Test
     @DisableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+    public void testCancelAutogroupSummary_cancelsAllChildren() throws Exception {
+        final String originalGroupName = "originalGroup";
+        final String aggregateGroupName = "Aggregate_Test";
+        final int summaryId = Integer.MAX_VALUE;
+        // Add 2 group notifications without a summary
+        NotificationRecord nr0 =
+                generateNotificationRecord(mTestNotificationChannel, 0, originalGroupName, false);
+        NotificationRecord nr1 =
+                generateNotificationRecord(mTestNotificationChannel, 1, originalGroupName, false);
+        mService.addNotification(nr0);
+        mService.addNotification(nr1);
+        mService.mSummaryByGroupKey.remove(nr0.getGroupKey());
+
+        // GroupHelper is a mock, so make the calls it would make
+        // Add aggregate group summary
+        NotificationAttributes attr = new NotificationAttributes(GroupHelper.BASE_FLAGS,
+                mock(Icon.class), 0, VISIBILITY_PRIVATE, GROUP_ALERT_CHILDREN,
+                nr0.getChannel().getId());
+        NotificationRecord aggregateSummary = mService.createAutoGroupSummary(nr0.getUserId(),
+                nr0.getSbn().getPackageName(), nr0.getKey(), aggregateGroupName, summaryId, attr);
+        mService.addNotification(aggregateSummary);
+        nr0.setOverrideGroupKey(aggregateGroupName);
+        nr1.setOverrideGroupKey(aggregateGroupName);
+        final String fullAggregateGroupKey = nr0.getGroupKey();
+
+        // Check that the aggregate group summary was created
+        assertThat(aggregateSummary.getNotification().getGroup()).isEqualTo(aggregateGroupName);
+        assertThat(aggregateSummary.getNotification().getChannelId()).isEqualTo(
+                nr0.getChannel().getId());
+        assertThat(mService.mSummaryByGroupKey.containsKey(fullAggregateGroupKey)).isTrue();
+
+        // Cancel aggregate group summary
+        mBinderService.cancelNotificationWithTag(mPkg, mPkg, aggregateSummary.getSbn().getTag(),
+                aggregateSummary.getSbn().getId(), aggregateSummary.getSbn().getUserId());
+        waitForIdle();
+
+        // Check that child notifications are also removed
+        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(aggregateSummary));
+        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(nr0));
+        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(nr1));
+
+        // Make sure the summary was removed and not re-posted
+        assertThat(mService.getNotificationRecordCount()).isEqualTo(0);
+    }
+
+    @Test
+    @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+    public void testCancelAutogroupSummary_forceGrouping_cancelsAllChildren() throws Exception {
+        final String originalGroupName = "originalGroup";
+        final String aggregateGroupName = "Aggregate_Test";
+        final int summaryId = Integer.MAX_VALUE;
+        // Add 2 group notifications without a summary
+        NotificationRecord nr0 =
+                generateNotificationRecord(mTestNotificationChannel, 0, originalGroupName, false);
+        NotificationRecord nr1 =
+                generateNotificationRecord(mTestNotificationChannel, 1, originalGroupName, false);
+        mService.addNotification(nr0);
+        mService.addNotification(nr1);
+        mService.mSummaryByGroupKey.remove(nr0.getGroupKey());
+
+        // GroupHelper is a mock, so make the calls it would make
+        // Add aggregate group summary
+        NotificationAttributes attr = new NotificationAttributes(GroupHelper.BASE_FLAGS,
+                mock(Icon.class), 0, VISIBILITY_PRIVATE, GROUP_ALERT_CHILDREN,
+                nr0.getChannel().getId());
+        NotificationRecord aggregateSummary = mService.createAutoGroupSummary(nr0.getUserId(),
+                nr0.getSbn().getPackageName(), nr0.getKey(), aggregateGroupName, summaryId, attr);
+        mService.addNotification(aggregateSummary);
+        nr0.setOverrideGroupKey(aggregateGroupName);
+        nr1.setOverrideGroupKey(aggregateGroupName);
+        final String fullAggregateGroupKey = nr0.getGroupKey();
+
+        // Check that the aggregate group summary was created
+        assertThat(aggregateSummary.getNotification().getGroup()).isEqualTo(aggregateGroupName);
+        assertThat(aggregateSummary.getNotification().getChannelId()).isEqualTo(
+                nr0.getChannel().getId());
+        assertThat(mService.mSummaryByGroupKey.containsKey(fullAggregateGroupKey)).isTrue();
+
+        // Cancel aggregate group summary
+        mBinderService.cancelNotificationWithTag(mPkg, mPkg, aggregateSummary.getSbn().getTag(),
+                aggregateSummary.getSbn().getId(), aggregateSummary.getSbn().getUserId());
+        waitForIdle();
+
+        // Check that child notifications are also removed
+        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(aggregateSummary), any());
+        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(nr0), any());
+        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(nr1), any());
+
+        // Make sure the summary was removed and not re-posted
+        assertThat(mService.getNotificationRecordCount()).isEqualTo(0);
+    }
+
+    @Test
+    @DisableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
     public void testUngroupingOngoingAutoSummary() throws Exception {
         NotificationRecord nr0 =
                 generateNotificationRecord(mTestNotificationChannel, 0);
@@ -15508,7 +15617,7 @@
         mBinderService.setInterruptionFilter("package", INTERRUPTION_FILTER_PRIORITY, false);
 
         verify(zenModeHelper).setManualZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS), eq(null),
-                eq(ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI), anyString(), eq("package"),
+                eq(ZenModeConfig.ORIGIN_SYSTEM), anyString(), eq("package"),
                 anyInt());
     }
 
@@ -15554,7 +15663,7 @@
 
         if (canSetGlobalPolicy) {
             verify(zenModeHelper).setManualZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS), eq(null),
-                    eq(ZenModeConfig.UPDATE_ORIGIN_APP), anyString(), eq("package"), anyInt());
+                    eq(ZenModeConfig.ORIGIN_APP), anyString(), eq("package"), anyInt());
         } else {
             verify(zenModeHelper).applyGlobalZenModeAsImplicitZenRule(anyString(), anyInt(),
                     eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS));
@@ -15594,7 +15703,7 @@
                 INTERRUPTION_FILTER_PRIORITY);
 
         verify(mService.mZenModeHelper).setManualZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS),
-                eq(null), eq(ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI), anyString(),
+                eq(null), eq(ZenModeConfig.ORIGIN_SYSTEM), anyString(),
                 eq("pkg"), eq(mUid));
     }
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenChangeOrigin.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenChangeOrigin.java
new file mode 100644
index 0000000..5728788
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenChangeOrigin.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.notification;
+
+import android.service.notification.ZenModeConfig;
+
+/** Enum version of {@link ZenModeConfig.ConfigOrigin}, for test parameterization. */
+public enum ZenChangeOrigin {
+    ORIGIN_UNKNOWN(ZenModeConfig.ORIGIN_UNKNOWN),
+    ORIGIN_INIT(ZenModeConfig.ORIGIN_INIT),
+    ORIGIN_INIT_USER(ZenModeConfig.ORIGIN_INIT_USER),
+    ORIGIN_APP(ZenModeConfig.ORIGIN_APP),
+    ORIGIN_USER_IN_APP(ZenModeConfig.ORIGIN_USER_IN_APP),
+    ORIGIN_SYSTEM(ZenModeConfig.ORIGIN_SYSTEM),
+    ORIGIN_USER_IN_SYSTEMUI(ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI),
+    ORIGIN_RESTORE_BACKUP(ZenModeConfig.ORIGIN_RESTORE_BACKUP);
+
+    private final int mValue;
+
+    ZenChangeOrigin(@ZenModeConfig.ConfigOrigin int value) {
+        mValue = value;
+    }
+
+    /** Gets the {@link ZenModeConfig.ConfigOrigin} int value corresponding to the enum. */
+    @ZenModeConfig.ConfigOrigin
+    public int value() {
+        return mValue;
+    }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenEnumTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenEnumTest.java
index 8add2f9..cdceb1f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenEnumTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenEnumTest.java
@@ -20,12 +20,14 @@
 
 import android.app.AutomaticZenRule;
 import android.provider.Settings;
+import android.service.notification.ZenModeConfig;
 import android.service.notification.ZenPolicy;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.os.dnd.ActiveRuleType;
+import com.android.os.dnd.ChangeOrigin;
 import com.android.os.dnd.ChannelPolicy;
 import com.android.os.dnd.ConversationType;
 import com.android.os.dnd.PeopleType;
@@ -78,6 +80,11 @@
         testEnum(ZenPolicy.class, "PEOPLE_TYPE", PeopleType.class, "PEOPLE");
     }
 
+    @Test
+    public void testEnum_changeOrigin() {
+        testEnum(ZenModeConfig.class, "ORIGIN", ChangeOrigin.class, "ORIGIN");
+    }
+
     /**
      * Verifies that any constants (i.e. {@code public static final int} fields) named {@code
      * <apiPrefix>_SOMETHING} in {@code apiClass} are present and have the same numerical value
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 3c3c2f3..e70ed5f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
@@ -31,6 +31,10 @@
 import static android.service.notification.Condition.STATE_FALSE;
 import static android.service.notification.Condition.STATE_TRUE;
 import static android.service.notification.NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_ON;
+import static android.service.notification.ZenModeConfig.XML_VERSION_MODES_API;
+import static android.service.notification.ZenModeConfig.ZEN_TAG;
+import static android.service.notification.ZenModeConfig.ZenRule.OVERRIDE_DEACTIVATE;
+import static android.service.notification.ZenModeConfig.ZenRule.OVERRIDE_NONE;
 import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_IMPORTANT;
 import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_NONE;
 import static android.service.notification.ZenPolicy.PEOPLE_TYPE_ANYONE;
@@ -283,8 +287,8 @@
         // the default value from the zen mode config.
         Policy policy = config.toNotificationPolicy(zenPolicy);
         assertEquals(Flags.modesUi()
-                ? config.manualRule.zenPolicy.getPriorityChannelsAllowed() == STATE_ALLOW
-                : config.isAllowPriorityChannels(),
+                        ? config.manualRule.zenPolicy.getPriorityChannelsAllowed() == STATE_ALLOW
+                        : config.isAllowPriorityChannels(),
                 policy.allowPriorityChannels());
     }
 
@@ -522,7 +526,7 @@
         rule.zenMode = INTERRUPTION_FILTER;
         rule.modified = true;
         rule.name = NAME;
-        rule.snoozing = true;
+        rule.setConditionOverride(OVERRIDE_DEACTIVATE);
         rule.pkg = OWNER.getPackageName();
         rule.zenPolicy = POLICY;
 
@@ -535,7 +539,7 @@
         rule.triggerDescription = TRIGGER_DESC;
         rule.deletionInstant = Instant.ofEpochMilli(1701790147000L);
         if (Flags.modesUi()) {
-            rule.disabledOrigin = ZenModeConfig.UPDATE_ORIGIN_USER;
+            rule.disabledOrigin = ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI;
         }
 
         Parcel parcel = Parcel.obtain();
@@ -544,7 +548,7 @@
         ZenModeConfig.ZenRule parceled = new ZenModeConfig.ZenRule(parcel);
 
         assertEquals(rule.pkg, parceled.pkg);
-        assertEquals(rule.snoozing, parceled.snoozing);
+        assertEquals(rule.getConditionOverride(), parceled.getConditionOverride());
         assertEquals(rule.enabler, parceled.enabler);
         assertEquals(rule.component, parceled.component);
         assertEquals(rule.configurationActivity, parceled.configurationActivity);
@@ -623,7 +627,7 @@
         rule.zenMode = INTERRUPTION_FILTER;
         rule.modified = true;
         rule.name = NAME;
-        rule.snoozing = true;
+        rule.setConditionOverride(OVERRIDE_DEACTIVATE);
         rule.pkg = OWNER.getPackageName();
         rule.zenPolicy = POLICY;
         rule.zenDeviceEffects = new ZenDeviceEffects.Builder()
@@ -650,7 +654,7 @@
         rule.triggerDescription = TRIGGER_DESC;
         rule.deletionInstant = Instant.ofEpochMilli(1701790147000L);
         if (Flags.modesUi()) {
-            rule.disabledOrigin = ZenModeConfig.UPDATE_ORIGIN_APP;
+            rule.disabledOrigin = ZenModeConfig.ORIGIN_APP;
         }
 
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -660,7 +664,7 @@
 
         assertEquals(rule.pkg, fromXml.pkg);
         // always resets on reboot
-        assertFalse(fromXml.snoozing);
+        assertEquals(OVERRIDE_NONE, fromXml.getConditionOverride());
         //should all match original
         assertEquals(rule.component, fromXml.component);
         assertEquals(rule.configurationActivity, fromXml.configurationActivity);
@@ -991,6 +995,58 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_MODES_UI)
+    public void testConfigXml_manualRule_upgradeWhenExisting() throws Exception {
+        // prior to modes_ui, it's possible to have a non-null manual rule that doesn't have much
+        // data on it because it's meant to indicate that the manual rule is on by merely existing.
+        ZenModeConfig config = new ZenModeConfig();
+        config.manualRule = new ZenModeConfig.ZenRule();
+        config.manualRule.enabled = true;
+        config.manualRule.pkg = "android";
+        config.manualRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        config.manualRule.conditionId = ZenModeConfig.toTimeCondition(mContext, 200, mUserId).id;
+        config.manualRule.enabler = "test";
+
+        // write out entire config xml
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        writeConfigXml(config, XML_VERSION_MODES_API, /* forBackup= */ false, baos);
+        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+        ZenModeConfig fromXml = readConfigXml(bais);
+
+        // The result should have a manual rule; it should have a non-null ZenPolicy and a condition
+        // whose state is true. The conditionId and enabler data should also be preserved.
+        assertThat(fromXml.manualRule).isNotNull();
+        assertThat(fromXml.manualRule.zenPolicy).isNotNull();
+        assertThat(fromXml.manualRule.condition).isNotNull();
+        assertThat(fromXml.manualRule.condition.state).isEqualTo(STATE_TRUE);
+        assertThat(fromXml.manualRule.conditionId).isEqualTo(config.manualRule.conditionId);
+        assertThat(fromXml.manualRule.enabler).isEqualTo("test");
+        assertThat(fromXml.isManualActive()).isTrue();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MODES_UI)
+    public void testConfigXml_manualRule_doesNotTurnOnIfNotUpgrade() throws Exception {
+        // confirm that if the manual rule is already properly set up for modes_ui, it does not get
+        // turned on (set to condition with STATE_TRUE) when reading xml.
+
+        // getMutedAllConfig sets up the manual rule with a policy muting everything
+        ZenModeConfig config = getMutedAllConfig();
+        config.manualRule.condition = new Condition(Uri.EMPTY, "", STATE_FALSE, SOURCE_USER_ACTION);
+        assertThat(config.isManualActive()).isFalse();
+
+        // write out entire config xml
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        writeConfigXml(config, XML_VERSION_MODES_API, /* forBackup= */ false, baos);
+        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+        ZenModeConfig fromXml = readConfigXml(bais);
+
+        // The result should have a manual rule; it should not be changed from the previous rule.
+        assertThat(fromXml.manualRule).isEqualTo(config.manualRule);
+        assertThat(fromXml.isManualActive()).isFalse();
+    }
+
+    @Test
     public void testGetDescription_off() {
         ZenModeConfig config = new ZenModeConfig();
         if (!modesUi()) {
@@ -1061,7 +1117,6 @@
         rule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         rule.modified = true;
         rule.name = "name";
-        rule.snoozing = false;
         rule.pkg = "b";
         config.automaticRules.put("key", rule);
 
@@ -1238,4 +1293,25 @@
         parser.nextTag();
         return ZenModeConfig.readZenPolicyXml(parser);
     }
+
+    private void writeConfigXml(ZenModeConfig config, Integer version, boolean forBackup,
+            ByteArrayOutputStream os) throws IOException {
+        String tag = ZEN_TAG;
+
+        TypedXmlSerializer out = Xml.newFastSerializer();
+        out.setOutput(new BufferedOutputStream(os), "utf-8");
+        out.startDocument(null, true);
+        out.startTag(null, tag);
+        config.writeXml(out, version, forBackup);
+        out.endTag(null, tag);
+        out.endDocument();
+    }
+
+    private ZenModeConfig readConfigXml(ByteArrayInputStream is)
+            throws XmlPullParserException, IOException {
+        TypedXmlPullParser parser = Xml.newFastPullParser();
+        parser.setInput(new BufferedInputStream(is), null);
+        parser.nextTag();
+        return ZenModeConfig.readXml(parser);
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
index 9af0021..91eb2ed 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
@@ -158,7 +158,10 @@
                             RuleDiff.FIELD_ZEN_DEVICE_EFFECTS,
                             RuleDiff.FIELD_LEGACY_SUPPRESSED_EFFECTS));
         }
-        if (!(Flags.modesApi() && Flags.modesUi())) {
+        if (Flags.modesApi() && Flags.modesUi()) {
+            exemptFields.add(RuleDiff.FIELD_SNOOZING); // Obsolete.
+        } else {
+            exemptFields.add(RuleDiff.FIELD_CONDITION_OVERRIDE);
             exemptFields.add(RuleDiff.FIELD_LEGACY_SUPPRESSED_EFFECTS);
         }
         return exemptFields;
@@ -339,7 +342,7 @@
         rule.zenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         rule.modified = false;
         rule.name = "name";
-        rule.snoozing = true;
+        rule.setConditionOverride(ZenModeConfig.ZenRule.OVERRIDE_DEACTIVATE);
         rule.pkg = "a";
         if (android.app.Flags.modesApi()) {
             rule.allowManualInvocation = true;
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 d7bae45..ed8ebc8 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -46,20 +46,25 @@
 import static android.app.NotificationManager.Policy.STATE_PRIORITY_CHANNELS_BLOCKED;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.Process.SYSTEM_UID;
 import static android.provider.Settings.Global.ZEN_MODE_ALARMS;
 import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
 import static android.provider.Settings.Global.ZEN_MODE_NO_INTERRUPTIONS;
 import static android.provider.Settings.Global.ZEN_MODE_OFF;
+import static android.service.notification.Condition.SOURCE_CONTEXT;
 import static android.service.notification.Condition.SOURCE_SCHEDULE;
 import static android.service.notification.Condition.SOURCE_USER_ACTION;
 import static android.service.notification.Condition.STATE_FALSE;
 import static android.service.notification.Condition.STATE_TRUE;
-import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_APP;
-import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_INIT;
-import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_INIT_USER;
-import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI;
-import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_UNKNOWN;
-import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER;
+import static android.service.notification.ZenModeConfig.ORIGIN_APP;
+import static android.service.notification.ZenModeConfig.ORIGIN_INIT;
+import static android.service.notification.ZenModeConfig.ORIGIN_INIT_USER;
+import static android.service.notification.ZenModeConfig.ORIGIN_UNKNOWN;
+import static android.service.notification.ZenModeConfig.ORIGIN_USER_IN_APP;
+import static android.service.notification.ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI;
+import static android.service.notification.ZenModeConfig.ZenRule.OVERRIDE_ACTIVATE;
+import static android.service.notification.ZenModeConfig.ZenRule.OVERRIDE_DEACTIVATE;
+import static android.service.notification.ZenModeConfig.ZenRule.OVERRIDE_NONE;
 import static android.service.notification.ZenPolicy.PEOPLE_TYPE_CONTACTS;
 import static android.service.notification.ZenPolicy.PEOPLE_TYPE_NONE;
 import static android.service.notification.ZenPolicy.PEOPLE_TYPE_STARRED;
@@ -131,7 +136,6 @@
 import android.media.VolumePolicy;
 import android.net.Uri;
 import android.os.Parcel;
-import android.os.Process;
 import android.os.SimpleClock;
 import android.os.UserHandle;
 import android.platform.test.annotations.DisableFlags;
@@ -186,9 +190,6 @@
 import org.mockito.MockitoAnnotations;
 import org.xmlpull.v1.XmlPullParserException;
 
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
-import platform.test.runner.parameterized.Parameters;
-
 import java.io.BufferedInputStream;
 import java.io.BufferedOutputStream;
 import java.io.ByteArrayInputStream;
@@ -208,6 +209,9 @@
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
 @SmallTest
 @SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service.
 @RunWith(ParameterizedAndroidJunit4.class)
@@ -338,27 +342,6 @@
         mZenModeEventLogger.reset();
     }
 
-    private enum ChangeOrigin {
-        ORIGIN_UNKNOWN(ZenModeConfig.UPDATE_ORIGIN_UNKNOWN),
-        ORIGIN_INIT(ZenModeConfig.UPDATE_ORIGIN_INIT),
-        ORIGIN_INIT_USER(ZenModeConfig.UPDATE_ORIGIN_INIT_USER),
-        ORIGIN_USER(ZenModeConfig.UPDATE_ORIGIN_USER),
-        ORIGIN_APP(ZenModeConfig.UPDATE_ORIGIN_APP),
-        ORIGIN_SYSTEM_OR_SYSTEMUI(ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI),
-        ORIGIN_RESTORE_BACKUP(ZenModeConfig.UPDATE_ORIGIN_RESTORE_BACKUP);
-
-        private final int mValue;
-
-        ChangeOrigin(@ZenModeConfig.ConfigChangeOrigin int value) {
-            mValue = value;
-        }
-
-        @ZenModeConfig.ConfigChangeOrigin
-        public int value() {
-            return mValue;
-        }
-    }
-
     private XmlResourceParser getDefaultConfigParser() throws IOException, XmlPullParserException {
         String xml = "<zen version=\"10\">\n"
                 + "<allow alarms=\"true\" media=\"true\" system=\"false\" calls=\"true\" "
@@ -395,8 +378,8 @@
         mZenModeHelper.writeXml(serializer, false, version, UserHandle.USER_ALL);
         serializer.endDocument();
         serializer.flush();
-        mZenModeHelper.setConfig(new ZenModeConfig(), null, UPDATE_ORIGIN_INIT, "writing xml",
-                Process.SYSTEM_UID);
+        mZenModeHelper.setConfig(new ZenModeConfig(), null, ORIGIN_INIT, "writing xml",
+                SYSTEM_UID);
         return baos;
     }
 
@@ -411,8 +394,8 @@
         serializer.flush();
         ZenModeConfig newConfig = new ZenModeConfig();
         newConfig.user = userId;
-        mZenModeHelper.setConfig(newConfig, null, UPDATE_ORIGIN_INIT, "writing xml",
-                Process.SYSTEM_UID);
+        mZenModeHelper.setConfig(newConfig, null, ORIGIN_INIT, "writing xml",
+                SYSTEM_UID);
         return baos;
     }
 
@@ -705,13 +688,13 @@
                 .setInterruptionFilter(INTERRUPTION_FILTER_NONE)
                 .build();
         String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                azr, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reason", Process.SYSTEM_UID);
+                azr, ZenModeConfig.ORIGIN_SYSTEM, "reason", SYSTEM_UID);
 
         // Enable rule
         mZenModeHelper.setAutomaticZenRuleState(ruleId,
                 new Condition(azr.getConditionId(), "", STATE_TRUE),
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
-                Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM,
+                SYSTEM_UID);
 
         // Confirm that the consolidated policy doesn't allow anything
         NotificationManager.Policy policy = mZenModeHelper.getConsolidatedNotificationPolicy();
@@ -739,13 +722,13 @@
                 .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
                 .build();
         String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                azr, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reason", Process.SYSTEM_UID);
+                azr, ZenModeConfig.ORIGIN_SYSTEM, "reason", SYSTEM_UID);
 
         // Enable rule
         mZenModeHelper.setAutomaticZenRuleState(ruleId,
                 new Condition(azr.getConditionId(), "", STATE_TRUE),
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
-                Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM,
+                SYSTEM_UID);
 
         // Confirm that the consolidated policy allows only alarms and media and nothing else
         NotificationManager.Policy policy = mZenModeHelper.getConsolidatedNotificationPolicy();
@@ -819,7 +802,7 @@
         when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
         // Set zen to priority-only with all notification sounds muted (so ringer will be muted)
         Policy totalSilence = new Policy(0, 0, 0);
-        mZenModeHelper.setNotificationPolicy(totalSilence, UPDATE_ORIGIN_APP, 1);
+        mZenModeHelper.setNotificationPolicy(totalSilence, ORIGIN_APP, 1);
         mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
 
         // 2. verify ringer is unchanged
@@ -857,7 +840,7 @@
         // in priority only mode:
         // ringtone, notification and system streams are affected by ringer mode
         mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, Uri.EMPTY,
-                UPDATE_ORIGIN_APP, "test", "caller", 1);
+                ORIGIN_APP, "test", "caller", 1);
         ZenModeHelper.RingerModeDelegate ringerModeDelegateRingerMuted =
                 mZenModeHelper.new RingerModeDelegate();
 
@@ -873,9 +856,9 @@
 
         // even when ringer is muted (since all ringer sounds cannot bypass DND),
         // system stream is still affected by ringer mode
-        mZenModeHelper.setNotificationPolicy(new Policy(0, 0, 0), UPDATE_ORIGIN_APP, 1);
+        mZenModeHelper.setNotificationPolicy(new Policy(0, 0, 0), ORIGIN_APP, 1);
         mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, Uri.EMPTY,
-                UPDATE_ORIGIN_APP, "test", "caller", 1);
+                ORIGIN_APP, "test", "caller", 1);
         ZenModeHelper.RingerModeDelegate ringerModeDelegateRingerNotMuted =
                 mZenModeHelper.new RingerModeDelegate();
 
@@ -983,7 +966,7 @@
         when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_SILENT);
         for (int i = 0; i < 3; i++) {
             mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, Uri.EMPTY,
-                    UPDATE_ORIGIN_APP, "test", "caller", 1);
+                    ORIGIN_APP, "test", "caller", 1);
         }
         verify(mAudioManager, never()).setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL,
                 mZenModeHelper.TAG);
@@ -1008,7 +991,7 @@
         for (int i = 0; i < 3; i++) {
             // if zen doesn't change, zen should not reapply itself to the ringer
             mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, Uri.EMPTY,
-                    UPDATE_ORIGIN_APP, "test", "caller", 1);
+                    ORIGIN_APP, "test", "caller", 1);
         }
         verify(mAudioManager, never()).setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL,
                 mZenModeHelper.TAG);
@@ -1033,7 +1016,7 @@
         for (int i = 0; i < 3; i++) {
             // if zen doesn't change, zen should not reapply itself to the ringer
             mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, Uri.EMPTY,
-                    UPDATE_ORIGIN_APP, "test", "caller", 1);
+                    ORIGIN_APP, "test", "caller", 1);
         }
         verify(mAudioManager, never()).setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL,
                 mZenModeHelper.TAG);
@@ -1048,7 +1031,8 @@
         reset(mAudioManager);
 
         // Turn manual zen mode on
-        mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, UPDATE_ORIGIN_APP,
+        mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+                ORIGIN_APP,
                 null, "test", CUSTOM_PKG_UID);
 
         // audio manager shouldn't do anything until the handler processes its messages
@@ -1076,14 +1060,14 @@
         mZenModeHelper.setManualZenMode(
                 ZEN_MODE_IMPORTANT_INTERRUPTIONS,
                 null,
-                UPDATE_ORIGIN_APP,
+                ORIGIN_APP,
                 null,
                 "test",
                 CUSTOM_PKG_UID);
         mZenModeHelper.setManualZenMode(
                 ZEN_MODE_IMPORTANT_INTERRUPTIONS,
                 null,
-                UPDATE_ORIGIN_APP,
+                ORIGIN_APP,
                 null,
                 "test",
                 CUSTOM_PKG_UID);
@@ -1108,15 +1092,15 @@
                         | PRIORITY_CATEGORY_MESSAGES | PRIORITY_CATEGORY_REPEAT_CALLERS
                         | PRIORITY_CATEGORY_CONVERSATIONS, PRIORITY_SENDERS_STARRED,
                         PRIORITY_SENDERS_STARRED, 0, CONVERSATION_SENDERS_ANYONE),
-                UPDATE_ORIGIN_UNKNOWN,
+                ORIGIN_UNKNOWN,
                 1);
         mZenModeHelper.setManualZenRuleDeviceEffects(new ZenDeviceEffects.Builder()
                 .setShouldDimWallpaper(true)
                 .setShouldDisplayGrayscale(true)
                 .setShouldUseNightMode(true)
-                .build(), UPDATE_ORIGIN_UNKNOWN, "test", 1);
+                .build(), ORIGIN_UNKNOWN, "test", 1);
         mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, Uri.EMPTY,
-                UPDATE_ORIGIN_UNKNOWN, "test", "me", 1);
+                ORIGIN_UNKNOWN, "test", "me", 1);
 
         ZenModeConfig actual = mZenModeHelper.mConfig.copy();
 
@@ -1130,13 +1114,13 @@
                         | PRIORITY_CATEGORY_CONVERSATIONS, PRIORITY_SENDERS_STARRED,
                         PRIORITY_SENDERS_STARRED, SUPPRESSED_EFFECT_BADGE,
                         CONVERSATION_SENDERS_ANYONE),
-                UPDATE_ORIGIN_UNKNOWN, 1);
+                ORIGIN_UNKNOWN, 1);
         mZenModeHelper.setManualZenRuleDeviceEffects(new ZenDeviceEffects.Builder()
                 .setShouldDimWallpaper(true)
                 .setShouldDisplayGrayscale(true)
-                .build(), UPDATE_ORIGIN_UNKNOWN, "test", 1);
+                .build(), ORIGIN_UNKNOWN, "test", 1);
         mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, Uri.EMPTY,
-                UPDATE_ORIGIN_UNKNOWN, "test", "me", 1);
+                ORIGIN_UNKNOWN, "test", "me", 1);
 
         ZenModeConfig expected = mZenModeHelper.mConfig.copy();
         if (Flags.modesUi()) {
@@ -1157,7 +1141,7 @@
     @Test
     public void testProto() throws InvalidProtocolBufferException {
         mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
-                Flags.modesApi() ? UPDATE_ORIGIN_USER : UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+                Flags.modesApi() ? ORIGIN_USER_IN_SYSTEMUI : ZenModeConfig.ORIGIN_SYSTEM,
                 null, "test", CUSTOM_PKG_UID);
 
         mZenModeHelper.mConfig.automaticRules = new ArrayMap<>(); // no automatic rules
@@ -1381,7 +1365,7 @@
         List<StatsEvent> events = new LinkedList<>();
 
         mZenModeHelper.pullRules(events);
-        mZenModeHelper.removeAutomaticZenRule(CUSTOM_RULE_ID, UPDATE_ORIGIN_APP, "test",
+        mZenModeHelper.removeAutomaticZenRule(CUSTOM_RULE_ID, ORIGIN_APP, "test",
                 CUSTOM_PKG_UID);
         assertTrue(-1
                 == mZenModeHelper.mRulesUidCache.getOrDefault(CUSTOM_PKG_NAME + "|" + 0, -1));
@@ -1410,7 +1394,8 @@
     public void testProtoWithManualRule() throws Exception {
         setupZenConfig();
         mZenModeHelper.mConfig.automaticRules = getCustomAutomaticRules();
-        mZenModeHelper.setManualZenMode(INTERRUPTION_FILTER_PRIORITY, Uri.EMPTY, UPDATE_ORIGIN_APP,
+        mZenModeHelper.setManualZenMode(INTERRUPTION_FILTER_PRIORITY, Uri.EMPTY,
+                ORIGIN_APP,
                 "test", "me", 1);
 
         List<StatsEvent> events = new LinkedList<>();
@@ -1437,14 +1422,14 @@
         Policy policy = new Policy(PRIORITY_CATEGORY_MEDIA | PRIORITY_CATEGORY_ALARMS, 0, 0);
         config10.applyNotificationPolicy(policy);
         config10.user = 10;
-        mZenModeHelper.setConfig(config10, null, UPDATE_ORIGIN_INIT, "writeXml",
-                Process.SYSTEM_UID);
+        mZenModeHelper.setConfig(config10, null, ORIGIN_INIT, "writeXml",
+                SYSTEM_UID);
         ZenModeConfig config11 = mZenModeHelper.mConfig.copy();
         config11.user = 11;
         policy = new Policy(0, 0, 0);
         config11.applyNotificationPolicy(policy);
-        mZenModeHelper.setConfig(config11, null, UPDATE_ORIGIN_INIT, "writeXml",
-                Process.SYSTEM_UID);
+        mZenModeHelper.setConfig(config11, null, ORIGIN_INIT, "writeXml",
+                SYSTEM_UID);
 
         // Backup user 10 and reset values.
         ByteArrayOutputStream baos = writeXmlAndPurgeForUser(null, 10);
@@ -1514,6 +1499,30 @@
     }
 
     @Test
+    public void testReadXmlRestore_doesNotEnableManualRule() throws Exception {
+        setupZenConfig();
+
+        // Turn on manual zen mode
+        mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+                ORIGIN_USER_IN_SYSTEMUI, "", "someCaller", SYSTEM_UID);
+        ZenModeConfig original = mZenModeHelper.mConfig.copy();
+        assertThat(original.isManualActive()).isTrue();
+
+        ByteArrayOutputStream baos = writeXmlAndPurge(null);
+        TypedXmlPullParser parser = getParserForByteStream(baos);
+        mZenModeHelper.readXml(parser, true, UserHandle.USER_ALL);
+
+        ZenModeConfig result = mZenModeHelper.getConfig();
+        assertThat(result.isManualActive()).isFalse();
+
+        // confirm that we do still keep policy information, modes_ui only; prior to modes_ui the
+        // entire rule is intentionally cleared
+        if (Flags.modesUi()) {
+            assertThat(result.manualRule.zenPolicy).isNotNull();
+        }
+    }
+
+    @Test
     public void testWriteXmlWithZenPolicy() throws Exception {
         final String ruleId = "customRule";
         setupZenConfig();
@@ -2307,7 +2316,7 @@
                     NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
             // We need the package name to be something that's not "android" so there aren't any
             // existing rules under that package.
-            String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, UPDATE_ORIGIN_APP,
+            String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, ORIGIN_APP,
                     "test", CUSTOM_PKG_UID);
             assertNotNull(id);
         }
@@ -2318,7 +2327,7 @@
                     ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
                     new ZenPolicy.Builder().build(),
                     NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-            String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, UPDATE_ORIGIN_APP,
+            String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, ORIGIN_APP,
                     "test", CUSTOM_PKG_UID);
             fail("allowed too many rules to be created");
         } catch (IllegalArgumentException e) {
@@ -2339,7 +2348,7 @@
                     ZenModeConfig.toScheduleConditionId(si),
                     new ZenPolicy.Builder().build(),
                     NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-            String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, UPDATE_ORIGIN_APP,
+            String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, ORIGIN_APP,
                     "test", CUSTOM_PKG_UID);
             assertNotNull(id);
         }
@@ -2350,7 +2359,7 @@
                     ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
                     new ZenPolicy.Builder().build(),
                     NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-            String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, UPDATE_ORIGIN_APP,
+            String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, ORIGIN_APP,
                     "test", CUSTOM_PKG_UID);
             fail("allowed too many rules to be created");
         } catch (IllegalArgumentException e) {
@@ -2371,7 +2380,7 @@
                     ZenModeConfig.toScheduleConditionId(si),
                     new ZenPolicy.Builder().build(),
                     NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-            String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, UPDATE_ORIGIN_APP,
+            String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, ORIGIN_APP,
                     "test", CUSTOM_PKG_UID);
             assertNotNull(id);
         }
@@ -2382,7 +2391,7 @@
                     ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
                     new ZenPolicy.Builder().build(),
                     NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-            String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, UPDATE_ORIGIN_APP,
+            String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, ORIGIN_APP,
                     "test", CUSTOM_PKG_UID);
             fail("allowed too many rules to be created");
         } catch (IllegalArgumentException e) {
@@ -2399,7 +2408,7 @@
                 new ZenPolicy.Builder().build(),
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id = mZenModeHelper.addAutomaticZenRule("android", zenRule,
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
 
         assertTrue(id != null);
         ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id);
@@ -2420,7 +2429,7 @@
                 ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id = mZenModeHelper.addAutomaticZenRule("android", zenRule,
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
 
         assertTrue(id != null);
         ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id);
@@ -2446,7 +2455,7 @@
                 ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id1 = mZenModeHelper.addAutomaticZenRule("android", zenRule1,
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
 
         // Zen rule with partially-filled policy: should get all of the filled fields set, and the
         // rest filled with default state
@@ -2461,7 +2470,7 @@
                         .build(),
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id2 = mZenModeHelper.addAutomaticZenRule("android", zenRule2,
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
 
         // rule 1 should exist
         assertThat(id1).isNotNull();
@@ -2506,11 +2515,11 @@
                 new ZenPolicy.Builder().build(),
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
 
-        String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, UPDATE_ORIGIN_APP, "test",
+        String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, ORIGIN_APP, "test",
                 CUSTOM_PKG_UID);
         mZenModeHelper.setAutomaticZenRuleState(zenRule.getConditionId(),
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE),
-                UPDATE_ORIGIN_APP,
+                ORIGIN_APP,
                 CUSTOM_PKG_UID);
 
         ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id);
@@ -2526,7 +2535,7 @@
                 new ZenPolicy.Builder().build(),
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
 
-        String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, UPDATE_ORIGIN_APP, "test",
+        String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, ORIGIN_APP, "test",
                 CUSTOM_PKG_UID);
 
         AutomaticZenRule zenRule2 = new AutomaticZenRule("NEW",
@@ -2536,7 +2545,7 @@
                 new ZenPolicy.Builder().build(),
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
 
-        mZenModeHelper.updateAutomaticZenRule(id, zenRule2, UPDATE_ORIGIN_APP, "", CUSTOM_PKG_UID);
+        mZenModeHelper.updateAutomaticZenRule(id, zenRule2, ORIGIN_APP, "", CUSTOM_PKG_UID);
 
         ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id);
         assertEquals("NEW", ruleInConfig.name);
@@ -2551,7 +2560,7 @@
                 new ZenPolicy.Builder().build(),
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
 
-        String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, UPDATE_ORIGIN_APP, "test",
+        String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, ORIGIN_APP, "test",
                 CUSTOM_PKG_UID);
 
         assertTrue(id != null);
@@ -2559,7 +2568,7 @@
         assertTrue(ruleInConfig != null);
         assertEquals(zenRule.getName(), ruleInConfig.name);
 
-        mZenModeHelper.removeAutomaticZenRule(id, UPDATE_ORIGIN_APP, "test", CUSTOM_PKG_UID);
+        mZenModeHelper.removeAutomaticZenRule(id, ORIGIN_APP, "test", CUSTOM_PKG_UID);
         assertNull(mZenModeHelper.mConfig.automaticRules.get(id));
     }
 
@@ -2571,7 +2580,7 @@
                 ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
                 new ZenPolicy.Builder().build(),
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-        String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, UPDATE_ORIGIN_APP, "test",
+        String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, ORIGIN_APP, "test",
                 CUSTOM_PKG_UID);
 
         assertTrue(id != null);
@@ -2579,7 +2588,7 @@
         assertTrue(ruleInConfig != null);
         assertEquals(zenRule.getName(), ruleInConfig.name);
 
-        mZenModeHelper.removeAutomaticZenRules(mContext.getPackageName(), UPDATE_ORIGIN_APP, "test",
+        mZenModeHelper.removeAutomaticZenRules(mContext.getPackageName(), ORIGIN_APP, "test",
                 CUSTOM_PKG_UID);
         assertNull(mZenModeHelper.mConfig.automaticRules.get(id));
     }
@@ -2596,17 +2605,17 @@
                 sharedUri,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id = mZenModeHelper.addAutomaticZenRule(mPkg, zenRule,
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
         AutomaticZenRule zenRule2 = new AutomaticZenRule("name2",
                 new ComponentName(mPkg, "ScheduleConditionProvider"),
                 sharedUri,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id2 = mZenModeHelper.addAutomaticZenRule(mPkg, zenRule2,
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
 
         Condition condition = new Condition(sharedUri, "", STATE_TRUE);
         mZenModeHelper.setAutomaticZenRuleState(sharedUri, condition,
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
 
         for (ZenModeConfig.ZenRule rule : mZenModeHelper.mConfig.automaticRules.values()) {
             if (rule.id.equals(id)) {
@@ -2621,7 +2630,7 @@
 
         condition = new Condition(sharedUri, "", STATE_FALSE);
         mZenModeHelper.setAutomaticZenRuleState(sharedUri, condition,
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
 
         for (ZenModeConfig.ZenRule rule : mZenModeHelper.mConfig.automaticRules.values()) {
             if (rule.id.equals(id)) {
@@ -2656,7 +2665,7 @@
                         .setOwner(OWNER)
                         .setDeviceEffects(zde)
                         .build(),
-                UPDATE_ORIGIN_APP, "reasons", 0);
+                ORIGIN_APP, "reasons", 0);
 
         AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
         assertThat(savedRule.getDeviceEffects()).isEqualTo(
@@ -2689,7 +2698,7 @@
                         .setOwner(OWNER)
                         .setDeviceEffects(zde)
                         .build(),
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reasons", 0);
+                ZenModeConfig.ORIGIN_SYSTEM, "reasons", 0);
 
         AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
         assertThat(savedRule.getDeviceEffects()).isEqualTo(zde);
@@ -2716,7 +2725,7 @@
                         .setOwner(OWNER)
                         .setDeviceEffects(zde)
                         .build(),
-                UPDATE_ORIGIN_USER,
+                ORIGIN_USER_IN_SYSTEMUI,
                 "reasons", 0);
 
         AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
@@ -2736,7 +2745,7 @@
                         .setOwner(OWNER)
                         .setDeviceEffects(original)
                         .build(),
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reasons", 0);
+                ZenModeConfig.ORIGIN_SYSTEM, "reasons", 0);
 
         ZenDeviceEffects updateFromApp = new ZenDeviceEffects.Builder()
                 .setShouldUseNightMode(true) // Good
@@ -2748,7 +2757,7 @@
                         .setOwner(OWNER)
                         .setDeviceEffects(updateFromApp)
                         .build(),
-                UPDATE_ORIGIN_APP, "reasons", 0);
+                ORIGIN_APP, "reasons", 0);
 
         AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
         assertThat(savedRule.getDeviceEffects()).isEqualTo(
@@ -2770,7 +2779,7 @@
                         .setOwner(OWNER)
                         .setDeviceEffects(original)
                         .build(),
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reasons", 0);
+                ZenModeConfig.ORIGIN_SYSTEM, "reasons", 0);
 
         ZenDeviceEffects updateFromSystem = new ZenDeviceEffects.Builder()
                 .setShouldUseNightMode(true) // Good
@@ -2780,7 +2789,7 @@
                 new AutomaticZenRule.Builder("Rule", CONDITION_ID)
                         .setDeviceEffects(updateFromSystem)
                         .build(),
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reasons", 0);
+                ZenModeConfig.ORIGIN_SYSTEM, "reasons", 0);
 
         AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
         assertThat(savedRule.getDeviceEffects()).isEqualTo(updateFromSystem);
@@ -2797,7 +2806,7 @@
                         .setOwner(OWNER)
                         .setDeviceEffects(original)
                         .build(),
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reasons", 0);
+                ZenModeConfig.ORIGIN_SYSTEM, "reasons", 0);
 
         ZenDeviceEffects updateFromUser = new ZenDeviceEffects.Builder()
                 .setShouldUseNightMode(true)
@@ -2810,7 +2819,7 @@
                 new AutomaticZenRule.Builder("Rule", CONDITION_ID)
                         .setDeviceEffects(updateFromUser)
                         .build(),
-                UPDATE_ORIGIN_USER, "reasons", 0);
+                ORIGIN_USER_IN_SYSTEMUI, "reasons", 0);
 
         AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
 
@@ -2829,13 +2838,13 @@
                                 .allowCalls(ZenPolicy.PEOPLE_TYPE_NONE) // default is stars
                                 .build())
                         .build(),
-                UPDATE_ORIGIN_APP, "reasons", 0);
+                ORIGIN_APP, "reasons", 0);
 
         mZenModeHelper.updateAutomaticZenRule(ruleId,
                 new AutomaticZenRule.Builder("Rule", CONDITION_ID)
                         // no zen policy
                         .build(),
-                UPDATE_ORIGIN_APP, "reasons", 0);
+                ORIGIN_APP, "reasons", 0);
 
         AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
         assertThat(savedRule.getZenPolicy().getPriorityCategoryCalls())
@@ -2857,7 +2866,7 @@
                                 .allowReminders(true)
                                 .build())
                         .build(),
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reasons", 0);
+                ZenModeConfig.ORIGIN_SYSTEM, "reasons", 0);
 
         mZenModeHelper.updateAutomaticZenRule(ruleId,
                 new AutomaticZenRule.Builder("Rule", CONDITION_ID)
@@ -2865,7 +2874,7 @@
                                 .allowCalls(ZenPolicy.PEOPLE_TYPE_CONTACTS)
                                 .build())
                         .build(),
-                UPDATE_ORIGIN_APP, "reasons", 0);
+                ORIGIN_APP, "reasons", 0);
 
         AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
         assertThat(savedRule.getZenPolicy().getPriorityCategoryCalls())
@@ -2893,7 +2902,8 @@
         AutomaticZenRule bedtime = new AutomaticZenRule.Builder("Bedtime Mode (TM)", CONDITION_ID)
                 .setType(TYPE_BEDTIME)
                 .build();
-        String bedtimeRuleId = mZenModeHelper.addAutomaticZenRule("pkg", bedtime, UPDATE_ORIGIN_APP,
+        String bedtimeRuleId = mZenModeHelper.addAutomaticZenRule("pkg", bedtime,
+                ORIGIN_APP,
                 "reason", CUSTOM_PKG_UID);
 
         assertThat(mZenModeHelper.mConfig.automaticRules.keySet()).containsExactly(bedtimeRuleId);
@@ -2913,7 +2923,8 @@
         AutomaticZenRule bedtime = new AutomaticZenRule.Builder("Bedtime Mode (TM)", CONDITION_ID)
                 .setType(TYPE_BEDTIME)
                 .build();
-        String bedtimeRuleId = mZenModeHelper.addAutomaticZenRule("pkg", bedtime, UPDATE_ORIGIN_APP,
+        String bedtimeRuleId = mZenModeHelper.addAutomaticZenRule("pkg", bedtime,
+                ORIGIN_APP,
                 "reason", CUSTOM_PKG_UID);
 
         assertThat(mZenModeHelper.mConfig.automaticRules.keySet()).containsExactly(
@@ -2934,7 +2945,8 @@
         AutomaticZenRule bedtime = new AutomaticZenRule.Builder("Bedtime Mode (TM)", CONDITION_ID)
                 .setType(TYPE_BEDTIME)
                 .build();
-        String bedtimeRuleId = mZenModeHelper.addAutomaticZenRule("pkg", bedtime, UPDATE_ORIGIN_APP,
+        String bedtimeRuleId = mZenModeHelper.addAutomaticZenRule("pkg", bedtime,
+                ORIGIN_APP,
                 "reason", CUSTOM_PKG_UID);
 
         assertThat(mZenModeHelper.mConfig.automaticRules.keySet()).containsExactly(
@@ -2948,15 +2960,15 @@
 
         // note that caller=null because that's how it comes in from NMS.setZenMode
         mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null, Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, "", null, SYSTEM_UID);
 
         // confirm that setting zen mode via setManualZenMode changed the zen mode correctly
         assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeHelper.mZenMode);
         assertEquals(true, mZenModeHelper.mConfig.manualRule.allowManualInvocation);
 
         // and also that it works to turn it back off again
-        mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
-                "", null, Process.SYSTEM_UID);
+        mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, ZenModeConfig.ORIGIN_SYSTEM,
+                "", null, SYSTEM_UID);
 
         assertEquals(Global.ZEN_MODE_OFF, mZenModeHelper.mZenMode);
     }
@@ -2965,79 +2977,87 @@
     @EnableFlags(FLAG_MODES_API)
     @DisableFlags(FLAG_MODES_UI)
     public void setManualZenMode_off_snoozesActiveRules() {
-        for (ChangeOrigin origin : ChangeOrigin.values()) {
+        for (ZenChangeOrigin origin : ZenChangeOrigin.values()) {
             // Start with an active rule and an inactive rule.
             mZenModeHelper.mConfig.automaticRules.clear();
             AutomaticZenRule activeRule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
                     .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
                     .build();
             String activeRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                    activeRule, UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
-            mZenModeHelper.setAutomaticZenRuleState(activeRuleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+                    activeRule, ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+            mZenModeHelper.setAutomaticZenRuleState(activeRuleId, CONDITION_TRUE,
+                    ORIGIN_APP,
                     CUSTOM_PKG_UID);
             AutomaticZenRule inactiveRule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
                     .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
                     .build();
             String inactiveRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                    inactiveRule, UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+                    inactiveRule, ORIGIN_APP, "add it", CUSTOM_PKG_UID);
 
             assertWithMessage("Failure for origin " + origin.name())
                     .that(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
 
             // User turns DND off.
             mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, origin.value(),
-                    "snoozing", "systemui", Process.SYSTEM_UID);
+                    "snoozing", "systemui", SYSTEM_UID);
             assertWithMessage("Failure for origin " + origin.name())
                     .that(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
             assertWithMessage("Failure for origin " + origin.name())
-                    .that(mZenModeHelper.mConfig.automaticRules.get(activeRuleId).snoozing)
-                    .isTrue();
+                    .that(mZenModeHelper.mConfig.automaticRules
+                            .get(activeRuleId).getConditionOverride())
+                    .isEqualTo(OVERRIDE_DEACTIVATE);
             assertWithMessage("Failure for origin " + origin.name())
-                    .that(mZenModeHelper.mConfig.automaticRules.get(inactiveRuleId).snoozing)
-                    .isFalse();
+                    .that(mZenModeHelper.mConfig.automaticRules
+                            .get(inactiveRuleId).getConditionOverride())
+                    .isEqualTo(OVERRIDE_NONE);
         }
     }
 
     @Test
     @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
-    public void setManualZenMode_off_doesNotSnoozeRulesIfFromUser() {
-        for (ChangeOrigin origin : ChangeOrigin.values()) {
+    public void setManualZenMode_off_doesNotSnoozeRulesIfFromUserInSystemUi() {
+        for (ZenChangeOrigin origin : ZenChangeOrigin.values()) {
             // Start with an active rule and an inactive rule
             mZenModeHelper.mConfig.automaticRules.clear();
             AutomaticZenRule activeRule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
                     .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
                     .build();
             String activeRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                    activeRule, UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
-            mZenModeHelper.setAutomaticZenRuleState(activeRuleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+                    activeRule, ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+            mZenModeHelper.setAutomaticZenRuleState(activeRuleId, CONDITION_TRUE,
+                    ORIGIN_APP,
                     CUSTOM_PKG_UID);
             AutomaticZenRule inactiveRule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
                     .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
                     .build();
             String inactiveRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                    inactiveRule, UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+                    inactiveRule, ORIGIN_APP, "add it", CUSTOM_PKG_UID);
 
             assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
 
             // User turns DND off.
             mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, origin.value(),
-                    "snoozing", "systemui", Process.SYSTEM_UID);
+                    "snoozing", "systemui", SYSTEM_UID);
             ZenModeConfig config = mZenModeHelper.mConfig;
-            if (origin == ChangeOrigin.ORIGIN_USER) {
+            if (origin == ZenChangeOrigin.ORIGIN_USER_IN_SYSTEMUI) {
                 // Other rule was unaffected.
                 assertWithMessage("Failure for origin " + origin.name()).that(
                         mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
                 assertWithMessage("Failure for origin " + origin.name()).that(
-                        config.automaticRules.get(activeRuleId).snoozing).isFalse();
+                        config.automaticRules.get(activeRuleId).getConditionOverride())
+                        .isEqualTo(OVERRIDE_NONE);
                 assertWithMessage("Failure for origin " + origin.name()).that(
-                        config.automaticRules.get(inactiveRuleId).snoozing).isFalse();
+                        config.automaticRules.get(inactiveRuleId).getConditionOverride())
+                        .isEqualTo(OVERRIDE_NONE);
             } else {
                 assertWithMessage("Failure for origin " + origin.name()).that(
                         mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
                 assertWithMessage("Failure for origin " + origin.name()).that(
-                        config.automaticRules.get(activeRuleId).snoozing).isTrue();
+                        config.automaticRules.get(activeRuleId).getConditionOverride())
+                        .isEqualTo(OVERRIDE_DEACTIVATE);
                 assertWithMessage("Failure for origin " + origin.name()).that(
-                        config.automaticRules.get(inactiveRuleId).snoozing).isFalse();
+                        config.automaticRules.get(inactiveRuleId).getConditionOverride())
+                        .isEqualTo(OVERRIDE_NONE);
             }
         }
     }
@@ -3048,14 +3068,14 @@
 
         // note that caller=null because that's how it comes in from NMS.setZenMode
         mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null, Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, "", null, SYSTEM_UID);
 
         // confirm that setting zen mode via setManualZenMode changed the zen mode correctly
         assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeHelper.mZenMode);
 
         // and also that it works to turn it back off again
-        mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "",
-                null, Process.SYSTEM_UID);
+        mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, ZenModeConfig.ORIGIN_SYSTEM, "",
+                null, SYSTEM_UID);
 
         assertEquals(ZEN_MODE_OFF, mZenModeHelper.mZenMode);
     }
@@ -3069,12 +3089,12 @@
         // Turn zen mode on (to important_interruptions)
         // Need to additionally call the looper in order to finish the post-apply-config process
         mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
-                Flags.modesApi() ? UPDATE_ORIGIN_USER : UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null,
-                Process.SYSTEM_UID);
+                Flags.modesApi() ? ORIGIN_USER_IN_SYSTEMUI : ZenModeConfig.ORIGIN_SYSTEM, "",
+                null, SYSTEM_UID);
 
         // Now turn zen mode off, but via a different package UID -- this should get registered as
         // "not an action by the user" because some other app is changing zen mode
-        mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, UPDATE_ORIGIN_APP, "", null,
+        mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, ORIGIN_APP, "", null,
                 CUSTOM_PKG_UID);
 
         // In total, this should be 2 loggable changes
@@ -3100,11 +3120,11 @@
         assertThat(mZenModeEventLogger.getFromSystemOrSystemUi(0)).isEqualTo(
                 !(Flags.modesUi() || Flags.modesApi()));
         assertTrue(mZenModeEventLogger.getIsUserAction(0));
-        assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(0));
+        assertEquals(SYSTEM_UID, mZenModeEventLogger.getPackageUid(0));
         checkDndProtoMatchesSetupZenConfig(mZenModeEventLogger.getPolicyProto(0));
         // change origin should be populated only under modes_ui
         assertThat(mZenModeEventLogger.getChangeOrigin(0)).isEqualTo(
-                (Flags.modesApi() && Flags.modesUi()) ? UPDATE_ORIGIN_USER : 0);
+                (Flags.modesApi() && Flags.modesUi()) ? ORIGIN_USER_IN_SYSTEMUI : 0);
 
         // and from turning zen mode off:
         //   - event ID: DND_TURNED_OFF
@@ -3128,7 +3148,7 @@
             checkDndProtoMatchesSetupZenConfig(mZenModeEventLogger.getPolicyProto(1));
         }
         assertThat(mZenModeEventLogger.getChangeOrigin(1)).isEqualTo(
-                Flags.modesUi() ? UPDATE_ORIGIN_APP : 0);
+                Flags.modesUi() ? ORIGIN_APP : 0);
     }
 
     @Test
@@ -3145,7 +3165,7 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
-                UPDATE_ORIGIN_APP, "test", CUSTOM_PKG_UID);
+                ORIGIN_APP, "test", CUSTOM_PKG_UID);
 
         // Event 1: Mimic the rule coming on automatically by setting the Condition to STATE_TRUE
         // Note that pre-modes_ui, this event serves as a test that automatic changes to an app's
@@ -3153,14 +3173,14 @@
         // modes_ui is true, we opt to trust the provided change origin.
         mZenModeHelper.setAutomaticZenRuleState(id,
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE),
-                Flags.modesUi() ? UPDATE_ORIGIN_APP : UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+                Flags.modesUi() ? ORIGIN_APP : ZenModeConfig.ORIGIN_SYSTEM,
                 CUSTOM_PKG_UID);
 
         // Event 2: "User" turns off the automatic rule (sets it to not enabled)
         zenRule.setEnabled(false);
         mZenModeHelper.updateAutomaticZenRule(id, zenRule,
-                Flags.modesApi() ? UPDATE_ORIGIN_USER : UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "",
-                Process.SYSTEM_UID);
+                Flags.modesApi() ? ORIGIN_USER_IN_SYSTEMUI : ZenModeConfig.ORIGIN_SYSTEM, "",
+                SYSTEM_UID);
 
         AutomaticZenRule systemRule = new AutomaticZenRule("systemRule",
                 null,
@@ -3169,18 +3189,18 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String systemId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), systemRule,
-                Flags.modesApi() ? UPDATE_ORIGIN_USER : UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test",
-                Process.SYSTEM_UID);
+                Flags.modesApi() ? ORIGIN_USER_IN_SYSTEMUI : ZenModeConfig.ORIGIN_SYSTEM, "test",
+                SYSTEM_UID);
 
         // Event 3: turn on the system rule
         mZenModeHelper.setAutomaticZenRuleState(systemId,
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE),
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
 
         // Event 4: "User" deletes the rule
         mZenModeHelper.removeAutomaticZenRule(systemId,
-                Flags.modesApi() ? UPDATE_ORIGIN_USER : UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "",
-                Process.SYSTEM_UID);
+                Flags.modesApi() ? ORIGIN_USER_IN_SYSTEMUI : ZenModeConfig.ORIGIN_SYSTEM, "",
+                SYSTEM_UID);
         // In total, this represents 4 events
         assertEquals(4, mZenModeEventLogger.numLoggedChanges());
 
@@ -3203,7 +3223,7 @@
         assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(0));
         checkDndProtoMatchesDefaultZenConfig(mZenModeEventLogger.getPolicyProto(0));
         assertThat(mZenModeEventLogger.getChangeOrigin(0)).isEqualTo(
-                Flags.modesUi() ? UPDATE_ORIGIN_APP : 0);
+                Flags.modesUi() ? ORIGIN_APP : 0);
 
         // When the automatic rule is disabled, this should turn off zen mode and also count as a
         // user action. We don't care what the consolidated policy is when DND turns off.
@@ -3217,14 +3237,14 @@
         assertEquals(0, mZenModeEventLogger.getNumRulesActive(1));
         assertTrue(mZenModeEventLogger.getIsUserAction(1));
         assertThat(mZenModeEventLogger.getPackageUid(1)).isEqualTo(
-                Flags.modesUi() ? CUSTOM_PKG_UID : Process.SYSTEM_UID);
+                Flags.modesUi() ? CUSTOM_PKG_UID : SYSTEM_UID);
         if (Flags.modesApi()) {
             assertThat(mZenModeEventLogger.getPolicyProto(1)).isNull();
         } else {
             checkDndProtoMatchesSetupZenConfig(mZenModeEventLogger.getPolicyProto(1));
         }
         assertThat(mZenModeEventLogger.getChangeOrigin(1)).isEqualTo(
-                Flags.modesUi() ? UPDATE_ORIGIN_USER : 0);
+                Flags.modesUi() ? ORIGIN_USER_IN_SYSTEMUI : 0);
 
         // When the system rule is enabled, this counts as an automatic action that comes from the
         // system and turns on DND
@@ -3233,9 +3253,9 @@
         assertEquals(DNDProtoEnums.AUTOMATIC_RULE, mZenModeEventLogger.getChangedRuleType(2));
         assertEquals(1, mZenModeEventLogger.getNumRulesActive(2));
         assertFalse(mZenModeEventLogger.getIsUserAction(2));
-        assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(2));
+        assertEquals(SYSTEM_UID, mZenModeEventLogger.getPackageUid(2));
         assertThat(mZenModeEventLogger.getChangeOrigin(2)).isEqualTo(
-                Flags.modesUi() ? UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI : 0);
+                Flags.modesUi() ? ZenModeConfig.ORIGIN_SYSTEM : 0);
 
         // When the system rule is deleted, we consider this a user action that turns DND off
         // (again)
@@ -3244,9 +3264,9 @@
         assertEquals(DNDProtoEnums.AUTOMATIC_RULE, mZenModeEventLogger.getChangedRuleType(3));
         assertEquals(0, mZenModeEventLogger.getNumRulesActive(3));
         assertTrue(mZenModeEventLogger.getIsUserAction(3));
-        assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(3));
+        assertEquals(SYSTEM_UID, mZenModeEventLogger.getPackageUid(3));
         assertThat(mZenModeEventLogger.getChangeOrigin(3)).isEqualTo(
-                Flags.modesUi() ? UPDATE_ORIGIN_USER : 0);
+                Flags.modesUi() ? ORIGIN_USER_IN_SYSTEMUI : 0);
     }
 
     @Test
@@ -3264,28 +3284,28 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
-                UPDATE_ORIGIN_APP, "test", CUSTOM_PKG_UID);
+                ORIGIN_APP, "test", CUSTOM_PKG_UID);
 
         // Event 1: Mimic the rule coming on manually when the user turns it on in the app
         // ("Turn on bedtime now" because user goes to bed earlier).
         mZenModeHelper.setAutomaticZenRuleState(id,
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE, SOURCE_USER_ACTION),
-                UPDATE_ORIGIN_USER, CUSTOM_PKG_UID);
+                ORIGIN_USER_IN_APP, CUSTOM_PKG_UID);
 
         // Event 2: App deactivates the rule automatically (it's 8 AM, bedtime schedule ends)
         mZenModeHelper.setAutomaticZenRuleState(id,
                 new Condition(zenRule.getConditionId(), "", STATE_FALSE, SOURCE_SCHEDULE),
-                UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
+                ORIGIN_APP, CUSTOM_PKG_UID);
 
         // Event 3: App activates the rule automatically (it's now 11 PM, bedtime schedule starts)
         mZenModeHelper.setAutomaticZenRuleState(id,
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE, SOURCE_SCHEDULE),
-                UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
+                ORIGIN_APP, CUSTOM_PKG_UID);
 
         // Event 4: User deactivates the rule manually (they get up before 8 AM on the next day)
         mZenModeHelper.setAutomaticZenRuleState(id,
                 new Condition(zenRule.getConditionId(), "", STATE_FALSE, SOURCE_USER_ACTION),
-                UPDATE_ORIGIN_USER, CUSTOM_PKG_UID);
+                ORIGIN_USER_IN_APP, CUSTOM_PKG_UID);
 
         // In total, this represents 4 events
         assertEquals(4, mZenModeEventLogger.numLoggedChanges());
@@ -3301,7 +3321,7 @@
         assertTrue(mZenModeEventLogger.getIsUserAction(0));
         assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(0));
         assertThat(mZenModeEventLogger.getChangeOrigin(0)).isEqualTo(
-                Flags.modesUi() ? UPDATE_ORIGIN_USER : 0);
+                Flags.modesUi() ? ORIGIN_USER_IN_APP : 0);
 
         // Automatic rule turned off automatically by app:
         //   - event ID: DND_TURNED_OFF
@@ -3314,7 +3334,7 @@
         assertFalse(mZenModeEventLogger.getIsUserAction(1));
         assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(1));
         assertThat(mZenModeEventLogger.getChangeOrigin(1)).isEqualTo(
-                Flags.modesUi() ? UPDATE_ORIGIN_APP : 0);
+                Flags.modesUi() ? ORIGIN_APP : 0);
 
         // Automatic rule turned on automatically by app:
         //   - event ID: DND_TURNED_ON
@@ -3328,7 +3348,7 @@
         assertFalse(mZenModeEventLogger.getIsUserAction(2));
         assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(2));
         assertThat(mZenModeEventLogger.getChangeOrigin(2)).isEqualTo(
-                Flags.modesUi() ? UPDATE_ORIGIN_APP : 0);
+                Flags.modesUi() ? ORIGIN_APP : 0);
 
         // Automatic rule turned off automatically by the user:
         //   - event ID: DND_TURNED_ON
@@ -3341,7 +3361,7 @@
         assertTrue(mZenModeEventLogger.getIsUserAction(3));
         assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(3));
         assertThat(mZenModeEventLogger.getChangeOrigin(3)).isEqualTo(
-                Flags.modesUi() ? UPDATE_ORIGIN_USER : 0);
+                Flags.modesUi() ? ORIGIN_USER_IN_APP : 0);
     }
 
     @Test
@@ -3352,21 +3372,21 @@
 
         // First just turn zen mode on
         mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
-                UPDATE_ORIGIN_USER, "", null, Process.SYSTEM_UID);
+                ORIGIN_USER_IN_SYSTEMUI, "", null, SYSTEM_UID);
 
         // Now change the policy slightly; want to confirm that this'll be reflected in the logs
         ZenModeConfig newConfig = mZenModeHelper.mConfig.copy();
         mZenModeHelper.setNotificationPolicy(new Policy(PRIORITY_CATEGORY_ALARMS, 0, 0),
-                UPDATE_ORIGIN_USER, Process.SYSTEM_UID);
+                ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
 
         // Turn zen mode off; we want to make sure policy changes do not get logged when zen mode
         // is off.
-        mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "",
-                null, Process.SYSTEM_UID);
+        mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, ZenModeConfig.ORIGIN_SYSTEM, "",
+                null, SYSTEM_UID);
 
         // Change the policy again
         mZenModeHelper.setNotificationPolicy(new Policy(PRIORITY_CATEGORY_REPEAT_CALLERS, 0, 0),
-                UPDATE_ORIGIN_USER, Process.SYSTEM_UID);
+                ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
 
         // Total events: we only expect ones for turning on, changing policy, and turning off
         assertEquals(3, mZenModeEventLogger.numLoggedChanges());
@@ -3386,7 +3406,7 @@
                 mZenModeEventLogger.getEventId(1));
         assertEquals(DNDProtoEnums.UNKNOWN_RULE, mZenModeEventLogger.getChangedRuleType(1));
         assertTrue(mZenModeEventLogger.getIsUserAction(1));
-        assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(1));
+        assertEquals(SYSTEM_UID, mZenModeEventLogger.getPackageUid(1));
         DNDPolicyProto dndProto = mZenModeEventLogger.getPolicyProto(1);
         assertEquals(STATE_ALLOW, dndProto.getAlarms().getNumber());
         assertEquals(STATE_DISALLOW, dndProto.getRepeatCallers().getNumber());
@@ -3410,7 +3430,7 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
 
         // Rule 2, same as rule 1
         AutomaticZenRule zenRule2 = new AutomaticZenRule("name2",
@@ -3420,7 +3440,7 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2,
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
 
         // Rule 3, has stricter settings than the default settings
         ZenModeConfig ruleConfig = mZenModeHelper.mConfig.copy();
@@ -3432,27 +3452,27 @@
                 ruleConfig.getZenPolicy(),
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id3 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule3,
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
 
         // First: turn on rule 1
         mZenModeHelper.setAutomaticZenRuleState(id,
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE),
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
 
         // Second: turn on rule 2
         mZenModeHelper.setAutomaticZenRuleState(id2,
                 new Condition(zenRule2.getConditionId(), "", STATE_TRUE),
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
 
         // Third: turn on rule 3
         mZenModeHelper.setAutomaticZenRuleState(id3,
                 new Condition(zenRule3.getConditionId(), "", STATE_TRUE),
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
 
         // Fourth: Turn *off* rule 2
         mZenModeHelper.setAutomaticZenRuleState(id2,
                 new Condition(zenRule2.getConditionId(), "", STATE_FALSE),
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
 
         // This should result in a total of four events
         assertEquals(4, mZenModeEventLogger.numLoggedChanges());
@@ -3465,7 +3485,7 @@
         assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeEventLogger.getNewZenMode(0));
         assertEquals(1, mZenModeEventLogger.getNumRulesActive(0));
         assertFalse(mZenModeEventLogger.getIsUserAction(0));
-        assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(0));
+        assertEquals(SYSTEM_UID, mZenModeEventLogger.getPackageUid(0));
         checkDndProtoMatchesDefaultZenConfig(mZenModeEventLogger.getPolicyProto(0));
 
         // Event 2: rule 2 turns on. This should not change anything about the policy, so the only
@@ -3474,7 +3494,7 @@
                 mZenModeEventLogger.getEventId(1));
         assertEquals(2, mZenModeEventLogger.getNumRulesActive(1));
         assertFalse(mZenModeEventLogger.getIsUserAction(1));
-        assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(1));
+        assertEquals(SYSTEM_UID, mZenModeEventLogger.getPackageUid(1));
         checkDndProtoMatchesDefaultZenConfig(mZenModeEventLogger.getPolicyProto(1));
 
         // Event 3: rule 3 turns on. This should trigger a policy change, and be classified as such,
@@ -3484,7 +3504,7 @@
                 mZenModeEventLogger.getEventId(2));
         assertEquals(3, mZenModeEventLogger.getNumRulesActive(2));
         assertFalse(mZenModeEventLogger.getIsUserAction(2));
-        assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(2));
+        assertEquals(SYSTEM_UID, mZenModeEventLogger.getPackageUid(2));
         DNDPolicyProto dndProto = mZenModeEventLogger.getPolicyProto(2);
         assertEquals(STATE_DISALLOW, dndProto.getReminders().getNumber());
         assertEquals(STATE_DISALLOW, dndProto.getCalls().getNumber());
@@ -3508,7 +3528,7 @@
         // Artificially turn zen mode "on". Re-evaluating zen mode should cause it to turn back off
         // given that we don't have any zen rules active.
         mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
-        mZenModeHelper.evaluateZenModeLocked(UPDATE_ORIGIN_UNKNOWN, "test", true);
+        mZenModeHelper.evaluateZenModeLocked(ORIGIN_UNKNOWN, "test", true);
 
         // Check that the change actually took: zen mode should be off now
         assertEquals(ZEN_MODE_OFF, mZenModeHelper.mZenMode);
@@ -3537,7 +3557,7 @@
                 manualRulePolicy,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
-                UPDATE_ORIGIN_APP, "test", Process.SYSTEM_UID);
+                ORIGIN_APP, "test", SYSTEM_UID);
 
         // Rule 2, same as rule 1 but owned by the system
         AutomaticZenRule zenRule2 = new AutomaticZenRule("name2",
@@ -3547,7 +3567,7 @@
                 manualRulePolicy,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2,
-                UPDATE_ORIGIN_USER, "test", Process.SYSTEM_UID);
+                ORIGIN_USER_IN_SYSTEMUI, "test", SYSTEM_UID);
 
         // Turn on rule 1; call looks like it's from the system. Because setting a condition is
         // typically an automatic (non-user-initiated) action, expect the calling UID to be
@@ -3555,23 +3575,24 @@
         // When modes_ui is true: we expect the change origin to be the source of truth.
         mZenModeHelper.setAutomaticZenRuleState(id,
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE),
-                Flags.modesUi() ? UPDATE_ORIGIN_APP : UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
-                Process.SYSTEM_UID);
+                Flags.modesUi() ? ORIGIN_APP : ZenModeConfig.ORIGIN_SYSTEM,
+                SYSTEM_UID);
 
         // Second: turn on rule 2. This is a system-owned rule and the UID should not be modified
         // (nor even looked up; the mock PackageManager won't handle "android" as input).
         mZenModeHelper.setAutomaticZenRuleState(id2,
                 new Condition(zenRule2.getConditionId(), "", STATE_TRUE),
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
 
         // Disable rule 1. Because this looks like a user action, the UID should not be modified
         // from the system-provided one unless modes_ui is true.
         zenRule.setEnabled(false);
         mZenModeHelper.updateAutomaticZenRule(id, zenRule,
-                UPDATE_ORIGIN_USER, "", Process.SYSTEM_UID);
+                ORIGIN_USER_IN_SYSTEMUI, "", SYSTEM_UID);
 
         // Add a manual rule. Any manual rule changes should not get calling uids reassigned.
-        mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, UPDATE_ORIGIN_APP,
+        mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+                ORIGIN_APP,
                 "", null, CUSTOM_PKG_UID);
 
         // Change rule 2's condition, but from some other UID. Since it doesn't look like it's from
@@ -3579,7 +3600,7 @@
         // Note that this probably shouldn't be able to occur in real scenarios.
         mZenModeHelper.setAutomaticZenRuleState(id2,
                 new Condition(zenRule2.getConditionId(), "", STATE_FALSE),
-                UPDATE_ORIGIN_APP, 12345);
+                ORIGIN_APP, 12345);
 
         // That was 5 events total
         assertEquals(5, mZenModeEventLogger.numLoggedChanges());
@@ -3598,7 +3619,7 @@
                 mZenModeEventLogger.getEventId(1));
         assertEquals(DNDProtoEnums.AUTOMATIC_RULE, mZenModeEventLogger.getChangedRuleType(1));
         assertFalse(mZenModeEventLogger.getIsUserAction(1));
-        assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(1));
+        assertEquals(SYSTEM_UID, mZenModeEventLogger.getPackageUid(1));
 
         // Third event: disable rule 1. This looks like a user action so UID should be left alone.
         // When modes_ui is true, we assign log this user action with the app that owns the rule.
@@ -3607,7 +3628,7 @@
         assertEquals(DNDProtoEnums.AUTOMATIC_RULE, mZenModeEventLogger.getChangedRuleType(2));
         assertTrue(mZenModeEventLogger.getIsUserAction(2));
         assertThat(mZenModeEventLogger.getPackageUid(2)).isEqualTo(
-                Flags.modesUi() ? CUSTOM_PKG_UID : Process.SYSTEM_UID);
+                Flags.modesUi() ? CUSTOM_PKG_UID : SYSTEM_UID);
 
         // Fourth event: turns on manual mode. Doesn't change effective policy so this is just a
         // change in active rules. Confirm that the package UID is left unchanged.
@@ -3636,29 +3657,30 @@
         // Turn on zen mode with a manual rule with an enabler set. This should *not* count
         // as a user action, and *should* get its UID reassigned.
         mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", CUSTOM_PKG_NAME, Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, "", CUSTOM_PKG_NAME, SYSTEM_UID);
         assertEquals(1, mZenModeEventLogger.numLoggedChanges());
 
         // Now change apps bypassing to true
         ZenModeConfig newConfig = mZenModeHelper.mConfig.copy();
         newConfig.areChannelsBypassingDnd = true;
         mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(),
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
         assertEquals(2, mZenModeEventLogger.numLoggedChanges());
 
         // and then back to false, all without changing anything else
         newConfig.areChannelsBypassingDnd = false;
         mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(),
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
         assertEquals(3, mZenModeEventLogger.numLoggedChanges());
 
         // Turn off manual mode, call from a package: don't reset UID even though enabler is set
-        mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, UPDATE_ORIGIN_APP, "",
+        mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, ORIGIN_APP, "",
                 CUSTOM_PKG_NAME, 12345);
         assertEquals(4, mZenModeEventLogger.numLoggedChanges());
 
         // And likewise when turning it back on again
-        mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, UPDATE_ORIGIN_APP,
+        mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+                ORIGIN_APP,
                 "", CUSTOM_PKG_NAME, 12345);
 
         // These are 5 events in total.
@@ -3705,7 +3727,7 @@
 
         // First just turn zen mode on
         mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null, Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, "", null, SYSTEM_UID);
 
         // Now change only the channels part of the policy; want to confirm that this'll be
         // reflected in the logs
@@ -3716,7 +3738,7 @@
                 STATE_PRIORITY_CHANNELS_BLOCKED,
                 oldPolicy.priorityConversationSenders);
         mZenModeHelper.setNotificationPolicy(newPolicy,
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
 
         // Total events: one for turning on, one for changing policy
         assertThat(mZenModeEventLogger.numLoggedChanges()).isEqualTo(2);
@@ -3757,17 +3779,17 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_ALL, true);
         String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
-                UPDATE_ORIGIN_APP, "test", CUSTOM_PKG_UID);
+                ORIGIN_APP, "test", CUSTOM_PKG_UID);
 
         // Event 1: App activates the rule automatically.
         mZenModeHelper.setAutomaticZenRuleState(id,
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE, SOURCE_SCHEDULE),
-                UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
+                ORIGIN_APP, CUSTOM_PKG_UID);
 
         // Event 2: App deactivates the rule automatically.
         mZenModeHelper.setAutomaticZenRuleState(id,
                 new Condition(zenRule.getConditionId(), "", STATE_FALSE, SOURCE_SCHEDULE),
-                UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
+                ORIGIN_APP, CUSTOM_PKG_UID);
 
         // In total, this represents 2 events.
         assertEquals(2, mZenModeEventLogger.numLoggedChanges());
@@ -3798,7 +3820,8 @@
                 .setInterruptionFilter(INTERRUPTION_FILTER_ALL)
                 .setType(TYPE_BEDTIME)
                 .build();
-        String bedtimeRuleId = mZenModeHelper.addAutomaticZenRule(mPkg, bedtime, UPDATE_ORIGIN_APP,
+        String bedtimeRuleId = mZenModeHelper.addAutomaticZenRule(mPkg, bedtime,
+                ORIGIN_APP,
                 "reason", CUSTOM_PKG_UID);
 
         // Create immersive rule
@@ -3806,27 +3829,28 @@
                 .setType(TYPE_IMMERSIVE)
                 .setZenPolicy(mZenModeHelper.mConfig.getZenPolicy()) // same as the manual rule
                 .build();
-        String immersiveId = mZenModeHelper.addAutomaticZenRule(mPkg, immersive, UPDATE_ORIGIN_APP,
+        String immersiveId = mZenModeHelper.addAutomaticZenRule(mPkg, immersive,
+                ORIGIN_APP,
                 "reason", CUSTOM_PKG_UID);
 
         // Event 1: Activate bedtime rule. This doesn't turn on notification filtering
         mZenModeHelper.setAutomaticZenRuleState(bedtimeRuleId,
                 new Condition(bedtime.getConditionId(), "", STATE_TRUE, SOURCE_SCHEDULE),
-                UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
+                ORIGIN_APP, CUSTOM_PKG_UID);
 
         // Event 2: turn on manual zen mode. Manual rule will have ACTIVE_RULE_TYPE_MANUAL
         mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null, Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, "", null, SYSTEM_UID);
 
         // Event 3: Turn immersive on
         mZenModeHelper.setAutomaticZenRuleState(immersiveId,
                 new Condition(immersive.getConditionId(), "", STATE_TRUE, SOURCE_SCHEDULE),
-                UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
+                ORIGIN_APP, CUSTOM_PKG_UID);
 
         // Event 4: Turn off bedtime mode, leaving just manual + immersive
         mZenModeHelper.setAutomaticZenRuleState(bedtimeRuleId,
                 new Condition(bedtime.getConditionId(), "", STATE_FALSE, SOURCE_SCHEDULE),
-                UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
+                ORIGIN_APP, CUSTOM_PKG_UID);
 
         // Total of 4 events
         assertEquals(4, mZenModeEventLogger.numLoggedChanges());
@@ -3887,12 +3911,12 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
 
         // enable the rule
         mZenModeHelper.setAutomaticZenRuleState(id,
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE),
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
 
         assertEquals(mZenModeHelper.getNotificationPolicy(),
                 mZenModeHelper.getConsolidatedNotificationPolicy());
@@ -3923,12 +3947,12 @@
                 null,  // null policy
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
 
         // enable the rule
         mZenModeHelper.setAutomaticZenRuleState(id,
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE),
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
 
         // inspect the consolidated policy, which should match the device default settings.
         assertThat(ZenAdapters.notificationPolicyToZenPolicy(mZenModeHelper.mConsolidatedPolicy))
@@ -3961,12 +3985,12 @@
                 customPolicy,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
 
         // enable the rule; this will update the consolidated policy
         mZenModeHelper.setAutomaticZenRuleState(id,
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE),
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
 
         // since this is the only active rule, the consolidated policy should match the custom
         // policy for every field specified, and take default values (from device default or
@@ -4006,12 +4030,12 @@
                 customPolicy,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
 
         // enable the rule; this will update the consolidated policy
         mZenModeHelper.setAutomaticZenRuleState(id,
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE),
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
 
         // since this is the only active rule, the consolidated policy should match the custom
         // policy for every field specified, and take default values (from either device default
@@ -4046,12 +4070,12 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
 
         // enable rule 1
         mZenModeHelper.setAutomaticZenRuleState(id,
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE),
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
 
         // custom policy for rule 2
         ZenPolicy customPolicy = new ZenPolicy.Builder()
@@ -4070,12 +4094,12 @@
                 customPolicy,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2,
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
 
         // enable rule 2; this will update the consolidated policy
         mZenModeHelper.setAutomaticZenRuleState(id2,
                 new Condition(zenRule2.getConditionId(), "", STATE_TRUE),
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
 
         // now both rules should be on, and the consolidated policy should reflect the most
         // restrictive option of each of the two
@@ -4107,12 +4131,12 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
 
         // enable rule 1
         mZenModeHelper.setAutomaticZenRuleState(id,
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE),
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
 
         // custom policy for rule 2
         ZenPolicy customPolicy = new ZenPolicy.Builder()
@@ -4131,12 +4155,12 @@
                 customPolicy,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2,
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
 
         // enable rule 2; this will update the consolidated policy
         mZenModeHelper.setAutomaticZenRuleState(id2,
                 new Condition(zenRule2.getConditionId(), "", STATE_TRUE),
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
 
         // now both rules should be on, and the consolidated policy should reflect the most
         // restrictive option of each of the two
@@ -4173,12 +4197,12 @@
                 customPolicy,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
 
         // enable the rule; this will update the consolidated policy
         mZenModeHelper.setAutomaticZenRuleState(id,
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE),
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
 
         // confirm that channels make it through
         assertTrue(mZenModeHelper.mConsolidatedPolicy.allowPriorityChannels());
@@ -4195,12 +4219,12 @@
                 strictPolicy,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2,
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
 
         // enable rule 2; this will update the consolidated policy
         mZenModeHelper.setAutomaticZenRuleState(id2,
                 new Condition(zenRule2.getConditionId(), "", STATE_TRUE),
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
 
         // rule 2 should override rule 1
         assertFalse(mZenModeHelper.mConsolidatedPolicy.allowPriorityChannels());
@@ -4226,10 +4250,10 @@
                         .build(),
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String rule1Id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                zenRuleWithPriority, UPDATE_ORIGIN_APP, "test", CUSTOM_PKG_UID);
+                zenRuleWithPriority, ORIGIN_APP, "test", CUSTOM_PKG_UID);
         mZenModeHelper.setAutomaticZenRuleState(rule1Id,
                 new Condition(zenRuleWithPriority.getConditionId(), "", STATE_TRUE),
-                UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
+                ORIGIN_APP, CUSTOM_PKG_UID);
 
         // Rule 2: ALL, but somehow with a super strict ZenPolicy.
         AutomaticZenRule zenRuleWithAll = new AutomaticZenRule("All",
@@ -4239,10 +4263,10 @@
                 new ZenPolicy.Builder().disallowAllSounds().build(),
                 NotificationManager.INTERRUPTION_FILTER_ALL, true);
         String rule2Id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                zenRuleWithAll, UPDATE_ORIGIN_APP, "test", CUSTOM_PKG_UID);
+                zenRuleWithAll, ORIGIN_APP, "test", CUSTOM_PKG_UID);
         mZenModeHelper.setAutomaticZenRuleState(rule2Id,
                 new Condition(zenRuleWithPriority.getConditionId(), "", STATE_TRUE),
-                UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
+                ORIGIN_APP, CUSTOM_PKG_UID);
 
         // Consolidated Policy should be default + rule1.
         assertThat(mZenModeHelper.mConsolidatedPolicy.allowAlarms()).isEqualTo(
@@ -4274,7 +4298,7 @@
         rule.zenMode = INTERRUPTION_FILTER_ZR;
         rule.modified = true;
         rule.name = NAME;
-        rule.snoozing = true;
+        rule.setConditionOverride(OVERRIDE_DEACTIVATE);
         rule.pkg = OWNER.getPackageName();
         rule.zenPolicy = POLICY;
 
@@ -4321,7 +4345,7 @@
                 .build();
 
         String ruleId = mZenModeHelper.addAutomaticZenRule(OWNER.getPackageName(), azr,
-                UPDATE_ORIGIN_APP, "add", CUSTOM_PKG_UID);
+                ORIGIN_APP, "add", CUSTOM_PKG_UID);
 
         ZenModeConfig.ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
 
@@ -4351,15 +4375,15 @@
                 .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
                 .build();
         String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
+                azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
         AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
 
         // Checks the name can be changed by the app because the user has not modified it.
         AutomaticZenRule azrUpdate = new AutomaticZenRule.Builder(rule)
                 .setName("NewName")
                 .build();
-        mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_APP, "reason",
-                Process.SYSTEM_UID);
+        mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, ORIGIN_APP, "reason",
+                SYSTEM_UID);
         rule = mZenModeHelper.getAutomaticZenRule(ruleId);
         assertThat(rule.getName()).isEqualTo("NewName");
 
@@ -4368,16 +4392,16 @@
         azrUpdate = new AutomaticZenRule.Builder(rule)
                 .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
                 .build();
-        mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_USER, "reason",
-                Process.SYSTEM_UID);
+        mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, ORIGIN_USER_IN_SYSTEMUI, "reason",
+                SYSTEM_UID);
 
         // ...but the app can still modify the name, because the name itself hasn't been modified
         // by the user.
         azrUpdate = new AutomaticZenRule.Builder(rule)
                 .setName("NewAppName")
                 .build();
-        mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_APP, "reason",
-                Process.SYSTEM_UID);
+        mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, ORIGIN_APP, "reason",
+                SYSTEM_UID);
         rule = mZenModeHelper.getAutomaticZenRule(ruleId);
         assertThat(rule.getName()).isEqualTo("NewAppName");
 
@@ -4385,8 +4409,8 @@
         azrUpdate = new AutomaticZenRule.Builder(rule)
                 .setName("UserProvidedName")
                 .build();
-        mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_USER, "reason",
-                Process.SYSTEM_UID);
+        mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, ORIGIN_USER_IN_SYSTEMUI, "reason",
+                SYSTEM_UID);
         rule = mZenModeHelper.getAutomaticZenRule(ruleId);
         assertThat(rule.getName()).isEqualTo("UserProvidedName");
 
@@ -4394,8 +4418,8 @@
         azrUpdate = new AutomaticZenRule.Builder(rule)
                 .setName("NewAppName")
                 .build();
-        mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_APP, "reason",
-                Process.SYSTEM_UID);
+        mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, ORIGIN_APP, "reason",
+                SYSTEM_UID);
         rule = mZenModeHelper.getAutomaticZenRule(ruleId);
         assertThat(rule.getName()).isEqualTo("UserProvidedName");
     }
@@ -4411,7 +4435,7 @@
                 .build();
         // Adds the rule using the app, to avoid having any user modified bits set.
         String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
+                azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
         AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
 
         // Modifies the filter, icon, zen policy, and device effects
@@ -4430,8 +4454,8 @@
                 .build();
 
         // Update the rule with the AZR from origin user.
-        mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_USER, "reason",
-                Process.SYSTEM_UID);
+        mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, ORIGIN_USER_IN_SYSTEMUI, "reason",
+                SYSTEM_UID);
         rule = mZenModeHelper.getAutomaticZenRule(ruleId);
 
         // UPDATE_ORIGIN_USER should change the bitmask and change the values.
@@ -4468,7 +4492,7 @@
                 .build();
         // Adds the rule using the app, to avoid having any user modified bits set.
         String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
+                azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
         AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
 
         // Modifies the icon, zen policy and device effects
@@ -4487,8 +4511,8 @@
                 .build();
 
         // Update the rule with the AZR from origin systemUI.
-        mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
-                "reason", Process.SYSTEM_UID);
+        mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, ZenModeConfig.ORIGIN_SYSTEM,
+                "reason", SYSTEM_UID);
         rule = mZenModeHelper.getAutomaticZenRule(ruleId);
 
         // UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI should change the value but NOT update the bitmask.
@@ -4518,7 +4542,7 @@
                 .build();
         // Adds the rule using the app, to avoid having any user modified bits set.
         String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
+                azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
         AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
 
         ZenPolicy policy = new ZenPolicy.Builder()
@@ -4535,8 +4559,8 @@
 
         // Since the rule is not already user modified, UPDATE_ORIGIN_APP can modify the rule.
         // The bitmask is not modified.
-        mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_APP, "reason",
-                Process.SYSTEM_UID);
+        mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, ORIGIN_APP, "reason",
+                SYSTEM_UID);
 
         ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
         assertThat(storedRule.userModifiedFields).isEqualTo(0);
@@ -4551,7 +4575,7 @@
 
         // Creates another rule, this time from user. This will have user modified bits set.
         String ruleIdUser = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                azrBase, UPDATE_ORIGIN_USER, "reason", Process.SYSTEM_UID);
+                azrBase, ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID);
         storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleIdUser);
         int ruleModifiedFields = storedRule.userModifiedFields;
         int rulePolicyModifiedFields = storedRule.zenPolicyUserModifiedFields;
@@ -4559,8 +4583,8 @@
 
         // Zen rule update coming from the app again. This cannot fully update the rule, because
         // the rule is already considered user modified.
-        mZenModeHelper.updateAutomaticZenRule(ruleIdUser, azrUpdate, UPDATE_ORIGIN_APP,
-                "reason", Process.SYSTEM_UID);
+        mZenModeHelper.updateAutomaticZenRule(ruleIdUser, azrUpdate, ORIGIN_APP,
+                "reason", SYSTEM_UID);
         AutomaticZenRule ruleUser = mZenModeHelper.getAutomaticZenRule(ruleIdUser);
 
         // The app can only change the value if the rule is not already user modified,
@@ -4591,7 +4615,7 @@
                         .build())
                 .build();
         String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
+                azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
         AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
 
         // The values are modified but the bitmask is not.
@@ -4613,7 +4637,7 @@
                 .build();
         // Adds the rule using the app, to avoid having any user modified bits set.
         String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
+                azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
 
         AutomaticZenRule azr = new AutomaticZenRule.Builder(azrBase)
                 // Sets Device Effects to null
@@ -4622,8 +4646,8 @@
 
         // Zen rule update coming from app, but since the rule isn't already
         // user modified, it can be updated.
-        mZenModeHelper.updateAutomaticZenRule(ruleId, azr, UPDATE_ORIGIN_APP, "reason",
-                Process.SYSTEM_UID);
+        mZenModeHelper.updateAutomaticZenRule(ruleId, azr, ORIGIN_APP, "reason",
+                SYSTEM_UID);
         AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
 
         // When AZR's ZenDeviceEffects is null, the updated rule's device effects are kept.
@@ -4639,7 +4663,7 @@
                 .build();
         // Adds the rule using the app, to avoid having any user modified bits set.
         String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
+                azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
         AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
 
         AutomaticZenRule azr = new AutomaticZenRule.Builder(azrBase)
@@ -4649,8 +4673,8 @@
 
         // Zen rule update coming from app, but since the rule isn't already
         // user modified, it can be updated.
-        mZenModeHelper.updateAutomaticZenRule(ruleId, azr, UPDATE_ORIGIN_APP, "reason",
-                Process.SYSTEM_UID);
+        mZenModeHelper.updateAutomaticZenRule(ruleId, azr, ORIGIN_APP, "reason",
+                SYSTEM_UID);
         rule = mZenModeHelper.getAutomaticZenRule(ruleId);
 
         // When AZR's ZenPolicy is null, we expect the updated rule's policy to be unchanged
@@ -4671,7 +4695,7 @@
                 .build();
         // Adds the rule using the app, to avoid having any user modified bits set.
         String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
+                azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
         AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
 
         // Create a fully populated ZenPolicy.
@@ -4700,8 +4724,8 @@
 
         // Applies the update to the rule.
         // Default config defined in getDefaultConfigParser() is used as the original rule.
-        mZenModeHelper.updateAutomaticZenRule(ruleId, azr, UPDATE_ORIGIN_USER, "reason",
-                Process.SYSTEM_UID);
+        mZenModeHelper.updateAutomaticZenRule(ruleId, azr, ORIGIN_USER_IN_SYSTEMUI, "reason",
+                SYSTEM_UID);
         rule = mZenModeHelper.getAutomaticZenRule(ruleId);
 
         // New ZenPolicy differs from the default config
@@ -4732,7 +4756,7 @@
                 .build();
         // Adds the rule using the app, to avoid having any user modified bits set.
         String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
+                azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
         AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
 
         ZenDeviceEffects deviceEffects = new ZenDeviceEffects.Builder()
@@ -4743,8 +4767,8 @@
                 .build();
 
         // Applies the update to the rule.
-        mZenModeHelper.updateAutomaticZenRule(ruleId, azr, UPDATE_ORIGIN_USER, "reason",
-                Process.SYSTEM_UID);
+        mZenModeHelper.updateAutomaticZenRule(ruleId, azr, ORIGIN_USER_IN_SYSTEMUI, "reason",
+                SYSTEM_UID);
         rule = mZenModeHelper.getAutomaticZenRule(ruleId);
 
         // New ZenDeviceEffects is used; all fields considered set, since previously were null.
@@ -4769,7 +4793,7 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+                zenRule, ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
 
         CountDownLatch latch = new CountDownLatch(1);
         final int[] actualStatus = new int[1];
@@ -4785,8 +4809,8 @@
         mZenModeHelper.addCallback(callback);
 
         zenRule.setEnabled(false);
-        mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
-                "", Process.SYSTEM_UID);
+        mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, ZenModeConfig.ORIGIN_SYSTEM,
+                "", SYSTEM_UID);
 
         assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
         assertEquals(AUTOMATIC_RULE_STATUS_DISABLED, actualStatus[0]);
@@ -4804,7 +4828,7 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, false);
         final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+                zenRule, ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
 
         CountDownLatch latch = new CountDownLatch(1);
         final int[] actualStatus = new int[1];
@@ -4820,8 +4844,8 @@
         mZenModeHelper.addCallback(callback);
 
         zenRule.setEnabled(true);
-        mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
-                "", Process.SYSTEM_UID);
+        mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, ZenModeConfig.ORIGIN_SYSTEM,
+                "", SYSTEM_UID);
 
         assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
         assertEquals(AUTOMATIC_RULE_STATUS_ENABLED, actualStatus[0]);
@@ -4840,7 +4864,7 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+                zenRule, ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
 
         CountDownLatch latch = new CountDownLatch(1);
         final int[] actualStatus = new int[1];
@@ -4857,7 +4881,7 @@
 
         mZenModeHelper.setAutomaticZenRuleState(createdId,
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE),
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
 
         assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
         if (CompatChanges.isChangeEnabled(ZenModeHelper.SEND_ACTIVATION_AZR_STATUSES)) {
@@ -4880,7 +4904,7 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+                zenRule, ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
 
         CountDownLatch latch = new CountDownLatch(1);
         final int[] actualStatus = new int[2];
@@ -4899,10 +4923,10 @@
 
         mZenModeHelper.setAutomaticZenRuleState(createdId,
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE),
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
 
-        mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
-                null, "", Process.SYSTEM_UID);
+        mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, ZenModeConfig.ORIGIN_SYSTEM,
+                null, "", SYSTEM_UID);
 
         assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
         if (CompatChanges.isChangeEnabled(ZenModeHelper.SEND_ACTIVATION_AZR_STATUSES)) {
@@ -4925,7 +4949,7 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+                zenRule, ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
 
         CountDownLatch latch = new CountDownLatch(1);
         final int[] actualStatus = new int[2];
@@ -4944,11 +4968,11 @@
 
         mZenModeHelper.setAutomaticZenRuleState(createdId,
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE),
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
 
         mZenModeHelper.setAutomaticZenRuleState(createdId,
                 new Condition(zenRule.getConditionId(), "", STATE_FALSE),
-                UPDATE_ORIGIN_APP, Process.SYSTEM_UID);
+                ORIGIN_APP, CUSTOM_PKG_UID);
 
         assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
         if (CompatChanges.isChangeEnabled(ZenModeHelper.SEND_ACTIVATION_AZR_STATUSES)) {
@@ -4970,23 +4994,24 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+                zenRule, ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
 
         // Event 1: Mimic the rule coming on automatically by setting the Condition to STATE_TRUE
         mZenModeHelper.setAutomaticZenRuleState(createdId,
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE),
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
 
         // Event 2: Snooze rule by turning off DND
-        mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
-                "", null, Process.SYSTEM_UID);
+        mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, ZenModeConfig.ORIGIN_SYSTEM,
+                "", null, SYSTEM_UID);
 
         // Event 3: "User" turns off the automatic rule (sets it to not enabled)
         zenRule.setEnabled(false);
-        mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
-                "", Process.SYSTEM_UID);
+        mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, ZenModeConfig.ORIGIN_SYSTEM,
+                "", SYSTEM_UID);
 
-        assertEquals(false, mZenModeHelper.mConfig.automaticRules.get(createdId).snoozing);
+        assertEquals(OVERRIDE_NONE,
+                mZenModeHelper.mConfig.automaticRules.get(createdId).getConditionOverride());
     }
 
     @Test
@@ -4997,16 +5022,16 @@
                 .setConfigurationActivity(new ComponentName(mPkg, "cls"))
                 .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
                 .build();
-        String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, UPDATE_ORIGIN_APP, "reason",
+        String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "reason",
                 CUSTOM_PKG_UID);
-        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
                 CUSTOM_PKG_UID);
         assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
 
         AutomaticZenRule updateWithDiff = new AutomaticZenRule.Builder(rule)
                 .setTriggerDescription("Whenever")
                 .build();
-        mZenModeHelper.updateAutomaticZenRule(ruleId, updateWithDiff, UPDATE_ORIGIN_APP, "reason",
+        mZenModeHelper.updateAutomaticZenRule(ruleId, updateWithDiff, ORIGIN_APP, "reason",
                 CUSTOM_PKG_UID);
 
         assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
@@ -5021,14 +5046,14 @@
                 .setConfigurationActivity(new ComponentName(mPkg, "cls"))
                 .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
                 .build();
-        String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, UPDATE_ORIGIN_APP, "reason",
+        String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "reason",
                 CUSTOM_PKG_UID);
-        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
                 CUSTOM_PKG_UID);
         assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
 
         AutomaticZenRule updateUnchanged = new AutomaticZenRule.Builder(rule).build();
-        mZenModeHelper.updateAutomaticZenRule(ruleId, updateUnchanged, UPDATE_ORIGIN_APP, "reason",
+        mZenModeHelper.updateAutomaticZenRule(ruleId, updateUnchanged, ORIGIN_APP, "reason",
                 CUSTOM_PKG_UID);
 
         assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
@@ -5044,17 +5069,17 @@
                 .setConfigurationActivity(new ComponentName(mPkg, "cls"))
                 .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
                 .build();
-        String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, UPDATE_ORIGIN_APP, "reason",
+        String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "reason",
                 CUSTOM_PKG_UID);
-        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
                 CUSTOM_PKG_UID);
         assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
 
         AutomaticZenRule updateWithDiff = new AutomaticZenRule.Builder(rule)
                 .setTriggerDescription("Whenever")
                 .build();
-        mZenModeHelper.updateAutomaticZenRule(ruleId, updateWithDiff, UPDATE_ORIGIN_USER, "reason",
-                CUSTOM_PKG_UID);
+        mZenModeHelper.updateAutomaticZenRule(ruleId, updateWithDiff, ORIGIN_USER_IN_SYSTEMUI,
+                "reason", SYSTEM_UID);
 
         assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
         assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).condition).isEqualTo(
@@ -5074,15 +5099,15 @@
                         .build();
         String ruleId =
                 mZenModeHelper.addAutomaticZenRule(
-                        mPkg, rule, UPDATE_ORIGIN_APP, "reason", CUSTOM_PKG_UID);
+                        mPkg, rule, ORIGIN_APP, "reason", CUSTOM_PKG_UID);
         mZenModeHelper.setAutomaticZenRuleState(
-                ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
+                ruleId, CONDITION_TRUE, ORIGIN_APP, CUSTOM_PKG_UID);
         assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
 
         AutomaticZenRule updateWithDiff =
                 new AutomaticZenRule.Builder(rule).setTriggerDescription("Whenever").build();
-        mZenModeHelper.updateAutomaticZenRule(ruleId, updateWithDiff, UPDATE_ORIGIN_USER, "reason",
-                CUSTOM_PKG_UID);
+        mZenModeHelper.updateAutomaticZenRule(ruleId, updateWithDiff, ORIGIN_USER_IN_SYSTEMUI,
+                "reason", SYSTEM_UID);
 
         assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
         assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).condition).isEqualTo(
@@ -5097,18 +5122,18 @@
                 .setConfigurationActivity(new ComponentName(mPkg, "cls"))
                 .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
                 .build();
-        String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, UPDATE_ORIGIN_APP, "reason",
+        String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "reason",
                 CUSTOM_PKG_UID);
-        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
                 CUSTOM_PKG_UID);
         assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
 
         mZenModeHelper.updateAutomaticZenRule(ruleId,
-                new AutomaticZenRule.Builder(rule).setEnabled(false).build(), UPDATE_ORIGIN_USER,
-                "disable", CUSTOM_PKG_UID);
+                new AutomaticZenRule.Builder(rule).setEnabled(false).build(),
+                ORIGIN_USER_IN_SYSTEMUI, "disable", SYSTEM_UID);
         mZenModeHelper.updateAutomaticZenRule(ruleId,
-                new AutomaticZenRule.Builder(rule).setEnabled(true).build(), UPDATE_ORIGIN_USER,
-                "enable", CUSTOM_PKG_UID);
+                new AutomaticZenRule.Builder(rule).setEnabled(true).build(),
+                ORIGIN_USER_IN_SYSTEMUI, "enable", SYSTEM_UID);
 
         assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
         assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).condition).isNull();
@@ -5123,13 +5148,13 @@
                 .setOwner(new ComponentName("android", "some.old.cps"))
                 .build();
         String ruleId = mZenModeHelper.addAutomaticZenRule("android", original,
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reason", Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, "reason", SYSTEM_UID);
 
         AutomaticZenRule update = new AutomaticZenRule.Builder("Rule", Uri.EMPTY)
                 .setOwner(new ComponentName("android", "brand.new.cps"))
                 .build();
-        mZenModeHelper.updateAutomaticZenRule(ruleId, update, UPDATE_ORIGIN_USER, "reason",
-                Process.SYSTEM_UID);
+        mZenModeHelper.updateAutomaticZenRule(ruleId, update, ORIGIN_USER_IN_SYSTEMUI, "reason",
+                SYSTEM_UID);
 
         AutomaticZenRule result = mZenModeHelper.getAutomaticZenRule(ruleId);
         assertThat(result).isNotNull();
@@ -5143,13 +5168,13 @@
                 .setOwner(new ComponentName(mContext.getPackageName(), "old.third.party.cps"))
                 .build();
         String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), original,
-                UPDATE_ORIGIN_APP, "reason", CUSTOM_PKG_UID);
+                ORIGIN_APP, "reason", CUSTOM_PKG_UID);
 
         AutomaticZenRule update = new AutomaticZenRule.Builder("Rule", Uri.EMPTY)
                 .setOwner(new ComponentName(mContext.getPackageName(), "new.third.party.cps"))
                 .build();
-        mZenModeHelper.updateAutomaticZenRule(ruleId, update, UPDATE_ORIGIN_USER, "reason",
-                Process.SYSTEM_UID);
+        mZenModeHelper.updateAutomaticZenRule(ruleId, update, ORIGIN_USER_IN_SYSTEMUI, "reason",
+                SYSTEM_UID);
 
         AutomaticZenRule result = mZenModeHelper.getAutomaticZenRule(ruleId);
         assertThat(result).isNotNull();
@@ -5166,24 +5191,24 @@
                 .setShouldSuppressAmbientDisplay(true)
                 .setShouldDimWallpaper(true)
                 .build());
-        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
                 CUSTOM_PKG_UID);
         mTestableLooper.processAllMessages();
-        verify(mDeviceEffectsApplier).apply(any(), eq(UPDATE_ORIGIN_APP));
+        verify(mDeviceEffectsApplier).apply(any(), eq(ORIGIN_APP));
 
         // Now delete the (currently active!) rule. For example, assume this is done from settings.
-        mZenModeHelper.removeAutomaticZenRule(ruleId, UPDATE_ORIGIN_USER, "remove",
-                Process.SYSTEM_UID);
+        mZenModeHelper.removeAutomaticZenRule(ruleId, ORIGIN_USER_IN_SYSTEMUI, "remove",
+                SYSTEM_UID);
         mTestableLooper.processAllMessages();
 
-        verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(UPDATE_ORIGIN_USER));
+        verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(ORIGIN_USER_IN_SYSTEMUI));
     }
 
     @Test
     @EnableFlags(FLAG_MODES_API)
     public void testDeviceEffects_applied() {
         mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
-        verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(UPDATE_ORIGIN_INIT));
+        verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(ORIGIN_INIT));
 
         ZenDeviceEffects effects = new ZenDeviceEffects.Builder()
                 .setShouldSuppressAmbientDisplay(true)
@@ -5192,11 +5217,11 @@
         String ruleId = addRuleWithEffects(effects);
         verifyNoMoreInteractions(mDeviceEffectsApplier);
 
-        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
                 CUSTOM_PKG_UID);
         mTestableLooper.processAllMessages();
 
-        verify(mDeviceEffectsApplier).apply(eq(effects), eq(UPDATE_ORIGIN_APP));
+        verify(mDeviceEffectsApplier).apply(eq(effects), eq(ORIGIN_APP));
     }
 
     @Test
@@ -5206,30 +5231,30 @@
 
         ZenDeviceEffects zde = new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build();
         String ruleId = addRuleWithEffects(zde);
-        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
                 CUSTOM_PKG_UID);
         mTestableLooper.processAllMessages();
-        verify(mDeviceEffectsApplier).apply(eq(zde), eq(UPDATE_ORIGIN_APP));
+        verify(mDeviceEffectsApplier).apply(eq(zde), eq(ORIGIN_APP));
 
-        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_FALSE, UPDATE_ORIGIN_APP,
+        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_FALSE, ORIGIN_APP,
                 CUSTOM_PKG_UID);
         mTestableLooper.processAllMessages();
 
-        verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(UPDATE_ORIGIN_APP));
+        verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(ORIGIN_APP));
     }
 
     @Test
     @EnableFlags(FLAG_MODES_API)
     public void testDeviceEffects_changeToConsolidatedEffects_applied() {
         mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
-        verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(UPDATE_ORIGIN_INIT));
+        verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(ORIGIN_INIT));
 
         String ruleId = addRuleWithEffects(
                 new ZenDeviceEffects.Builder()
                         .setShouldDisplayGrayscale(true)
                         .addExtraEffect("ONE")
                         .build());
-        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
                 CUSTOM_PKG_UID);
         mTestableLooper.processAllMessages();
         verify(mDeviceEffectsApplier).apply(
@@ -5237,7 +5262,7 @@
                         .setShouldDisplayGrayscale(true)
                         .addExtraEffect("ONE")
                         .build()),
-                eq(UPDATE_ORIGIN_APP));
+                eq(ORIGIN_APP));
 
         // Now create and activate a second rule that adds more effects.
         String secondRuleId = addRuleWithEffects(
@@ -5245,7 +5270,7 @@
                         .setShouldDimWallpaper(true)
                         .addExtraEffect("TWO")
                         .build());
-        mZenModeHelper.setAutomaticZenRuleState(secondRuleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+        mZenModeHelper.setAutomaticZenRuleState(secondRuleId, CONDITION_TRUE, ORIGIN_APP,
                 CUSTOM_PKG_UID);
         mTestableLooper.processAllMessages();
 
@@ -5255,28 +5280,28 @@
                         .setShouldDimWallpaper(true)
                         .setExtraEffects(ImmutableSet.of("ONE", "TWO"))
                         .build()),
-                eq(UPDATE_ORIGIN_APP));
+                eq(ORIGIN_APP));
     }
 
     @Test
     @EnableFlags(FLAG_MODES_API)
     public void testDeviceEffects_noChangeToConsolidatedEffects_notApplied() {
         mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
-        verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(UPDATE_ORIGIN_INIT));
+        verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(ORIGIN_INIT));
 
         ZenDeviceEffects zde = new ZenDeviceEffects.Builder()
                 .setShouldUseNightMode(true)
                 .addExtraEffect("extra_effect")
                 .build();
         String ruleId = addRuleWithEffects(zde);
-        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
                 CUSTOM_PKG_UID);
         mTestableLooper.processAllMessages();
-        verify(mDeviceEffectsApplier).apply(eq(zde), eq(UPDATE_ORIGIN_APP));
+        verify(mDeviceEffectsApplier).apply(eq(zde), eq(ORIGIN_APP));
 
         // Now create and activate a second rule that doesn't add any more effects.
         String secondRuleId = addRuleWithEffects(zde);
-        mZenModeHelper.setAutomaticZenRuleState(secondRuleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+        mZenModeHelper.setAutomaticZenRuleState(secondRuleId, CONDITION_TRUE, ORIGIN_APP,
                 CUSTOM_PKG_UID);
         mTestableLooper.processAllMessages();
 
@@ -5290,20 +5315,20 @@
         String ruleId = addRuleWithEffects(zde);
         verify(mDeviceEffectsApplier, never()).apply(any(), anyInt());
 
-        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
                 CUSTOM_PKG_UID);
         mTestableLooper.processAllMessages();
         verify(mDeviceEffectsApplier, never()).apply(any(), anyInt());
 
         mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
-        verify(mDeviceEffectsApplier).apply(eq(zde), eq(UPDATE_ORIGIN_INIT));
+        verify(mDeviceEffectsApplier).apply(eq(zde), eq(ORIGIN_INIT));
     }
 
     @Test
     @EnableFlags(FLAG_MODES_API)
     public void testDeviceEffects_onUserSwitch_appliedImmediately() {
         mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
-        verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(UPDATE_ORIGIN_INIT));
+        verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(ORIGIN_INIT));
 
         // Initialize default configurations (default rules) for both users.
         mZenModeHelper.onUserSwitched(1);
@@ -5324,7 +5349,7 @@
         mTestableLooper.processAllMessages();
 
         verify(mDeviceEffectsApplier).apply(eq(user1Rule.zenDeviceEffects),
-                eq(UPDATE_ORIGIN_INIT_USER));
+                eq(ORIGIN_INIT_USER));
     }
 
     private String addRuleWithEffects(ZenDeviceEffects effects) {
@@ -5332,7 +5357,7 @@
                 .setDeviceEffects(effects)
                 .build();
         return mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reasons", Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, "reasons", SYSTEM_UID);
     }
 
     @Test
@@ -5346,7 +5371,7 @@
                 .setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(false).build())
                 .build();
         String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
-                UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+                ORIGIN_APP, "add it", CUSTOM_PKG_UID);
         assertThat(mZenModeHelper.getAutomaticZenRule(ruleId).getCreationTime()).isEqualTo(1000);
 
         // User customizes it.
@@ -5354,12 +5379,12 @@
                 .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
                 .setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(true).build())
                 .build();
-        mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdate, UPDATE_ORIGIN_USER, "userUpdate",
-                Process.SYSTEM_UID);
+        mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdate, ORIGIN_USER_IN_SYSTEMUI,
+                "userUpdate", SYSTEM_UID);
 
         // App deletes it.
         mTestClock.advanceByMillis(1000);
-        mZenModeHelper.removeAutomaticZenRule(ruleId, UPDATE_ORIGIN_APP, "delete it",
+        mZenModeHelper.removeAutomaticZenRule(ruleId, ORIGIN_APP, "delete it",
                 CUSTOM_PKG_UID);
         assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(0);
         assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(1);
@@ -5367,7 +5392,7 @@
         // App adds it again.
         mTestClock.advanceByMillis(1000);
         String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
-                UPDATE_ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
+                ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
 
         // Verify that the rule was restored:
         // - id and creation time is the same as the original one.
@@ -5401,12 +5426,12 @@
                 .setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(false).build())
                 .build();
         String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
-                UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+                ORIGIN_APP, "add it", CUSTOM_PKG_UID);
         assertThat(mZenModeHelper.getAutomaticZenRule(ruleId).getCreationTime()).isEqualTo(1000);
 
         // App deletes it.
         mTestClock.advanceByMillis(1000);
-        mZenModeHelper.removeAutomaticZenRule(ruleId, UPDATE_ORIGIN_APP, "delete it",
+        mZenModeHelper.removeAutomaticZenRule(ruleId, ORIGIN_APP, "delete it",
                 CUSTOM_PKG_UID);
         assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(0);
         assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(0);
@@ -5414,7 +5439,7 @@
         // App adds it again.
         mTestClock.advanceByMillis(1000);
         String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
-                UPDATE_ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
+                ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
 
         // Verify that the rule was recreated. This means id and creation time are new.
         AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(newRuleId);
@@ -5433,7 +5458,7 @@
                 .setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(false).build())
                 .build();
         String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
-                UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+                ORIGIN_APP, "add it", CUSTOM_PKG_UID);
         assertThat(mZenModeHelper.getAutomaticZenRule(ruleId).getCreationTime()).isEqualTo(1000);
 
         // User customizes it.
@@ -5442,12 +5467,12 @@
                 .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
                 .setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(true).build())
                 .build();
-        mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdate, UPDATE_ORIGIN_USER, "userUpdate",
-                Process.SYSTEM_UID);
+        mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdate, ORIGIN_USER_IN_SYSTEMUI,
+                "userUpdate", SYSTEM_UID);
 
         // App deletes it.
         mTestClock.advanceByMillis(1000);
-        mZenModeHelper.removeAutomaticZenRule(ruleId, UPDATE_ORIGIN_APP, "delete it",
+        mZenModeHelper.removeAutomaticZenRule(ruleId, ORIGIN_APP, "delete it",
                 CUSTOM_PKG_UID);
         assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(0);
         assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(1);
@@ -5455,7 +5480,7 @@
         // User creates it again (unusual case, but ok).
         mTestClock.advanceByMillis(1000);
         String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
-                UPDATE_ORIGIN_USER, "add it anew", CUSTOM_PKG_UID);
+                ORIGIN_USER_IN_SYSTEMUI, "add it anew", SYSTEM_UID);
 
         // Verify that the rule was recreated. This means id and creation time are new, and the rule
         // matches the latest data supplied to addAZR.
@@ -5481,7 +5506,7 @@
                 .setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(false).build())
                 .build();
         String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
-                UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+                ORIGIN_APP, "add it", CUSTOM_PKG_UID);
         assertThat(mZenModeHelper.getAutomaticZenRule(ruleId).getCreationTime()).isEqualTo(1000);
 
         // User customizes it.
@@ -5490,20 +5515,20 @@
                 .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
                 .setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(true).build())
                 .build();
-        mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdate, UPDATE_ORIGIN_USER, "userUpdate",
-                Process.SYSTEM_UID);
+        mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdate, ORIGIN_USER_IN_SYSTEMUI,
+                "userUpdate", SYSTEM_UID);
 
         // User deletes it.
         mTestClock.advanceByMillis(1000);
-        mZenModeHelper.removeAutomaticZenRule(ruleId, UPDATE_ORIGIN_USER, "delete it",
-                CUSTOM_PKG_UID);
+        mZenModeHelper.removeAutomaticZenRule(ruleId, ORIGIN_USER_IN_SYSTEMUI, "delete it",
+                SYSTEM_UID);
         assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(0);
         assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(0);
 
         // App creates it again.
         mTestClock.advanceByMillis(1000);
         String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
-                UPDATE_ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
+                ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
 
         // Verify that the rule was recreated. This means id and creation time are new.
         AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(newRuleId);
@@ -5520,29 +5545,34 @@
 
         // Start with a bunch of customized rules where conditionUris are not unique.
         String id1 = mZenModeHelper.addAutomaticZenRule("pkg1",
-                new AutomaticZenRule.Builder("Test1", Uri.parse("uri1")).build(), UPDATE_ORIGIN_APP,
+                new AutomaticZenRule.Builder("Test1", Uri.parse("uri1")).build(),
+                ORIGIN_APP,
                 "add it", CUSTOM_PKG_UID);
         String id2 = mZenModeHelper.addAutomaticZenRule("pkg1",
-                new AutomaticZenRule.Builder("Test2", Uri.parse("uri2")).build(), UPDATE_ORIGIN_APP,
+                new AutomaticZenRule.Builder("Test2", Uri.parse("uri2")).build(),
+                ORIGIN_APP,
                 "add it", CUSTOM_PKG_UID);
         String id3 = mZenModeHelper.addAutomaticZenRule("pkg1",
-                new AutomaticZenRule.Builder("Test3", Uri.parse("uri2")).build(), UPDATE_ORIGIN_APP,
+                new AutomaticZenRule.Builder("Test3", Uri.parse("uri2")).build(),
+                ORIGIN_APP,
                 "add it", CUSTOM_PKG_UID);
         String id4 = mZenModeHelper.addAutomaticZenRule("pkg2",
-                new AutomaticZenRule.Builder("Test4", Uri.parse("uri1")).build(), UPDATE_ORIGIN_APP,
+                new AutomaticZenRule.Builder("Test4", Uri.parse("uri1")).build(),
+                ORIGIN_APP,
                 "add it", CUSTOM_PKG_UID);
         String id5 = mZenModeHelper.addAutomaticZenRule("pkg2",
-                new AutomaticZenRule.Builder("Test5", Uri.parse("uri1")).build(), UPDATE_ORIGIN_APP,
+                new AutomaticZenRule.Builder("Test5", Uri.parse("uri1")).build(),
+                ORIGIN_APP,
                 "add it", CUSTOM_PKG_UID);
         for (ZenRule zenRule : mZenModeHelper.mConfig.automaticRules.values()) {
             zenRule.userModifiedFields = AutomaticZenRule.FIELD_INTERRUPTION_FILTER;
         }
 
-        mZenModeHelper.removeAutomaticZenRule(id1, UPDATE_ORIGIN_APP, "begone", CUSTOM_PKG_UID);
-        mZenModeHelper.removeAutomaticZenRule(id2, UPDATE_ORIGIN_APP, "begone", CUSTOM_PKG_UID);
-        mZenModeHelper.removeAutomaticZenRule(id3, UPDATE_ORIGIN_APP, "begone", CUSTOM_PKG_UID);
-        mZenModeHelper.removeAutomaticZenRule(id4, UPDATE_ORIGIN_APP, "begone", CUSTOM_PKG_UID);
-        mZenModeHelper.removeAutomaticZenRule(id5, UPDATE_ORIGIN_APP, "begone", CUSTOM_PKG_UID);
+        mZenModeHelper.removeAutomaticZenRule(id1, ORIGIN_APP, "begone", CUSTOM_PKG_UID);
+        mZenModeHelper.removeAutomaticZenRule(id2, ORIGIN_APP, "begone", CUSTOM_PKG_UID);
+        mZenModeHelper.removeAutomaticZenRule(id3, ORIGIN_APP, "begone", CUSTOM_PKG_UID);
+        mZenModeHelper.removeAutomaticZenRule(id4, ORIGIN_APP, "begone", CUSTOM_PKG_UID);
+        mZenModeHelper.removeAutomaticZenRule(id5, ORIGIN_APP, "begone", CUSTOM_PKG_UID);
 
         assertThat(mZenModeHelper.mConfig.deletedRules.keySet())
                 .containsExactly("pkg1|uri1", "pkg1|uri2", "pkg2|uri1");
@@ -5557,17 +5587,19 @@
         mZenModeHelper.mConfig.automaticRules.clear();
 
         mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                new AutomaticZenRule.Builder("Test1", Uri.parse("uri1")).build(), UPDATE_ORIGIN_APP,
+                new AutomaticZenRule.Builder("Test1", Uri.parse("uri1")).build(),
+                ORIGIN_APP,
                 "add it", CUSTOM_PKG_UID);
         mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                new AutomaticZenRule.Builder("Test2", Uri.parse("uri2")).build(), UPDATE_ORIGIN_APP,
+                new AutomaticZenRule.Builder("Test2", Uri.parse("uri2")).build(),
+                ORIGIN_APP,
                 "add it", CUSTOM_PKG_UID);
 
         for (ZenRule zenRule : mZenModeHelper.mConfig.automaticRules.values()) {
             zenRule.userModifiedFields = AutomaticZenRule.FIELD_INTERRUPTION_FILTER;
         }
 
-        mZenModeHelper.removeAutomaticZenRules(mContext.getPackageName(), UPDATE_ORIGIN_APP,
+        mZenModeHelper.removeAutomaticZenRules(mContext.getPackageName(), ORIGIN_APP,
                 "begone", CUSTOM_PKG_UID);
 
         assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(2);
@@ -5586,7 +5618,7 @@
         mZenModeHelper.mConfig.deletedRules.put(ZenModeConfig.deletedRuleKey(pkg2Rule), pkg2Rule);
 
         mZenModeHelper.removeAutomaticZenRules("pkg1",
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "goodbye pkg1", Process.SYSTEM_UID);
+                ZenModeConfig.ORIGIN_SYSTEM, "goodbye pkg1", SYSTEM_UID);
 
         // Preserved rules from pkg1 are gone; those from pkg2 are still there.
         assertThat(mZenModeHelper.mConfig.deletedRules.values().stream().map(r -> r.pkg)
@@ -5603,23 +5635,23 @@
                 .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
                 .build();
         String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
-                UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+                ORIGIN_APP, "add it", CUSTOM_PKG_UID);
 
         // User customizes it.
         AutomaticZenRule userUpdate = new AutomaticZenRule.Builder(rule)
                 .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
                 .build();
-        mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdate, UPDATE_ORIGIN_USER, "userUpdate",
-                Process.SYSTEM_UID);
+        mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdate, ORIGIN_USER_IN_SYSTEMUI,
+                "userUpdate", SYSTEM_UID);
         assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
 
         // App activates it.
-        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
                 CUSTOM_PKG_UID);
         assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS);
 
         // App deletes it.
-        mZenModeHelper.removeAutomaticZenRule(ruleId, UPDATE_ORIGIN_APP, "delete it",
+        mZenModeHelper.removeAutomaticZenRule(ruleId, ORIGIN_APP, "delete it",
                 CUSTOM_PKG_UID);
         assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(0);
         assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(1);
@@ -5627,7 +5659,7 @@
 
         // App adds it again.
         String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
-                UPDATE_ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
+                ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
 
         // The rule is restored...
         assertThat(newRuleId).isEqualTo(ruleId);
@@ -5652,35 +5684,35 @@
                 .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
                 .build();
         String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
-                UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+                ORIGIN_APP, "add it", CUSTOM_PKG_UID);
 
         // User customizes it.
         AutomaticZenRule userUpdate = new AutomaticZenRule.Builder(rule)
                 .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
                 .build();
-        mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdate, UPDATE_ORIGIN_USER, "userUpdate",
-                Process.SYSTEM_UID);
+        mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdate, ORIGIN_USER_IN_SYSTEMUI,
+                "userUpdate", SYSTEM_UID);
         assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
 
         // App activates it.
-        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
                 CUSTOM_PKG_UID);
         assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS);
 
         // User snoozes it.
-        mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
-                "snoozing", "systemui", Process.SYSTEM_UID);
+        mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, ZenModeConfig.ORIGIN_SYSTEM,
+                "snoozing", "systemui", SYSTEM_UID);
         assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
 
         // App deletes it.
-        mZenModeHelper.removeAutomaticZenRule(ruleId, UPDATE_ORIGIN_APP, "delete it",
+        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.
         String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
-                UPDATE_ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
+                ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
 
         // The rule is restored...
         assertThat(newRuleId).isEqualTo(ruleId);
@@ -5692,7 +5724,7 @@
         assertThat(storedRule.isAutomaticActive()).isFalse();
         assertThat(storedRule.isTrueOrUnknown()).isFalse();
         assertThat(storedRule.condition).isNull();
-        assertThat(storedRule.snoozing).isFalse();
+        assertThat(storedRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE);
         assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
     }
 
@@ -5762,20 +5794,20 @@
                         .setConfigurationActivity(
                                 new ComponentName(mContext.getPackageName(), "Blah"))
                         .build(),
-                UPDATE_ORIGIN_APP, "reasons", CUSTOM_PKG_UID);
+                ORIGIN_APP, "reasons", CUSTOM_PKG_UID);
 
         // Null condition -> STATE_FALSE
         assertThat(mZenModeHelper.getAutomaticZenRuleState(id)).isEqualTo(Condition.STATE_FALSE);
 
-        mZenModeHelper.setAutomaticZenRuleState(id, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+        mZenModeHelper.setAutomaticZenRuleState(id, CONDITION_TRUE, ORIGIN_APP,
                 CUSTOM_PKG_UID);
         assertThat(mZenModeHelper.getAutomaticZenRuleState(id)).isEqualTo(Condition.STATE_TRUE);
 
-        mZenModeHelper.setAutomaticZenRuleState(id, CONDITION_FALSE, UPDATE_ORIGIN_APP,
+        mZenModeHelper.setAutomaticZenRuleState(id, CONDITION_FALSE, ORIGIN_APP,
                 CUSTOM_PKG_UID);
         assertThat(mZenModeHelper.getAutomaticZenRuleState(id)).isEqualTo(Condition.STATE_FALSE);
 
-        mZenModeHelper.removeAutomaticZenRule(id, UPDATE_ORIGIN_APP, "", CUSTOM_PKG_UID);
+        mZenModeHelper.removeAutomaticZenRule(id, ORIGIN_APP, "", CUSTOM_PKG_UID);
         assertThat(mZenModeHelper.getAutomaticZenRuleState(id)).isEqualTo(Condition.STATE_UNKNOWN);
     }
 
@@ -5788,7 +5820,7 @@
         systemRule.condition = new Condition(systemRule.conditionId, "on", Condition.STATE_TRUE);
         ZenModeConfig config = mZenModeHelper.mConfig.copy();
         config.automaticRules.put("systemRule", systemRule);
-        mZenModeHelper.setConfig(config, null, UPDATE_ORIGIN_INIT, "", Process.SYSTEM_UID);
+        mZenModeHelper.setConfig(config, null, ORIGIN_INIT, "", SYSTEM_UID);
         assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS);
 
         assertThat(mZenModeHelper.getAutomaticZenRuleState("systemRule")).isEqualTo(
@@ -5805,13 +5837,13 @@
         otherRule.condition = new Condition(otherRule.conditionId, "on", Condition.STATE_TRUE);
         ZenModeConfig config = mZenModeHelper.mConfig.copy();
         config.automaticRules.put("otherRule", otherRule);
-        mZenModeHelper.setConfig(config, null, UPDATE_ORIGIN_INIT, "", Process.SYSTEM_UID);
+        mZenModeHelper.setConfig(config, null, ORIGIN_INIT, "", SYSTEM_UID);
         assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS);
 
         // Should be ignored.
         mZenModeHelper.setAutomaticZenRuleState("otherRule",
                 new Condition(otherRule.conditionId, "off", Condition.STATE_FALSE),
-                UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
+                ORIGIN_APP, CUSTOM_PKG_UID);
 
         assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS);
     }
@@ -5826,13 +5858,13 @@
         otherRule.condition = new Condition(otherRule.conditionId, "on", Condition.STATE_TRUE);
         ZenModeConfig config = mZenModeHelper.mConfig.copy();
         config.automaticRules.put("otherRule", otherRule);
-        mZenModeHelper.setConfig(config, null, UPDATE_ORIGIN_INIT, "", Process.SYSTEM_UID);
+        mZenModeHelper.setConfig(config, null, ORIGIN_INIT, "", SYSTEM_UID);
         assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS);
 
         // Should be ignored.
         mZenModeHelper.setAutomaticZenRuleState(otherRule.conditionId,
                 new Condition(otherRule.conditionId, "off", Condition.STATE_FALSE),
-                UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
+                ORIGIN_APP, CUSTOM_PKG_UID);
 
         assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS);
     }
@@ -5851,7 +5883,7 @@
         });
 
         Policy totalSilencePolicy = new Policy(0, 0, 0);
-        mZenModeHelper.setNotificationPolicy(totalSilencePolicy, UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
+        mZenModeHelper.setNotificationPolicy(totalSilencePolicy, ORIGIN_APP, CUSTOM_PKG_UID);
 
         Policy callbackPolicy = futurePolicy.get(1, TimeUnit.SECONDS);
         assertThat(callbackPolicy.allowReminders()).isFalse();
@@ -5874,9 +5906,9 @@
                         .setOwner(OWNER)
                         .setInterruptionFilter(INTERRUPTION_FILTER_NONE)
                         .build(),
-                UPDATE_ORIGIN_APP, "reasons", 0);
+                ORIGIN_APP, "reasons", 0);
         mZenModeHelper.setAutomaticZenRuleState(totalSilenceRuleId,
-                new Condition(CONDITION_ID, "", STATE_TRUE), UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
+                new Condition(CONDITION_ID, "", STATE_TRUE), ORIGIN_APP, CUSTOM_PKG_UID);
 
         Policy callbackPolicy = futureConsolidatedPolicy.get(1, TimeUnit.SECONDS);
         assertThat(callbackPolicy.allowMedia()).isFalse();
@@ -5905,7 +5937,7 @@
 
         mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
                 ZEN_MODE_IMPORTANT_INTERRUPTIONS);
-        mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, UPDATE_ORIGIN_APP, "test", "test", 0);
+        mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, ORIGIN_APP, "test", "test", 0);
         assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(1);
 
         mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
@@ -5937,8 +5969,8 @@
         AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule)
                 .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
                 .build();
-        mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, UPDATE_ORIGIN_USER, "reason",
-                Process.SYSTEM_UID);
+        mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, ORIGIN_USER_IN_SYSTEMUI,
+                "reason", SYSTEM_UID);
 
         // From app, call "setInterruptionFilter" again.
         mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, CUSTOM_PKG_UID,
@@ -5969,8 +6001,8 @@
         AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule)
                 .setName("Renamed")
                 .build();
-        mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, UPDATE_ORIGIN_USER, "reason",
-                Process.SYSTEM_UID);
+        mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, ORIGIN_USER_IN_SYSTEMUI,
+                "reason", SYSTEM_UID);
 
         // From app, call "setInterruptionFilter" again.
         mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, CUSTOM_PKG_UID,
@@ -6018,15 +6050,18 @@
         mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
                 ZEN_MODE_IMPORTANT_INTERRUPTIONS);
         assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(1);
-        assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).snoozing).isFalse();
+        assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).getConditionOverride())
+                .isEqualTo(OVERRIDE_NONE);
 
-        mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, UPDATE_ORIGIN_APP, "test", "test", 0);
-        assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).snoozing).isTrue();
+        mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, ORIGIN_APP, "test", "test", 0);
+        assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).getConditionOverride())
+                .isEqualTo(OVERRIDE_DEACTIVATE);
 
         mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
                 ZEN_MODE_ALARMS);
 
-        assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).snoozing).isFalse();
+        assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).getConditionOverride())
+                .isEqualTo(OVERRIDE_NONE);
         assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).condition.state)
                 .isEqualTo(STATE_TRUE);
     }
@@ -6121,8 +6156,8 @@
         AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule)
                 .setZenPolicy(userUpdateZenPolicy)
                 .build();
-        mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, UPDATE_ORIGIN_USER, "reason",
-                Process.SYSTEM_UID);
+        mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, ORIGIN_USER_IN_SYSTEMUI,
+                "reason", SYSTEM_UID);
 
         // From app, call "setNotificationPolicy" again.
         Policy appUpdatePolicy = new Policy(PRIORITY_CATEGORY_SYSTEM, 0, 0);
@@ -6159,8 +6194,8 @@
         AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule)
                 .setName("Rule renamed, not touching policy")
                 .build();
-        mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, UPDATE_ORIGIN_USER, "reason",
-                Process.SYSTEM_UID);
+        mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, ORIGIN_USER_IN_SYSTEMUI,
+                "reason", SYSTEM_UID);
 
         // From app, call "setNotificationPolicy" again.
         Policy appUpdatePolicy = new Policy(PRIORITY_CATEGORY_SYSTEM, 0, 0);
@@ -6215,7 +6250,7 @@
         // If the policy then changes afterwards, it should inherit updates because user cannot
         // edit the policy in the UI.
         mZenModeHelper.setNotificationPolicy(new Policy(PRIORITY_CATEGORY_ALARMS, 0, 0),
-                UPDATE_ORIGIN_APP, 1);
+                ORIGIN_APP, 1);
         Policy readPolicy = mZenModeHelper.getNotificationPolicyFromImplicitZenRule(
                 CUSTOM_PKG_NAME);
 
@@ -6228,7 +6263,7 @@
     @EnableFlags(FLAG_MODES_API)
     public void getNotificationPolicyFromImplicitZenRule_noImplicitRule_returnsGlobalPolicy() {
         Policy policy = new Policy(PRIORITY_CATEGORY_CALLS, PRIORITY_SENDERS_STARRED, 0);
-        mZenModeHelper.setNotificationPolicy(policy, UPDATE_ORIGIN_USER, 1);
+        mZenModeHelper.setNotificationPolicy(policy, ORIGIN_APP, CUSTOM_PKG_UID);
 
         Policy readPolicy = mZenModeHelper.getNotificationPolicyFromImplicitZenRule(
                 CUSTOM_PKG_NAME);
@@ -6264,7 +6299,7 @@
                 ZEN_MODE_IMPORTANT_INTERRUPTIONS, previousManualZenPolicy);
 
         Policy newManualPolicy = new Policy(PRIORITY_CATEGORY_EVENTS, 0, 0);
-        mZenModeHelper.setNotificationPolicy(newManualPolicy, UPDATE_ORIGIN_USER, 0);
+        mZenModeHelper.setNotificationPolicy(newManualPolicy, ORIGIN_APP, CUSTOM_PKG_UID);
         ZenPolicy newManualZenPolicy = ZenAdapters.notificationPolicyToZenPolicy(newManualPolicy);
 
         // Only app rules with default or same-as-manual policies were updated.
@@ -6294,7 +6329,7 @@
 
         String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
                 new AutomaticZenRule.Builder("Rule", CONDITION_ID).setIconResId(resourceId).build(),
-                UPDATE_ORIGIN_APP, "reason", CUSTOM_PKG_UID);
+                ORIGIN_APP, "reason", CUSTOM_PKG_UID);
         AutomaticZenRule storedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
 
         assertThat(storedRule.getIconResId()).isEqualTo(0);
@@ -6306,7 +6341,8 @@
         ZenDeviceEffects effects = new ZenDeviceEffects.Builder()
                 .setShouldDimWallpaper(true)
                 .build();
-        mZenModeHelper.setManualZenRuleDeviceEffects(effects, UPDATE_ORIGIN_USER, "settings", 1000);
+        mZenModeHelper.setManualZenRuleDeviceEffects(effects, ORIGIN_USER_IN_SYSTEMUI, "settings",
+                SYSTEM_UID);
 
         assertThat(mZenModeHelper.getConfig().manualRule).isNotNull();
         assertThat(mZenModeHelper.getConfig().isManualActive()).isFalse();
@@ -6316,13 +6352,14 @@
     @Test
     @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
     public void setManualZenRuleDeviceEffects_preexistingMode() {
-        mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, Uri.EMPTY, UPDATE_ORIGIN_USER,
-                "create manual rule", "settings", 1000);
+        mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, Uri.EMPTY, ORIGIN_USER_IN_SYSTEMUI,
+                "create manual rule", "settings", SYSTEM_UID);
 
         ZenDeviceEffects effects = new ZenDeviceEffects.Builder()
                 .setShouldDimWallpaper(true)
                 .build();
-        mZenModeHelper.setManualZenRuleDeviceEffects(effects, UPDATE_ORIGIN_USER, "settings", 1000);
+        mZenModeHelper.setManualZenRuleDeviceEffects(effects, ORIGIN_USER_IN_SYSTEMUI, "settings",
+                SYSTEM_UID);
 
         assertThat(mZenModeHelper.getConfig().manualRule).isNotNull();
         assertThat(mZenModeHelper.getConfig().isManualActive()).isFalse();
@@ -6337,11 +6374,12 @@
                 .setEnabled(false)
                 .build();
 
-        String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, startsDisabled, UPDATE_ORIGIN_APP,
+        String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, startsDisabled,
+                ORIGIN_APP,
                 "new", CUSTOM_PKG_UID);
 
         assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).disabledOrigin).isEqualTo(
-                UPDATE_ORIGIN_APP);
+                ORIGIN_APP);
     }
 
     @Test
@@ -6351,19 +6389,20 @@
                 .setOwner(new ComponentName(mPkg, "SomeProvider"))
                 .setEnabled(true)
                 .build();
-        String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, startsEnabled, UPDATE_ORIGIN_APP,
+        String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, startsEnabled,
+                ORIGIN_APP,
                 "new", CUSTOM_PKG_UID);
         assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).disabledOrigin).isEqualTo(
-                UPDATE_ORIGIN_UNKNOWN);
+                ORIGIN_UNKNOWN);
 
         AutomaticZenRule nowDisabled = new AutomaticZenRule.Builder(startsEnabled)
                 .setEnabled(false)
                 .build();
-        mZenModeHelper.updateAutomaticZenRule(ruleId, nowDisabled, UPDATE_ORIGIN_USER, "off",
-                Process.SYSTEM_UID);
+        mZenModeHelper.updateAutomaticZenRule(ruleId, nowDisabled, ORIGIN_USER_IN_SYSTEMUI, "off",
+                SYSTEM_UID);
 
         assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).disabledOrigin).isEqualTo(
-                UPDATE_ORIGIN_USER);
+                ORIGIN_USER_IN_SYSTEMUI);
     }
 
     @Test
@@ -6373,26 +6412,27 @@
                 .setOwner(new ComponentName(mPkg, "SomeProvider"))
                 .setEnabled(true)
                 .build();
-        String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, startsEnabled, UPDATE_ORIGIN_APP,
+        String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, startsEnabled,
+                ORIGIN_APP,
                 "new", CUSTOM_PKG_UID);
         AutomaticZenRule nowDisabled = new AutomaticZenRule.Builder(startsEnabled)
                 .setEnabled(false)
                 .build();
-        mZenModeHelper.updateAutomaticZenRule(ruleId, nowDisabled, UPDATE_ORIGIN_USER, "off",
-                Process.SYSTEM_UID);
+        mZenModeHelper.updateAutomaticZenRule(ruleId, nowDisabled, ORIGIN_USER_IN_SYSTEMUI, "off",
+                SYSTEM_UID);
         assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).disabledOrigin).isEqualTo(
-                UPDATE_ORIGIN_USER);
+                ORIGIN_USER_IN_SYSTEMUI);
 
         // Now update it again, for an unrelated reason with a different origin.
         AutomaticZenRule nowRenamed = new AutomaticZenRule.Builder(nowDisabled)
                 .setName("Fancy pants rule")
                 .build();
-        mZenModeHelper.updateAutomaticZenRule(ruleId, nowRenamed, UPDATE_ORIGIN_APP, "update",
+        mZenModeHelper.updateAutomaticZenRule(ruleId, nowRenamed, ORIGIN_APP, "update",
                 CUSTOM_PKG_UID);
 
         // Identity of the disabler is preserved.
         assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).disabledOrigin).isEqualTo(
-                UPDATE_ORIGIN_USER);
+                ORIGIN_USER_IN_SYSTEMUI);
     }
 
     @Test
@@ -6402,26 +6442,234 @@
                 .setOwner(new ComponentName(mPkg, "SomeProvider"))
                 .setEnabled(true)
                 .build();
-        String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, startsEnabled, UPDATE_ORIGIN_APP,
+        String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, startsEnabled,
+                ORIGIN_APP,
                 "new", CUSTOM_PKG_UID);
         AutomaticZenRule nowDisabled = new AutomaticZenRule.Builder(startsEnabled)
                 .setEnabled(false)
                 .build();
-        mZenModeHelper.updateAutomaticZenRule(ruleId, nowDisabled, UPDATE_ORIGIN_USER, "off",
-                Process.SYSTEM_UID);
+        mZenModeHelper.updateAutomaticZenRule(ruleId, nowDisabled, ORIGIN_USER_IN_SYSTEMUI, "off",
+                SYSTEM_UID);
         assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).disabledOrigin).isEqualTo(
-                UPDATE_ORIGIN_USER);
+                ORIGIN_USER_IN_SYSTEMUI);
 
         // Now enable it again
         AutomaticZenRule nowEnabled = new AutomaticZenRule.Builder(nowDisabled)
                 .setEnabled(true)
                 .build();
-        mZenModeHelper.updateAutomaticZenRule(ruleId, nowEnabled, UPDATE_ORIGIN_APP, "on",
+        mZenModeHelper.updateAutomaticZenRule(ruleId, nowEnabled, ORIGIN_APP, "on",
                 CUSTOM_PKG_UID);
 
         // Identity of the disabler was cleared.
         assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).disabledOrigin).isEqualTo(
-                UPDATE_ORIGIN_UNKNOWN);
+                ORIGIN_UNKNOWN);
+    }
+
+    @Test
+    @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+    public void setAutomaticZenRuleState_manualActivation_appliesOverride() {
+        AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
+                .setPackage(mPkg)
+                .build();
+        String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "adding",
+                CUSTOM_PKG_UID);
+
+        mZenModeHelper.setAutomaticZenRuleState(ruleId,
+                new Condition(rule.getConditionId(), "manual-on", STATE_TRUE, SOURCE_USER_ACTION),
+                ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
+
+        ZenRule zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+        assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_ACTIVATE);
+        assertThat(zenRule.condition).isNull();
+    }
+
+    @Test
+    @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+    public void setAutomaticZenRuleState_manualActivationAndThenDeactivation_removesOverride() {
+        AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
+                .setPackage(mPkg)
+                .build();
+        String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "adding",
+                CUSTOM_PKG_UID);
+        Condition autoOn = new Condition(rule.getConditionId(), "auto-on", STATE_TRUE,
+                SOURCE_CONTEXT);
+        ZenRule zenRule;
+
+        mZenModeHelper.setAutomaticZenRuleState(ruleId,
+                new Condition(rule.getConditionId(), "manual-on", STATE_TRUE, SOURCE_USER_ACTION),
+                ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
+        zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+        assertThat(zenRule.isAutomaticActive()).isTrue();
+        assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_ACTIVATE);
+        assertThat(zenRule.condition).isNull();
+
+        mZenModeHelper.setAutomaticZenRuleState(ruleId,
+                new Condition(rule.getConditionId(), "manual-off", STATE_FALSE, SOURCE_USER_ACTION),
+                ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
+        zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+        assertThat(zenRule.isAutomaticActive()).isFalse();
+        assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE);
+        assertThat(zenRule.condition).isNull();
+
+        // Bonus check: app has resumed control over the rule and can now turn it on.
+        mZenModeHelper.setAutomaticZenRuleState(ruleId, autoOn,  ORIGIN_APP, CUSTOM_PKG_UID);
+        zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+        assertThat(zenRule.isAutomaticActive()).isTrue();
+        assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE);
+        assertThat(zenRule.condition).isEqualTo(autoOn);
+    }
+
+    @Test
+    @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+    public void setAutomaticZenRuleState_manualDeactivationAndThenReactivation_removesOverride() {
+        AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
+                .setPackage(mPkg)
+                .build();
+        String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "adding",
+                CUSTOM_PKG_UID);
+        Condition autoOn = new Condition(rule.getConditionId(), "auto-on", STATE_TRUE,
+                SOURCE_CONTEXT);
+        Condition autoOff = new Condition(rule.getConditionId(), "auto-off", STATE_FALSE,
+                SOURCE_CONTEXT);
+        ZenRule zenRule;
+
+        mZenModeHelper.setAutomaticZenRuleState(ruleId, autoOn,  ORIGIN_APP, CUSTOM_PKG_UID);
+        zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+        assertThat(zenRule.isAutomaticActive()).isTrue();
+        assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE);
+        assertThat(zenRule.condition).isEqualTo(autoOn);
+
+        mZenModeHelper.setAutomaticZenRuleState(ruleId,
+                new Condition(rule.getConditionId(), "manual-off", STATE_FALSE, SOURCE_USER_ACTION),
+                ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
+        zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+        assertThat(zenRule.isAutomaticActive()).isFalse();
+        assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_DEACTIVATE);
+        assertThat(zenRule.condition).isEqualTo(autoOn);
+
+        mZenModeHelper.setAutomaticZenRuleState(ruleId,
+                new Condition(rule.getConditionId(), "manual-on", STATE_TRUE, SOURCE_USER_ACTION),
+                ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
+        zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+        assertThat(zenRule.isAutomaticActive()).isTrue();
+        assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE);
+        assertThat(zenRule.condition).isEqualTo(autoOn);
+
+        // Bonus check: app has resumed control over the rule and can now turn it off.
+        mZenModeHelper.setAutomaticZenRuleState(ruleId, autoOff,  ORIGIN_APP, CUSTOM_PKG_UID);
+        zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+        assertThat(zenRule.isAutomaticActive()).isFalse();
+        assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE);
+        assertThat(zenRule.condition).isEqualTo(autoOff);
+    }
+
+    @Test
+    @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+    public void setAutomaticZenRuleState_manualDeactivation_appliesOverride() {
+        AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
+                .setPackage(mPkg)
+                .build();
+        String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "adding",
+                CUSTOM_PKG_UID);
+
+        mZenModeHelper.setAutomaticZenRuleState(ruleId,
+                new Condition(rule.getConditionId(), "auto-on", STATE_TRUE, SOURCE_CONTEXT),
+                ORIGIN_APP, CUSTOM_PKG_UID);
+        ZenRule zenRuleOn = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+        assertThat(zenRuleOn.isAutomaticActive()).isTrue();
+        assertThat(zenRuleOn.getConditionOverride()).isEqualTo(OVERRIDE_NONE);
+        assertThat(zenRuleOn.condition).isNotNull();
+
+        mZenModeHelper.setAutomaticZenRuleState(ruleId,
+                new Condition(rule.getConditionId(), "manual-off", STATE_FALSE, SOURCE_USER_ACTION),
+                ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
+        ZenRule zenRuleOff = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+        assertThat(zenRuleOff.isAutomaticActive()).isFalse();
+        assertThat(zenRuleOff.getConditionOverride()).isEqualTo(OVERRIDE_DEACTIVATE);
+        assertThat(zenRuleOff.condition).isNotNull();
+    }
+
+    @Test
+    @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+    public void setAutomaticZenRuleState_ifManualActive_appCannotDeactivateBeforeActivating() {
+        AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
+                .setPackage(mPkg)
+                .build();
+        String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "adding",
+                CUSTOM_PKG_UID);
+        ZenRule zenRule;
+
+        mZenModeHelper.setAutomaticZenRuleState(ruleId,
+                new Condition(rule.getConditionId(), "manual-on", STATE_TRUE, SOURCE_USER_ACTION),
+                ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
+        zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+        assertThat(zenRule.isAutomaticActive()).isTrue();
+        assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_ACTIVATE);
+
+        mZenModeHelper.setAutomaticZenRuleState(ruleId,
+                new Condition(rule.getConditionId(), "auto-off", STATE_FALSE, SOURCE_CONTEXT),
+                ORIGIN_APP, CUSTOM_PKG_UID);
+        zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+        assertThat(zenRule.isAutomaticActive()).isTrue();
+        assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_ACTIVATE);
+
+        mZenModeHelper.setAutomaticZenRuleState(ruleId,
+                new Condition(rule.getConditionId(), "auto-on", STATE_TRUE, SOURCE_CONTEXT),
+                ORIGIN_APP, CUSTOM_PKG_UID);
+        zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+        assertThat(zenRule.isAutomaticActive()).isTrue();
+        assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE);
+        mZenModeHelper.setAutomaticZenRuleState(ruleId,
+                new Condition(rule.getConditionId(), "auto-off", STATE_FALSE, SOURCE_CONTEXT),
+                ORIGIN_APP, CUSTOM_PKG_UID);
+        zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+        assertThat(zenRule.isAutomaticActive()).isFalse();
+    }
+
+    @Test
+    @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+    public void setAutomaticZenRuleState_ifManualInactive_appCannotReactivateBeforeDeactivating() {
+        AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
+                .setPackage(mPkg)
+                .build();
+        String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "adding",
+                CUSTOM_PKG_UID);
+        ZenRule zenRule;
+
+        mZenModeHelper.setAutomaticZenRuleState(ruleId,
+                new Condition(rule.getConditionId(), "auto-on", STATE_TRUE, SOURCE_CONTEXT),
+                ORIGIN_APP, CUSTOM_PKG_UID);
+        zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+        assertThat(zenRule.isAutomaticActive()).isTrue();
+        assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE);
+
+        mZenModeHelper.setAutomaticZenRuleState(ruleId,
+                new Condition(rule.getConditionId(), "manual-off", STATE_FALSE, SOURCE_USER_ACTION),
+                ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
+        zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+        assertThat(zenRule.isAutomaticActive()).isFalse();
+        assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_DEACTIVATE);
+
+        mZenModeHelper.setAutomaticZenRuleState(ruleId,
+                new Condition(rule.getConditionId(), "auto-on", STATE_TRUE, SOURCE_CONTEXT),
+                ORIGIN_APP, CUSTOM_PKG_UID);
+        zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+        assertThat(zenRule.isAutomaticActive()).isFalse();
+        assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_DEACTIVATE);
+
+        mZenModeHelper.setAutomaticZenRuleState(ruleId,
+                new Condition(rule.getConditionId(), "auto-off", STATE_FALSE, SOURCE_CONTEXT),
+                ORIGIN_APP, CUSTOM_PKG_UID);
+        zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+        assertThat(zenRule.isAutomaticActive()).isFalse();
+        assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE);
+
+        mZenModeHelper.setAutomaticZenRuleState(ruleId,
+                new Condition(rule.getConditionId(), "auto-on", STATE_TRUE, SOURCE_CONTEXT),
+                ORIGIN_APP, CUSTOM_PKG_UID);
+        zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+        assertThat(zenRule.isAutomaticActive()).isTrue();
+        assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE);
     }
 
     private static void addZenRule(ZenModeConfig config, String id, String ownerPkg, int zenMode,
@@ -6494,7 +6742,7 @@
                 SUPPRESSED_EFFECT_BADGE,
                 0,
                 CONVERSATION_SENDERS_IMPORTANT);
-        mZenModeHelper.setNotificationPolicy(customPolicy, UPDATE_ORIGIN_UNKNOWN, 1);
+        mZenModeHelper.setNotificationPolicy(customPolicy, ORIGIN_UNKNOWN, 1);
         if (!Flags.modesUi()) {
             mZenModeHelper.mConfig.manualRule = null;
         }
diff --git a/services/tests/vibrator/Android.bp b/services/tests/vibrator/Android.bp
index 757bcd8..43ad44f 100644
--- a/services/tests/vibrator/Android.bp
+++ b/services/tests/vibrator/Android.bp
@@ -32,6 +32,7 @@
         "frameworks-base-testutils",
         "frameworks-services-vibrator-testutils",
         "junit",
+        "junit-params",
         "mockito-target-inline-minus-junit4",
         "platform-test-annotations",
         "service-permission.stubs.system_server",
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java b/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java
index 59d5577..1493253 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java
@@ -21,6 +21,7 @@
 import static org.mockito.Mockito.when;
 
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.pm.PackageManagerInternal;
 import android.hardware.vibrator.IVibrator;
 import android.os.CombinedVibration;
@@ -32,11 +33,12 @@
 import android.os.vibrator.PrimitiveSegment;
 import android.os.vibrator.RampSegment;
 import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationConfig;
 import android.os.vibrator.VibrationEffectSegment;
 import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.util.SparseArray;
 
-import androidx.test.InstrumentationRegistry;
+import androidx.test.core.app.ApplicationProvider;
 
 import com.android.server.LocalServices;
 
@@ -76,9 +78,10 @@
         LocalServices.removeServiceForTest(PackageManagerInternal.class);
         LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternalMock);
 
+        Context context = ApplicationProvider.getApplicationContext();
         mTestLooper = new TestLooper();
-        mVibrationSettings = new VibrationSettings(
-                InstrumentationRegistry.getContext(), new Handler(mTestLooper.getLooper()));
+        mVibrationSettings = new VibrationSettings(context, new Handler(mTestLooper.getLooper()),
+                new VibrationConfig(context.getResources()));
 
         SparseArray<VibratorController> vibrators = new SparseArray<>();
         vibrators.put(EMPTY_VIBRATOR_ID, createEmptyVibratorController(EMPTY_VIBRATOR_ID));
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 2b23b18..e0d05df 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java
@@ -16,16 +16,17 @@
 
 package com.android.server.vibrator;
 
-
 import static android.os.VibrationEffect.Composition.PRIMITIVE_TICK;
 import static android.os.VibrationEffect.EFFECT_CLICK;
 
+import static com.android.internal.R.xml.haptic_feedback_customization;
 import static com.android.server.vibrator.HapticFeedbackCustomization.CustomizationParserException;
 
 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.doThrow;
 import static org.mockito.Mockito.when;
 
 import android.content.res.Resources;
@@ -39,10 +40,15 @@
 import androidx.test.InstrumentationRegistry;
 
 import com.android.internal.R;
+import com.android.internal.annotations.Keep;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
 
 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;
@@ -50,6 +56,7 @@
 import java.io.File;
 import java.io.FileOutputStream;
 
+@RunWith(JUnitParamsRunner.class)
 public class HapticFeedbackCustomizationTest {
     @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
@@ -78,21 +85,35 @@
     @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}};
+    }
+
     @Before
     public void setUp() {
         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);
     }
 
     @Test
-    public void testParseCustomizations_noCustomization_success() throws Exception {
-        assertParseCustomizationsSucceeds(
-                /* xml= */ "<haptic-feedback-constants></haptic-feedback-constants>",
-                /* expectedCustomizations= */ new SparseArray<>());
+    @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
-    public void testParseCustomizations_featureFlagDisabled_returnsNull() throws Exception {
+    @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+    public void testParseCustomizations_featureFlagDisabled_returnsNull(
+            boolean hasConfigFile, boolean hasRes) throws Exception {
         mSetFlagsRule.disableFlags(Flags.FLAG_HAPTIC_FEEDBACK_VIBRATION_OEM_CUSTOMIZATION_ENABLED);
         // Valid customization XML.
         String xml = "<haptic-feedback-constants>"
@@ -100,14 +121,16 @@
                 + COMPOSITION_VIBRATION_XML
                 + "</constant>"
                 + "</haptic-feedback-constants>";
-        setupCustomizationFile(xml);
 
+        setupParseCustomizations(xml, hasConfigFile, hasRes);
         assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock))
                 .isNull();
     }
 
     @Test
-    public void testParseCustomizations_oneVibrationCustomization_success() throws Exception {
+    @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
@@ -116,11 +139,13 @@
         SparseArray<VibrationEffect> expectedMapping = new SparseArray<>();
         expectedMapping.put(10, COMPOSITION_VIBRATION);
 
-        assertParseCustomizationsSucceeds(xml, expectedMapping);
+        assertParseCustomizationsSucceeds(xml, expectedMapping, hasConfigFile, hasRes);
     }
 
     @Test
-    public void testParseCustomizations_oneVibrationSelectCustomization_success() throws Exception {
+    @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+    public void testParseCustomizations_oneVibrationSelectCustomization_success(
+            boolean hasConfigFile, boolean hasRes) throws Exception {
         String xml = "<haptic-feedback-constants>"
                 + "<constant id=\"10\">"
                 + "<vibration-select>"
@@ -131,11 +156,13 @@
         SparseArray<VibrationEffect> expectedMapping = new SparseArray<>();
         expectedMapping.put(10, COMPOSITION_VIBRATION);
 
-        assertParseCustomizationsSucceeds(xml, expectedMapping);
+        assertParseCustomizationsSucceeds(xml, expectedMapping, hasConfigFile, hasRes);
     }
 
     @Test
-    public void testParseCustomizations_multipleCustomizations_success() throws Exception {
+    @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
@@ -162,11 +189,13 @@
         expectedMapping.put(150, PREDEFINED_VIBRATION);
         expectedMapping.put(10, WAVEFORM_VIBARTION);
 
-        assertParseCustomizationsSucceeds(xml, expectedMapping);
+        assertParseCustomizationsSucceeds(xml, expectedMapping, hasConfigFile, hasRes);
     }
 
     @Test
-    public void testParseCustomizations_multipleCustomizations_noSupportedVibration_success()
+    @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>"
@@ -189,13 +218,16 @@
                 + "</vibration-select>"
                 + "</constant>"
                 + "</haptic-feedback-constants>";
+        SparseArray<VibrationEffect> expectedMapping = new SparseArray<>();
 
-        assertParseCustomizationsSucceeds(xml, new SparseArray<>());
+        assertParseCustomizationsSucceeds(xml, expectedMapping, hasConfigFile, hasRes);
     }
 
     @Test
-    public void testParseCustomizations_multipleCustomizations_someUnsupportedVibration_success()
-                throws Exception {
+    @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+    public void testParseCustomizations_multipleCustomizations_someUnsupportedVibration_success(
+            boolean hasConfigFile, boolean hasRes)
+            throws Exception {
         makeSupported(PREDEFINED_VIBRATION, WAVEFORM_VIBARTION);
         makeUnsupported(COMPOSITION_VIBRATION);
         String xml = "<haptic-feedback-constants>"
@@ -230,7 +262,7 @@
         expectedMapping.put(150, PREDEFINED_VIBRATION);
         expectedMapping.put(10, PREDEFINED_VIBRATION);
 
-        assertParseCustomizationsSucceeds(xml, expectedMapping);
+        assertParseCustomizationsSucceeds(xml, expectedMapping, hasConfigFile, hasRes);
     }
 
     @Test
@@ -252,12 +284,23 @@
     }
 
     @Test
-    public void testParseCustomizations_disallowedVibrationForHapticFeedback_throwsException()
-            throws Exception {
+    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 {
         // The XML content is good, but the serialized vibration is not supported for haptic
         // feedback usage (i.e. repeating vibration).
-        assertParseCustomizationsFails(
-                "<haptic-feedback-constants>"
+        String xml = "<haptic-feedback-constants>"
                 + "<constant id=\"10\">"
                 + "<vibration-effect>"
                 + "<waveform-effect>"
@@ -267,127 +310,139 @@
                 + "</waveform-effect>"
                 + "</vibration-effect>"
                 + "</constant>"
-                + "</haptic-feedback-constants>");
+                + "</haptic-feedback-constants>";
+
+        assertParseCustomizationsFails(xml, hasConfigFile, hasRes);
     }
 
     @Test
-    public void testParseCustomizations_emptyXml_throwsException() throws Exception {
-        assertParseCustomizationsFails("");
+    @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+    public void testParseCustomizations_emptyXml_throwsException(
+            boolean hasConfigFile, boolean hasRes) throws Exception {
+        assertParseCustomizationsFails("", hasConfigFile, hasRes);
     }
 
     @Test
-    public void testParseCustomizations_noVibrationXml_throwsException() throws Exception {
-        assertParseCustomizationsFails(
-                "<haptic-feedback-constants>"
+    @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+    public void testParseCustomizations_noVibrationXml_throwsException(
+            boolean hasConfigFile, boolean hasRes) throws Exception {
+        String xml = "<haptic-feedback-constants>"
                 + "<constant id=\"1\">"
                 + "</constant>"
-                + "</haptic-feedback-constants>");
+                + "</haptic-feedback-constants>";
+
+        assertParseCustomizationsFails(xml, hasConfigFile, hasRes);
     }
 
     @Test
-    public void testParseCustomizations_badEffectId_throwsException() throws Exception {
+    @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+    public void testParseCustomizations_badEffectId_throwsException(
+            boolean hasConfigFile, boolean hasRes) throws Exception {
         // Negative id
-        assertParseCustomizationsFails(
-                "<haptic-feedback-constants>"
+        String xmlNegativeId = "<haptic-feedback-constants>"
                 + "<constant id=\"-10\">"
                 + COMPOSITION_VIBRATION_XML
                 + "</constant>"
-                + "</haptic-feedback-constants>");
-
+                + "</haptic-feedback-constants>";
         // Non-numeral id
-        assertParseCustomizationsFails(
-                "<haptic-feedback-constants>"
+        String xmlNonNumericalId = "<haptic-feedback-constants>"
                 + "<constant id=\"xyz\">"
                 + COMPOSITION_VIBRATION_XML
                 + "</constant>"
-                + "</haptic-feedback-constants>");
+                + "</haptic-feedback-constants>";
+
+        assertParseCustomizationsFails(xmlNegativeId, hasConfigFile, hasRes);
+        assertParseCustomizationsFails(xmlNonNumericalId, hasConfigFile, hasRes);
     }
 
     @Test
-    public void testParseCustomizations_malformedXml_throwsException() throws Exception {
+    @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+    public void testParseCustomizations_malformedXml_throwsException(
+            boolean hasConfigFile, boolean hasRes) throws Exception {
         // No start "<constant>" tag
-        assertParseCustomizationsFails(
-                "<haptic-feedback-constants>"
+        String xmlNoStartConstantTag = "<haptic-feedback-constants>"
                 + COMPOSITION_VIBRATION_XML
                 + "</constant>"
-                + "</haptic-feedback-constants>");
-
+                + "</haptic-feedback-constants>";
         // No end "<constant>" tag
-        assertParseCustomizationsFails(
-                "<haptic-feedback-constants>"
+        String xmlNoEndConstantTag = "<haptic-feedback-constants>"
                 + "<constant id=\"10\">"
                 + COMPOSITION_VIBRATION_XML
-                + "</haptic-feedback-constants>");
-
+                + "</haptic-feedback-constants>";
         // No start "<haptic-feedback-constants>" tag
-        assertParseCustomizationsFails(
-                "<constant id=\"10\">"
+        String xmlNoStartCustomizationTag = "<constant id=\"10\">"
                 + COMPOSITION_VIBRATION_XML
                 + "</constant>"
-                + "</haptic-feedback-constants>");
-
+                + "</haptic-feedback-constants>";
         // No end "<haptic-feedback-constants>" tag
-        assertParseCustomizationsFails(
-                "<haptic-feedback-constants>"
+        String xmlNoEndCustomizationTag = "<haptic-feedback-constants>"
                 + "<constant id=\"10\">"
                 + COMPOSITION_VIBRATION_XML
-                + "</constant>");
+                + "</constant>";
+
+        assertParseCustomizationsFails(xmlNoStartConstantTag, hasConfigFile, hasRes);
+        assertParseCustomizationsFails(xmlNoEndConstantTag, hasConfigFile, hasRes);
+        assertParseCustomizationsFails(xmlNoStartCustomizationTag, hasConfigFile, hasRes);
+        assertParseCustomizationsFails(xmlNoEndCustomizationTag, hasConfigFile, hasRes);
     }
 
     @Test
-    public void testParseCustomizations_badVibrationXml_throwsException() throws Exception {
-        assertParseCustomizationsFails(
-                "<haptic-feedback-constants>"
+    @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+    public void testParseCustomizations_badVibrationXml_throwsException(
+            boolean hasConfigFile, boolean hasRes) throws Exception {
+        String xmlBad1 = "<haptic-feedback-constants>"
                 + "<constant id=\"10\">"
                 + "<bad-vibration-effect></bad-vibration-effect>"
                 + "</constant>"
-                + "</haptic-feedback-constants>");
-
-        assertParseCustomizationsFails(
-                "<haptic-feedback-constants>"
+                + "</haptic-feedback-constants>";
+        String xmlBad2 = "<haptic-feedback-constants>"
                 + "<constant id=\"10\">"
                 + "<vibration-effect><predefined-effect name=\"bad-effect\"/></vibration-effect>"
                 + "</constant>"
-                + "</haptic-feedback-constants>");
-
-        assertParseCustomizationsFails(
-                "<haptic-feedback-constants>"
+                + "</haptic-feedback-constants>";
+        String xmlBad3 = "<haptic-feedback-constants>"
                 + "<constant id=\"10\">"
                 + "<vibration-select>"
                 + "<vibration-effect><predefined-effect name=\"bad-effect\"/></vibration-effect>"
                 + "</constant>"
-                + "</haptic-feedback-constants>");
-
-        assertParseCustomizationsFails(
-                "<haptic-feedback-constants>"
+                + "</haptic-feedback-constants>";
+        String xmlBad4 = "<haptic-feedback-constants>"
                 + "<constant id=\"10\">"
                 + "<vibration-effect><predefined-effect name=\"bad-effect\"/></vibration-effect>"
                 + "</vibration-select>"
                 + "</constant>"
-                + "</haptic-feedback-constants>");
+                + "</haptic-feedback-constants>";
+
+        assertParseCustomizationsFails(xmlBad1, hasConfigFile, hasRes);
+        assertParseCustomizationsFails(xmlBad2, hasConfigFile, hasRes);
+        assertParseCustomizationsFails(xmlBad3, hasConfigFile, hasRes);
+        assertParseCustomizationsFails(xmlBad4, hasConfigFile, hasRes);
     }
 
     @Test
-    public void testParseCustomizations_badConstantAttribute_throwsException() throws Exception {
-        assertParseCustomizationsFails(
-                "<haptic-feedback-constants>"
+    @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+    public void testParseCustomizations_badConstantAttribute_throwsException(
+            boolean hasConfigFile, boolean hasRes) throws Exception {
+        String xmlBadConstantAttribute1 = "<haptic-feedback-constants>"
                 + "<constant iddddd=\"10\">"
                 + COMPOSITION_VIBRATION_XML
                 + "</constant>"
-                + "</haptic-feedback-constants>");
-
-        assertParseCustomizationsFails(
-                "<haptic-feedback-constants>"
+                + "</haptic-feedback-constants>";
+        String xmlBadConstantAttribute2 = "<haptic-feedback-constants>"
                 + "<constant id=\"10\" unwanted-attr=\"1\">"
                 + COMPOSITION_VIBRATION_XML
                 + "</constant>"
-                + "</haptic-feedback-constants>");
+                + "</haptic-feedback-constants>";
+
+        assertParseCustomizationsFails(xmlBadConstantAttribute1, hasConfigFile, hasRes);
+        assertParseCustomizationsFails(xmlBadConstantAttribute2, hasConfigFile, hasRes);
     }
 
     @Test
-    public void testParseCustomizations_duplicateEffects_throwsException() throws Exception {
-        assertParseCustomizationsFails(
-                "<haptic-feedback-constants>"
+    @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+    public void testParseCustomizations_duplicateEffects_throwsException(
+            boolean hasConfigFile, boolean hasRes) throws Exception {
+        String xmlDuplicateEffect = "<haptic-feedback-constants>"
                 + "<constant id=\"10\">"
                 + COMPOSITION_VIBRATION_XML
                 + "</constant>"
@@ -397,32 +452,46 @@
                 + "<constant id=\"11\">"
                 + PREDEFINED_VIBRATION_XML
                 + "</constant>"
-                + "</haptic-feedback-constants>");
+                + "</haptic-feedback-constants>";
+
+        assertParseCustomizationsFails(xmlDuplicateEffect, hasConfigFile, hasRes);
     }
 
-    private void assertParseCustomizationsSucceeds(
-            String xml, SparseArray<VibrationEffect> expectedCustomizations) throws Exception {
-        setupCustomizationFile(xml);
+    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();
+                .isTrue();
     }
 
-    private void assertParseCustomizationsFails(String xml) throws Exception {
-        setupCustomizationFile(xml);
-        assertThrows("Expected haptic feedback customization to fail for " + xml,
-                CustomizationParserException.class,
-                () ->  HapticFeedbackCustomization.loadVibrations(
-                        mResourcesMock, mVibratorInfoMock));
-    }
-
-    private void assertParseCustomizationsFails() throws Exception {
+    private void assertParseCustomizationsFails(String xml, boolean hasConfigFile, boolean hasRes)
+            throws Exception {
+        setupParseCustomizations(xml, hasConfigFile, hasRes);
         assertThrows("Expected haptic feedback customization to fail",
                 CustomizationParserException.class,
                 () ->  HapticFeedbackCustomization.loadVibrations(
                         mResourcesMock, mVibratorInfoMock));
     }
 
+    private void setupParseCustomizations(String xml, boolean hasConfigFile, boolean hasRes)
+            throws Exception {
+        clearFileAndResourceSetup();
+        if (hasConfigFile) {
+            setupCustomizationFile(xml);
+        }
+        if (hasRes) {
+            setupCustomizationResource(xml);
+        }
+    }
+
+    private void clearFileAndResourceSetup() {
+        when(mResourcesMock.getString(R.string.config_hapticFeedbackCustomizationFile))
+                .thenReturn(null);
+        when(mResourcesMock.getXml(haptic_feedback_customization)).thenReturn(null);
+    }
+
     private void setupCustomizationFile(String xml) throws Exception {
         File file = createFile(xml);
         setCustomizationFilePath(file.getAbsolutePath());
@@ -433,6 +502,13 @@
                 .thenReturn(path);
     }
 
+    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 makeSupported(VibrationEffect... effects) {
         for (VibrationEffect effect : effects) {
             when(mVibratorInfoMock.areVibrationFeaturesSupported(effect)).thenReturn(true);
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 4f75931..240bd1e 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
@@ -50,7 +50,6 @@
 import android.os.VibrationAttributes;
 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;
@@ -256,22 +255,7 @@
     }
 
     @Test
-    public void testKeyboardHaptic_fixAmplitude_keyboardCategoryOff_defaultVibrationReturned() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED);
-        mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK);
-        mockKeyboardVibrationFixedAmplitude(KEYBOARD_VIBRATION_FIXED_AMPLITUDE);
-
-        HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
-
-        assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_TAP))
-                .isEqualTo(VibrationEffect.get(EFFECT_CLICK, true /* fallback */));
-        assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_RELEASE))
-                .isEqualTo(VibrationEffect.get(EFFECT_TICK, false /* fallback */));
-    }
-
-    @Test
     public void testKeyboardHaptic_fixAmplitude_keyboardCategoryOn_keyboardVibrationReturned() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED);
         mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK);
         mockKeyboardVibrationFixedAmplitude(KEYBOARD_VIBRATION_FIXED_AMPLITUDE);
 
@@ -346,24 +330,7 @@
     }
 
     @Test
-    public void testVibrationAttribute_keyboardCategoryOff_isIme_useTouchUsage() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED);
-        HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
-
-        for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
-            VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
-                    effectId, /* flags */ 0,
-                    HapticFeedbackConstants.PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS);
-            assertWithMessage("Expected USAGE_TOUCH for effect " + effectId)
-                    .that(attrs.getUsage()).isEqualTo(USAGE_TOUCH);
-            assertWithMessage("Expected no CATEGORY_KEYBOARD for effect " + effectId)
-                    .that(attrs.getCategory()).isEqualTo(CATEGORY_UNKNOWN);
-        }
-    }
-
-    @Test
     public void testVibrationAttribute_keyboardCategoryOn_notIme_useTouchUsage() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED);
         HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
 
         for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
@@ -378,7 +345,6 @@
 
     @Test
     public void testVibrationAttribute_keyboardCategoryOn_isIme_useImeFeedbackUsage() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED);
         HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
 
         for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
index 9ebeaa8..4704691 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
@@ -50,10 +50,9 @@
 import android.os.vibrator.StepSegment;
 import android.os.vibrator.VibrationConfig;
 import android.os.vibrator.VibrationEffectSegment;
-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.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
 
 import androidx.test.InstrumentationRegistry;
@@ -71,12 +70,13 @@
 import org.mockito.junit.MockitoRule;
 
 public class VibrationScalerTest {
+    private static final float TOLERANCE = 1e-2f;
+    private static final int TEST_DEFAULT_AMPLITUDE = 255;
+    private static final float TEST_DEFAULT_SCALE_LEVEL_GAIN = 1.4f;
 
     @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
     @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
-    @Rule
-    public final CheckFlagsRule mCheckFlagsRule =
-            DeviceFlagsValueProvider.createCheckFlagsRule();
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     @Mock private PowerManagerInternal mPowerManagerInternalMock;
     @Mock private PackageManagerInternal mPackageManagerInternalMock;
@@ -96,6 +96,10 @@
         when(mContextSpy.getContentResolver()).thenReturn(contentResolver);
         when(mPackageManagerInternalMock.getSystemUiServiceComponent())
                 .thenReturn(new ComponentName("", ""));
+        when(mVibrationConfigMock.getDefaultVibrationAmplitude())
+                .thenReturn(TEST_DEFAULT_AMPLITUDE);
+        when(mVibrationConfigMock.getDefaultVibrationScaleLevelGain())
+                .thenReturn(TEST_DEFAULT_SCALE_LEVEL_GAIN);
 
         LocalServices.removeServiceForTest(PackageManagerInternal.class);
         LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternalMock);
@@ -107,7 +111,7 @@
 
         mVibrationSettings = new VibrationSettings(
                 mContextSpy, new Handler(mTestLooper.getLooper()), mVibrationConfigMock);
-        mVibrationScaler = new VibrationScaler(mContextSpy, mVibrationSettings);
+        mVibrationScaler = new VibrationScaler(mVibrationConfigMock, mVibrationSettings);
 
         mVibrationSettings.onSystemReady();
     }
@@ -147,33 +151,76 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
-    public void testAdaptiveHapticsScale_withAdaptiveHapticsAvailable() {
-        setDefaultIntensity(USAGE_TOUCH, Vibrator.VIBRATION_INTENSITY_LOW);
-        setDefaultIntensity(USAGE_RINGTONE, Vibrator.VIBRATION_INTENSITY_LOW);
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_HIGH);
-        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_HIGH);
+    @DisableFlags(Flags.FLAG_HAPTICS_SCALE_V2_ENABLED)
+    public void testGetScaleFactor_withLegacyScaling() {
+        // Default scale gain will be ignored.
+        when(mVibrationConfigMock.getDefaultVibrationScaleLevelGain()).thenReturn(1.4f);
+        mVibrationScaler = new VibrationScaler(mVibrationConfigMock, mVibrationSettings);
 
+        setDefaultIntensity(USAGE_TOUCH, Vibrator.VIBRATION_INTENSITY_LOW);
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_HIGH);
+        assertEquals(1.4f, mVibrationScaler.getScaleFactor(USAGE_TOUCH), TOLERANCE); // VERY_HIGH
+
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_MEDIUM);
+        assertEquals(1.2f, mVibrationScaler.getScaleFactor(USAGE_TOUCH), TOLERANCE); // HIGH
+
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_LOW);
+        assertEquals(1f, mVibrationScaler.getScaleFactor(USAGE_TOUCH), TOLERANCE); // NONE
+
+        setDefaultIntensity(USAGE_TOUCH, VIBRATION_INTENSITY_MEDIUM);
+        assertEquals(0.8f, mVibrationScaler.getScaleFactor(USAGE_TOUCH), TOLERANCE); // LOW
+
+        setDefaultIntensity(USAGE_TOUCH, VIBRATION_INTENSITY_HIGH);
+        assertEquals(0.6f, mVibrationScaler.getScaleFactor(USAGE_TOUCH), TOLERANCE); // VERY_LOW
+
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
+        // Vibration setting being bypassed will use default setting and not scale.
+        assertEquals(1f, mVibrationScaler.getScaleFactor(USAGE_TOUCH), TOLERANCE); // NONE
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_HAPTICS_SCALE_V2_ENABLED)
+    public void testGetScaleFactor_withScalingV2() {
+        // Test scale factors for a default gain of 1.4
+        when(mVibrationConfigMock.getDefaultVibrationScaleLevelGain()).thenReturn(1.4f);
+        mVibrationScaler = new VibrationScaler(mVibrationConfigMock, mVibrationSettings);
+
+        setDefaultIntensity(USAGE_TOUCH, Vibrator.VIBRATION_INTENSITY_LOW);
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_HIGH);
+        assertEquals(1.95f, mVibrationScaler.getScaleFactor(USAGE_TOUCH), TOLERANCE); // VERY_HIGH
+
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_MEDIUM);
+        assertEquals(1.4f, mVibrationScaler.getScaleFactor(USAGE_TOUCH), TOLERANCE); // HIGH
+
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_LOW);
+        assertEquals(1f, mVibrationScaler.getScaleFactor(USAGE_TOUCH), TOLERANCE); // NONE
+
+        setDefaultIntensity(USAGE_TOUCH, VIBRATION_INTENSITY_MEDIUM);
+        assertEquals(0.71f, mVibrationScaler.getScaleFactor(USAGE_TOUCH), TOLERANCE); // LOW
+
+        setDefaultIntensity(USAGE_TOUCH, VIBRATION_INTENSITY_HIGH);
+        assertEquals(0.51f, mVibrationScaler.getScaleFactor(USAGE_TOUCH), TOLERANCE); // VERY_LOW
+
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
+        // Vibration setting being bypassed will use default setting and not scale.
+        assertEquals(1f, mVibrationScaler.getScaleFactor(USAGE_TOUCH), TOLERANCE); // NONE
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+    public void testAdaptiveHapticsScale_withAdaptiveHapticsAvailable() {
         mVibrationScaler.updateAdaptiveHapticsScale(USAGE_TOUCH, 0.5f);
         mVibrationScaler.updateAdaptiveHapticsScale(USAGE_RINGTONE, 0.2f);
 
         assertEquals(0.5f, mVibrationScaler.getAdaptiveHapticsScale(USAGE_TOUCH));
         assertEquals(0.2f, mVibrationScaler.getAdaptiveHapticsScale(USAGE_RINGTONE));
         assertEquals(1f, mVibrationScaler.getAdaptiveHapticsScale(USAGE_NOTIFICATION));
-
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
-        // Vibration setting being bypassed will apply adaptive haptics scales.
         assertEquals(0.2f, mVibrationScaler.getAdaptiveHapticsScale(USAGE_RINGTONE));
     }
 
     @Test
-    @RequiresFlagsDisabled(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+    @DisableFlags(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
     public void testAdaptiveHapticsScale_flagDisabled_adaptiveHapticScaleAlwaysNone() {
-        setDefaultIntensity(USAGE_TOUCH, Vibrator.VIBRATION_INTENSITY_LOW);
-        setDefaultIntensity(USAGE_RINGTONE, Vibrator.VIBRATION_INTENSITY_LOW);
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_HIGH);
-        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_HIGH);
-
         mVibrationScaler.updateAdaptiveHapticsScale(USAGE_TOUCH, 0.5f);
         mVibrationScaler.updateAdaptiveHapticsScale(USAGE_RINGTONE, 0.2f);
 
@@ -233,7 +280,7 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
     public void scale_withVendorEffect_setsEffectStrengthBasedOnSettings() {
         setDefaultIntensity(USAGE_NOTIFICATION, VIBRATION_INTENSITY_LOW);
         setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_HIGH);
@@ -269,13 +316,13 @@
         StepSegment resolved = getFirstSegment(mVibrationScaler.scale(
                 VibrationEffect.createOneShot(10, VibrationEffect.DEFAULT_AMPLITUDE),
                 USAGE_RINGTONE));
-        assertTrue(resolved.getAmplitude() > 0);
+        assertEquals(TEST_DEFAULT_AMPLITUDE / 255f, resolved.getAmplitude(), TOLERANCE);
 
         resolved = getFirstSegment(mVibrationScaler.scale(
                 VibrationEffect.createWaveform(new long[]{10},
                         new int[]{VibrationEffect.DEFAULT_AMPLITUDE}, -1),
                 USAGE_RINGTONE));
-        assertTrue(resolved.getAmplitude() > 0);
+        assertEquals(TEST_DEFAULT_AMPLITUDE / 255f, resolved.getAmplitude(), TOLERANCE);
     }
 
     @Test
@@ -330,7 +377,7 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+    @EnableFlags(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
     public void scale_withAdaptiveHaptics_scalesVibrationsCorrectly() {
         setDefaultIntensity(USAGE_RINGTONE, VIBRATION_INTENSITY_HIGH);
         setDefaultIntensity(USAGE_NOTIFICATION, VIBRATION_INTENSITY_HIGH);
@@ -351,7 +398,7 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+    @EnableFlags(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
     public void scale_clearAdaptiveHapticsScales_clearsAllCachedScales() {
         setDefaultIntensity(USAGE_RINGTONE, VIBRATION_INTENSITY_HIGH);
         setDefaultIntensity(USAGE_NOTIFICATION, VIBRATION_INTENSITY_HIGH);
@@ -373,7 +420,7 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+    @EnableFlags(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
     public void scale_removeAdaptiveHapticsScale_removesCachedScale() {
         setDefaultIntensity(USAGE_RINGTONE, VIBRATION_INTENSITY_HIGH);
         setDefaultIntensity(USAGE_NOTIFICATION, VIBRATION_INTENSITY_HIGH);
@@ -395,7 +442,7 @@
     }
 
     @Test
-    @RequiresFlagsEnabled({
+    @EnableFlags({
             android.os.vibrator.Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED,
             android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS,
     })
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 8d4a6aa..6f06050 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
@@ -70,9 +70,7 @@
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.os.test.TestLooper;
-import android.os.vibrator.Flags;
 import android.os.vibrator.VibrationConfig;
-import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.provider.Settings;
@@ -121,6 +119,7 @@
             USAGE_PHYSICAL_EMULATION,
             USAGE_RINGTONE,
             USAGE_TOUCH,
+            USAGE_IME_FEEDBACK
     };
 
     @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
@@ -527,7 +526,7 @@
         setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
 
         for (int usage : ALL_USAGES) {
-            if (usage == USAGE_TOUCH) {
+            if (usage == USAGE_TOUCH || usage == USAGE_IME_FEEDBACK) {
                 assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_SETTINGS);
             } else {
                 assertVibrationNotIgnoredForUsage(usage);
@@ -602,16 +601,15 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED)
     public void shouldIgnoreVibration_withKeyboardSettingsOff_shouldIgnoreKeyboardVibration() {
+        setKeyboardVibrationSettingsSupported(true);
         setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_MEDIUM);
         setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 0 /* OFF*/);
-        setKeyboardVibrationSettingsSupported(true);
 
         // Keyboard touch ignored.
         assertVibrationIgnoredForAttributes(
                 new VibrationAttributes.Builder()
-                        .setUsage(USAGE_TOUCH)
+                        .setUsage(USAGE_IME_FEEDBACK)
                         .setCategory(VibrationAttributes.CATEGORY_KEYBOARD)
                         .build(),
                 Vibration.Status.IGNORED_FOR_SETTINGS);
@@ -620,18 +618,17 @@
         assertVibrationNotIgnoredForUsage(USAGE_TOUCH);
         assertVibrationNotIgnoredForAttributes(
                 new VibrationAttributes.Builder()
-                        .setUsage(USAGE_TOUCH)
+                        .setUsage(USAGE_IME_FEEDBACK)
                         .setCategory(VibrationAttributes.CATEGORY_KEYBOARD)
                         .setFlags(VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)
                         .build());
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED)
     public void shouldIgnoreVibration_withKeyboardSettingsOn_shouldNotIgnoreKeyboardVibration() {
+        setKeyboardVibrationSettingsSupported(true);
         setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
         setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 1 /* ON */);
-        setKeyboardVibrationSettingsSupported(true);
 
         // General touch ignored.
         assertVibrationIgnoredForUsage(USAGE_TOUCH, Vibration.Status.IGNORED_FOR_SETTINGS);
@@ -639,17 +636,16 @@
         // Keyboard touch not ignored.
         assertVibrationNotIgnoredForAttributes(
                 new VibrationAttributes.Builder()
-                        .setUsage(USAGE_TOUCH)
+                        .setUsage(USAGE_IME_FEEDBACK)
                         .setCategory(VibrationAttributes.CATEGORY_KEYBOARD)
                         .build());
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED)
-    public void shouldIgnoreVibration_notSupportKeyboardVibration_ignoresKeyboardTouchVibration() {
+    public void shouldIgnoreVibration_notSupportKeyboardVibration_followsTouchFeedbackSettings() {
+        setKeyboardVibrationSettingsSupported(false);
         setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
         setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 1 /* ON */);
-        setKeyboardVibrationSettingsSupported(false);
 
         // General touch ignored.
         assertVibrationIgnoredForUsage(USAGE_TOUCH, Vibration.Status.IGNORED_FOR_SETTINGS);
@@ -657,7 +653,7 @@
         // Keyboard touch ignored.
         assertVibrationIgnoredForAttributes(
                 new VibrationAttributes.Builder()
-                        .setUsage(USAGE_TOUCH)
+                        .setUsage(USAGE_IME_FEEDBACK)
                         .setCategory(VibrationAttributes.CATEGORY_KEYBOARD)
                         .build(),
                 Vibration.Status.IGNORED_FOR_SETTINGS);
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 3bd56de..0fbdce4 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -102,6 +102,8 @@
     private static final String PACKAGE_NAME = "package";
     private static final VibrationAttributes ATTRS = new VibrationAttributes.Builder().build();
     private static final int TEST_RAMP_STEP_DURATION = 5;
+    private static final int TEST_DEFAULT_AMPLITUDE = 255;
+    private static final float TEST_DEFAULT_SCALE_LEVEL_GAIN = 1.4f;
 
     @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
     @Rule
@@ -133,6 +135,10 @@
         when(mVibrationConfigMock.getDefaultVibrationIntensity(anyInt()))
                 .thenReturn(Vibrator.VIBRATION_INTENSITY_MEDIUM);
         when(mVibrationConfigMock.getRampStepDurationMs()).thenReturn(TEST_RAMP_STEP_DURATION);
+        when(mVibrationConfigMock.getDefaultVibrationAmplitude())
+                .thenReturn(TEST_DEFAULT_AMPLITUDE);
+        when(mVibrationConfigMock.getDefaultVibrationScaleLevelGain())
+                .thenReturn(TEST_DEFAULT_SCALE_LEVEL_GAIN);
         when(mPackageManagerInternalMock.getSystemUiServiceComponent())
                 .thenReturn(new ComponentName("", ""));
         doAnswer(answer -> {
@@ -146,7 +152,7 @@
         Context context = InstrumentationRegistry.getContext();
         mVibrationSettings = new VibrationSettings(context, new Handler(mTestLooper.getLooper()),
                 mVibrationConfigMock);
-        mVibrationScaler = new VibrationScaler(context, mVibrationSettings);
+        mVibrationScaler = new VibrationScaler(mVibrationConfigMock, mVibrationSettings);
 
         mockVibrators(VIBRATOR_ID);
 
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
index c496bbb..79e272b 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
@@ -37,6 +37,7 @@
 import static org.mockito.Mockito.when;
 
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.pm.PackageManagerInternal;
 import android.frameworks.vibrator.ScaleParam;
 import android.frameworks.vibrator.VibrationParam;
@@ -46,6 +47,7 @@
 import android.os.Process;
 import android.os.test.TestLooper;
 import android.os.vibrator.Flags;
+import android.os.vibrator.VibrationConfig;
 import android.platform.test.annotations.RequiresFlagsDisabled;
 import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
@@ -97,8 +99,9 @@
         LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternalMock);
 
         TestLooper testLooper = new TestLooper();
-        mVibrationSettings = new VibrationSettings(
-                ApplicationProvider.getApplicationContext(), new Handler(testLooper.getLooper()));
+        Context context = ApplicationProvider.getApplicationContext();
+        mVibrationSettings = new VibrationSettings(context, new Handler(testLooper.getLooper()),
+                new VibrationConfig(context.getResources()));
 
         mFakeVibratorController = new FakeVibratorController(mTestLooper.getLooper());
         mVibratorControlService = new VibratorControlService(
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 e411a17..f009229 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -2743,7 +2743,7 @@
     }
 
     private HalVibration performHapticFeedbackAndWaitUntilFinished(VibratorManagerService service,
-                int constant, boolean always) throws InterruptedException {
+            int constant, boolean always) throws InterruptedException {
         HalVibration vib = service.performHapticFeedbackInternal(UID, Context.DEVICE_ID_DEFAULT,
                 PACKAGE_NAME, constant, "some reason", service,
                 always ? HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING : 0 /* flags */,
diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/FakeXmlResourceParser.java b/services/tests/vibrator/utils/com/android/server/vibrator/FakeXmlResourceParser.java
new file mode 100644
index 0000000..ab7d43c
--- /dev/null
+++ b/services/tests/vibrator/utils/com/android/server/vibrator/FakeXmlResourceParser.java
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.content.res.XmlResourceParser;
+import android.util.Xml;
+
+import com.android.modules.utils.TypedXmlPullParser;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+
+/**
+ * Wrapper to use TypedXmlPullParser as XmlResourceParser for Resources.getXml(). This is borrowed
+ * from {@code ZenModeHelperTest}.
+ */
+public final class FakeXmlResourceParser implements XmlResourceParser {
+    private final TypedXmlPullParser mParser;
+
+    public FakeXmlResourceParser(TypedXmlPullParser parser) {
+        this.mParser = parser;
+    }
+
+    /** Create a {@link FakeXmlResourceParser} given a xml {@link String}. */
+    public static XmlResourceParser fromXml(String xml) throws XmlPullParserException {
+        TypedXmlPullParser parser = Xml.newFastPullParser();
+        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())), null);
+        return new FakeXmlResourceParser(parser);
+    }
+
+    @Override
+    public int getEventType() throws XmlPullParserException {
+        return mParser.getEventType();
+    }
+
+    @Override
+    public void setFeature(String name, boolean state) throws XmlPullParserException {
+        mParser.setFeature(name, state);
+    }
+
+    @Override
+    public boolean getFeature(String name) {
+        return false;
+    }
+
+    @Override
+    public void setProperty(String name, Object value) throws XmlPullParserException {
+        mParser.setProperty(name, value);
+    }
+
+    @Override
+    public Object getProperty(String name) {
+        return mParser.getProperty(name);
+    }
+
+    @Override
+    public void setInput(Reader in) throws XmlPullParserException {
+        mParser.setInput(in);
+    }
+
+    @Override
+    public void setInput(InputStream inputStream, String inputEncoding)
+            throws XmlPullParserException {
+        mParser.setInput(inputStream, inputEncoding);
+    }
+
+    @Override
+    public String getInputEncoding() {
+        return mParser.getInputEncoding();
+    }
+
+    @Override
+    public void defineEntityReplacementText(String entityName, String replacementText)
+            throws XmlPullParserException {
+        mParser.defineEntityReplacementText(entityName, replacementText);
+    }
+
+    @Override
+    public int getNamespaceCount(int depth) throws XmlPullParserException {
+        return mParser.getNamespaceCount(depth);
+    }
+
+    @Override
+    public String getNamespacePrefix(int pos) throws XmlPullParserException {
+        return mParser.getNamespacePrefix(pos);
+    }
+
+    @Override
+    public String getNamespaceUri(int pos) throws XmlPullParserException {
+        return mParser.getNamespaceUri(pos);
+    }
+
+    @Override
+    public String getNamespace(String prefix) {
+        return mParser.getNamespace(prefix);
+    }
+
+    @Override
+    public int getDepth() {
+        return mParser.getDepth();
+    }
+
+    @Override
+    public String getPositionDescription() {
+        return mParser.getPositionDescription();
+    }
+
+    @Override
+    public int getLineNumber() {
+        return mParser.getLineNumber();
+    }
+
+    @Override
+    public int getColumnNumber() {
+        return mParser.getColumnNumber();
+    }
+
+    @Override
+    public boolean isWhitespace() throws XmlPullParserException {
+        return mParser.isWhitespace();
+    }
+
+    @Override
+    public String getText() {
+        return mParser.getText();
+    }
+
+    @Override
+    public char[] getTextCharacters(int[] holderForStartAndLength) {
+        return mParser.getTextCharacters(holderForStartAndLength);
+    }
+
+    @Override
+    public String getNamespace() {
+        return mParser.getNamespace();
+    }
+
+    @Override
+    public String getName() {
+        return mParser.getName();
+    }
+
+    @Override
+    public String getPrefix() {
+        return mParser.getPrefix();
+    }
+
+    @Override
+    public boolean isEmptyElementTag() throws XmlPullParserException {
+        return false;
+    }
+
+    @Override
+    public int getAttributeCount() {
+        return mParser.getAttributeCount();
+    }
+
+    @Override
+    public int next() throws IOException, XmlPullParserException {
+        return mParser.next();
+    }
+
+    @Override
+    public int nextToken() throws XmlPullParserException, IOException {
+        return mParser.next();
+    }
+
+    @Override
+    public void require(int type, String namespace, String name)
+            throws XmlPullParserException, IOException {
+        mParser.require(type, namespace, name);
+    }
+
+    @Override
+    public String nextText() throws XmlPullParserException, IOException {
+        return mParser.nextText();
+    }
+
+    @Override
+    public String getAttributeNamespace(int index) {
+        return "";
+    }
+
+    @Override
+    public String getAttributeName(int index) {
+        return mParser.getAttributeName(index);
+    }
+
+    @Override
+    public String getAttributePrefix(int index) {
+        return mParser.getAttributePrefix(index);
+    }
+
+    @Override
+    public String getAttributeType(int index) {
+        return mParser.getAttributeType(index);
+    }
+
+    @Override
+    public boolean isAttributeDefault(int index) {
+        return mParser.isAttributeDefault(index);
+    }
+
+    @Override
+    public String getAttributeValue(int index) {
+        return mParser.getAttributeValue(index);
+    }
+
+    @Override
+    public String getAttributeValue(String namespace, String name) {
+        return mParser.getAttributeValue(namespace, name);
+    }
+
+    @Override
+    public int getAttributeNameResource(int index) {
+        return 0;
+    }
+
+    @Override
+    public int getAttributeListValue(String namespace, String attribute, String[] options,
+            int defaultValue) {
+        return 0;
+    }
+
+    @Override
+    public boolean getAttributeBooleanValue(String namespace, String attribute,
+            boolean defaultValue) {
+        return false;
+    }
+
+    @Override
+    public int getAttributeResourceValue(String namespace, String attribute, int defaultValue) {
+        return 0;
+    }
+
+    @Override
+    public int getAttributeIntValue(String namespace, String attribute, int defaultValue) {
+        return 0;
+    }
+
+    @Override
+    public int getAttributeUnsignedIntValue(String namespace, String attribute,
+            int defaultValue) {
+        return 0;
+    }
+
+    @Override
+    public float getAttributeFloatValue(String namespace, String attribute,
+            float defaultValue) {
+        return 0;
+    }
+
+    @Override
+    public int getAttributeListValue(int index, String[] options, int defaultValue) {
+        return 0;
+    }
+
+    @Override
+    public boolean getAttributeBooleanValue(int index, boolean defaultValue) {
+        return false;
+    }
+
+    @Override
+    public int getAttributeResourceValue(int index, int defaultValue) {
+        return 0;
+    }
+
+    @Override
+    public int getAttributeIntValue(int index, int defaultValue) {
+        return 0;
+    }
+
+    @Override
+    public int getAttributeUnsignedIntValue(int index, int defaultValue) {
+        return 0;
+    }
+
+    @Override
+    public float getAttributeFloatValue(int index, float defaultValue) {
+        return 0;
+    }
+
+    @Override
+    public String getIdAttribute() {
+        return null;
+    }
+
+    @Override
+    public String getClassAttribute() {
+        return null;
+    }
+
+    @Override
+    public int getIdAttributeResourceValue(int defaultValue) {
+        return 0;
+    }
+
+    @Override
+    public int getStyleAttribute() {
+        return 0;
+    }
+
+    @Override
+    public void close() {
+    }
+
+    @Override
+    public int nextTag() throws IOException, XmlPullParserException {
+        return mParser.nextTag();
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/policy/KeyboardSystemShortcutTests.java b/services/tests/wmtests/src/com/android/server/policy/KeyboardSystemShortcutTests.java
new file mode 100644
index 0000000..e26f3e0
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/policy/KeyboardSystemShortcutTests.java
@@ -0,0 +1,449 @@
+/*
+ * Copyright 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.policy;
+
+import static com.android.server.policy.PhoneWindowManager.DOUBLE_TAP_HOME_RECENT_SYSTEM_UI;
+import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_HOME_ALL_APPS;
+import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_HOME_ASSIST;
+import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_HOME_NOTIFICATION_PANEL;
+import static com.android.server.policy.PhoneWindowManager.SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL;
+
+import android.hardware.input.KeyboardSystemShortcut;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.view.KeyEvent;
+
+import androidx.test.filters.MediumTest;
+
+import com.android.internal.annotations.Keep;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@MediumTest
+@RunWith(JUnitParamsRunner.class)
+public class KeyboardSystemShortcutTests extends ShortcutKeyTestBase {
+
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
+
+    private static final int META_KEY = KeyEvent.KEYCODE_META_LEFT;
+    private static final int META_ON = MODIFIER.get(KeyEvent.KEYCODE_META_LEFT);
+    private static final int ALT_KEY = KeyEvent.KEYCODE_ALT_LEFT;
+    private static final int ALT_ON = MODIFIER.get(KeyEvent.KEYCODE_ALT_LEFT);
+    private static final int CTRL_KEY = KeyEvent.KEYCODE_CTRL_LEFT;
+    private static final int CTRL_ON = MODIFIER.get(KeyEvent.KEYCODE_CTRL_LEFT);
+    private static final int SHIFT_KEY = KeyEvent.KEYCODE_SHIFT_LEFT;
+    private static final int SHIFT_ON = MODIFIER.get(KeyEvent.KEYCODE_SHIFT_LEFT);
+
+    @Keep
+    private static Object[][] shortcutTestArguments() {
+        // testName, testKeys, expectedSystemShortcut, expectedKey, expectedModifierState
+        return new Object[][]{
+                {"Meta + H -> Open Home", new int[]{META_KEY, KeyEvent.KEYCODE_H},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_HOME, KeyEvent.KEYCODE_H, META_ON},
+                {"Meta + Enter -> Open Home", new int[]{META_KEY, KeyEvent.KEYCODE_ENTER},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_HOME, KeyEvent.KEYCODE_ENTER,
+                        META_ON},
+                {"HOME key -> Open Home", new int[]{KeyEvent.KEYCODE_HOME},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_HOME,
+                        KeyEvent.KEYCODE_HOME, 0},
+                {"RECENT_APPS key -> Open Overview", new int[]{KeyEvent.KEYCODE_RECENT_APPS},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_RECENT_APPS,
+                        KeyEvent.KEYCODE_RECENT_APPS, 0},
+                {"Meta + Tab -> Open Overview", new int[]{META_KEY, KeyEvent.KEYCODE_TAB},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_RECENT_APPS, KeyEvent.KEYCODE_TAB,
+                        META_ON},
+                {"Alt + Tab -> Open Overview", new int[]{ALT_KEY, KeyEvent.KEYCODE_TAB},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_RECENT_APPS, KeyEvent.KEYCODE_TAB,
+                        ALT_ON},
+                {"BACK key -> Go back", new int[]{KeyEvent.KEYCODE_BACK},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_BACK,
+                        KeyEvent.KEYCODE_BACK, 0},
+                {"Meta + Escape -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_ESCAPE},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_BACK, KeyEvent.KEYCODE_ESCAPE,
+                        META_ON},
+                {"Meta + Left arrow -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_DPAD_LEFT},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_BACK, KeyEvent.KEYCODE_DPAD_LEFT,
+                        META_ON},
+                {"Meta + Del -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_DEL},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_BACK, KeyEvent.KEYCODE_DEL, META_ON},
+                {"APP_SWITCH key -> Open App switcher", new int[]{KeyEvent.KEYCODE_APP_SWITCH},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_APP_SWITCH,
+                        KeyEvent.KEYCODE_APP_SWITCH, 0},
+                {"ASSIST key -> Launch assistant", new int[]{KeyEvent.KEYCODE_ASSIST},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT,
+                        KeyEvent.KEYCODE_ASSIST, 0},
+                {"Meta + A -> Launch assistant", new int[]{META_KEY, KeyEvent.KEYCODE_A},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT, KeyEvent.KEYCODE_A,
+                        META_ON},
+                {"VOICE_ASSIST key -> Launch Voice Assistant",
+                        new int[]{KeyEvent.KEYCODE_VOICE_ASSIST},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT,
+                        KeyEvent.KEYCODE_VOICE_ASSIST, 0},
+                {"Meta + I -> Launch System Settings", new int[]{META_KEY, KeyEvent.KEYCODE_I},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS,
+                        KeyEvent.KEYCODE_I, META_ON},
+                {"Meta + N -> Toggle Notification panel", new int[]{META_KEY, KeyEvent.KEYCODE_N},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL,
+                        KeyEvent.KEYCODE_N, META_ON},
+                {"NOTIFICATION key -> Toggle Notification Panel",
+                        new int[]{KeyEvent.KEYCODE_NOTIFICATION},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL,
+                        KeyEvent.KEYCODE_NOTIFICATION,
+                        0},
+                {"Meta + Ctrl + S -> Take Screenshot",
+                        new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_S},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TAKE_SCREENSHOT, KeyEvent.KEYCODE_S,
+                        META_ON | CTRL_ON},
+                {"Meta + / -> Open Shortcut Helper", new int[]{META_KEY, KeyEvent.KEYCODE_SLASH},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER,
+                        KeyEvent.KEYCODE_SLASH, META_ON},
+                {"BRIGHTNESS_UP key -> Increase Brightness",
+                        new int[]{KeyEvent.KEYCODE_BRIGHTNESS_UP},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_BRIGHTNESS_UP,
+                        KeyEvent.KEYCODE_BRIGHTNESS_UP, 0},
+                {"BRIGHTNESS_DOWN key -> Decrease Brightness",
+                        new int[]{KeyEvent.KEYCODE_BRIGHTNESS_DOWN},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_BRIGHTNESS_DOWN,
+                        KeyEvent.KEYCODE_BRIGHTNESS_DOWN, 0},
+                {"KEYBOARD_BACKLIGHT_UP key -> Increase Keyboard Backlight",
+                        new int[]{KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP,
+                        KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP, 0},
+                {"KEYBOARD_BACKLIGHT_DOWN key -> Decrease Keyboard Backlight",
+                        new int[]{KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN,
+                        KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN, 0},
+                {"KEYBOARD_BACKLIGHT_TOGGLE key -> Toggle Keyboard Backlight",
+                        new int[]{KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE,
+                        KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE, 0},
+                {"VOLUME_UP key -> Increase Volume", new int[]{KeyEvent.KEYCODE_VOLUME_UP},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_VOLUME_UP,
+                        KeyEvent.KEYCODE_VOLUME_UP, 0},
+                {"VOLUME_DOWN key -> Decrease Volume", new int[]{KeyEvent.KEYCODE_VOLUME_DOWN},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_VOLUME_DOWN,
+                        KeyEvent.KEYCODE_VOLUME_DOWN, 0},
+                {"VOLUME_MUTE key -> Mute Volume", new int[]{KeyEvent.KEYCODE_VOLUME_MUTE},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_VOLUME_MUTE,
+                        KeyEvent.KEYCODE_VOLUME_MUTE, 0},
+                {"ALL_APPS key -> Open App Drawer in Accessibility mode",
+                        new int[]{KeyEvent.KEYCODE_ALL_APPS},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS,
+                        KeyEvent.KEYCODE_ALL_APPS, 0},
+                {"SEARCH key -> Launch Search Activity", new int[]{KeyEvent.KEYCODE_SEARCH},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_SEARCH,
+                        KeyEvent.KEYCODE_SEARCH, 0},
+                {"LANGUAGE_SWITCH key -> Switch Keyboard Language",
+                        new int[]{KeyEvent.KEYCODE_LANGUAGE_SWITCH},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LANGUAGE_SWITCH,
+                        KeyEvent.KEYCODE_LANGUAGE_SWITCH, 0},
+                {"META key -> Open App Drawer in Accessibility mode", new int[]{META_KEY},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS, META_KEY,
+                        META_ON},
+                {"Meta + Alt -> Toggle CapsLock", new int[]{META_KEY, ALT_KEY},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK, ALT_KEY,
+                        META_ON | ALT_ON},
+                {"Alt + Meta -> Toggle CapsLock", new int[]{ALT_KEY, META_KEY},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK, META_KEY,
+                        META_ON | ALT_ON},
+                {"CAPS_LOCK key -> Toggle CapsLock", new int[]{KeyEvent.KEYCODE_CAPS_LOCK},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK,
+                        KeyEvent.KEYCODE_CAPS_LOCK, 0},
+                {"MUTE key -> Mute System Microphone", new int[]{KeyEvent.KEYCODE_MUTE},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SYSTEM_MUTE, KeyEvent.KEYCODE_MUTE,
+                        0},
+                {"Meta + Ctrl + DPAD_UP -> Split screen navigation",
+                        new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_UP},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION,
+                        KeyEvent.KEYCODE_DPAD_UP,
+                        META_ON | CTRL_ON},
+                {"Meta + Ctrl + DPAD_LEFT -> Split screen navigation",
+                        new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_LEFT},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION,
+                        KeyEvent.KEYCODE_DPAD_LEFT,
+                        META_ON | CTRL_ON},
+                {"Meta + Ctrl + DPAD_RIGHT -> Split screen navigation",
+                        new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_RIGHT},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION,
+                        KeyEvent.KEYCODE_DPAD_RIGHT,
+                        META_ON | CTRL_ON},
+                {"Meta + L -> Lock Homescreen", new int[]{META_KEY, KeyEvent.KEYCODE_L},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LOCK_SCREEN, KeyEvent.KEYCODE_L,
+                        META_ON},
+                {"Meta + Ctrl + N -> Open Notes", new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_N},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_OPEN_NOTES, KeyEvent.KEYCODE_N,
+                        META_ON | CTRL_ON},
+                {"POWER key -> Toggle Power", new int[]{KeyEvent.KEYCODE_POWER},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_POWER, KeyEvent.KEYCODE_POWER,
+                        0},
+                {"TV_POWER key -> Toggle Power", new int[]{KeyEvent.KEYCODE_TV_POWER},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_POWER,
+                        KeyEvent.KEYCODE_TV_POWER, 0},
+                {"SYSTEM_NAVIGATION_DOWN key -> System Navigation",
+                        new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SYSTEM_NAVIGATION,
+                        KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN,
+                        0},
+                {"SYSTEM_NAVIGATION_UP key -> System Navigation",
+                        new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SYSTEM_NAVIGATION,
+                        KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP,
+                        0},
+                {"SYSTEM_NAVIGATION_LEFT key -> System Navigation",
+                        new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SYSTEM_NAVIGATION,
+                        KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT,
+                        0},
+                {"SYSTEM_NAVIGATION_RIGHT key -> System Navigation",
+                        new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SYSTEM_NAVIGATION,
+                        KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT, 0},
+                {"SLEEP key -> System Sleep", new int[]{KeyEvent.KEYCODE_SLEEP},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SLEEP, KeyEvent.KEYCODE_SLEEP, 0},
+                {"SOFT_SLEEP key -> System Sleep", new int[]{KeyEvent.KEYCODE_SOFT_SLEEP},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SLEEP, KeyEvent.KEYCODE_SOFT_SLEEP,
+                        0},
+                {"WAKEUP key -> System Wakeup", new int[]{KeyEvent.KEYCODE_WAKEUP},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_WAKEUP, KeyEvent.KEYCODE_WAKEUP, 0},
+                {"MEDIA_PLAY key -> Media Control", new int[]{KeyEvent.KEYCODE_MEDIA_PLAY},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_MEDIA_KEY,
+                        KeyEvent.KEYCODE_MEDIA_PLAY, 0},
+                {"MEDIA_PAUSE key -> Media Control", new int[]{KeyEvent.KEYCODE_MEDIA_PAUSE},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_MEDIA_KEY,
+                        KeyEvent.KEYCODE_MEDIA_PAUSE, 0},
+                {"MEDIA_PLAY_PAUSE key -> Media Control",
+                        new int[]{KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_MEDIA_KEY,
+                        KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, 0},
+                {"Meta + B -> Launch Default Browser", new int[]{META_KEY, KeyEvent.KEYCODE_B},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER,
+                        KeyEvent.KEYCODE_B, META_ON},
+                {"EXPLORER key -> Launch Default Browser", new int[]{KeyEvent.KEYCODE_EXPLORER},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER,
+                        KeyEvent.KEYCODE_EXPLORER, 0},
+                {"Meta + C -> Launch Default Contacts", new int[]{META_KEY, KeyEvent.KEYCODE_C},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS,
+                        KeyEvent.KEYCODE_C, META_ON},
+                {"CONTACTS key -> Launch Default Contacts", new int[]{KeyEvent.KEYCODE_CONTACTS},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS,
+                        KeyEvent.KEYCODE_CONTACTS, 0},
+                {"Meta + E -> Launch Default Email", new int[]{META_KEY, KeyEvent.KEYCODE_E},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL,
+                        KeyEvent.KEYCODE_E, META_ON},
+                {"ENVELOPE key -> Launch Default Email", new int[]{KeyEvent.KEYCODE_ENVELOPE},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL,
+                        KeyEvent.KEYCODE_ENVELOPE, 0},
+                {"Meta + K -> Launch Default Calendar", new int[]{META_KEY, KeyEvent.KEYCODE_K},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR,
+                        KeyEvent.KEYCODE_K, META_ON},
+                {"CALENDAR key -> Launch Default Calendar", new int[]{KeyEvent.KEYCODE_CALENDAR},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR,
+                        KeyEvent.KEYCODE_CALENDAR, 0},
+                {"Meta + P -> Launch Default Music", new int[]{META_KEY, KeyEvent.KEYCODE_P},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC,
+                        KeyEvent.KEYCODE_P, META_ON},
+                {"MUSIC key -> Launch Default Music", new int[]{KeyEvent.KEYCODE_MUSIC},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC,
+                        KeyEvent.KEYCODE_MUSIC, 0},
+                {"Meta + U -> Launch Default Calculator", new int[]{META_KEY, KeyEvent.KEYCODE_U},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR,
+                        KeyEvent.KEYCODE_U, META_ON},
+                {"CALCULATOR key -> Launch Default Calculator",
+                        new int[]{KeyEvent.KEYCODE_CALCULATOR},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR,
+                        KeyEvent.KEYCODE_CALCULATOR, 0},
+                {"Meta + M -> Launch Default Maps", new int[]{META_KEY, KeyEvent.KEYCODE_M},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS,
+                        KeyEvent.KEYCODE_M, META_ON},
+                {"Meta + S -> Launch Default Messaging App",
+                        new int[]{META_KEY, KeyEvent.KEYCODE_S},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING,
+                        KeyEvent.KEYCODE_S, META_ON},
+                {"Meta + Ctrl + DPAD_DOWN -> Enter desktop mode",
+                        new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_DOWN},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_DESKTOP_MODE,
+                        KeyEvent.KEYCODE_DPAD_DOWN,
+                        META_ON | CTRL_ON}};
+    }
+
+    @Keep
+    private static Object[][] longPressOnHomeTestArguments() {
+        // testName, testKeys, longPressOnHomeBehavior, expectedSystemShortcut, expectedKey,
+        // expectedModifierState
+        return new Object[][]{
+                {"Long press HOME key -> Toggle Notification panel",
+                        new int[]{KeyEvent.KEYCODE_HOME}, LONG_PRESS_HOME_NOTIFICATION_PANEL,
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL,
+                        KeyEvent.KEYCODE_HOME, 0},
+                {"Long press META + ENTER -> Toggle Notification panel",
+                        new int[]{META_KEY, KeyEvent.KEYCODE_ENTER},
+                        LONG_PRESS_HOME_NOTIFICATION_PANEL,
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL,
+                        KeyEvent.KEYCODE_ENTER,
+                        META_ON},
+                {"Long press META + H -> Toggle Notification panel",
+                        new int[]{META_KEY, KeyEvent.KEYCODE_H}, LONG_PRESS_HOME_NOTIFICATION_PANEL,
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL,
+                        KeyEvent.KEYCODE_H, META_ON},
+                {"Long press HOME key -> Launch assistant",
+                        new int[]{KeyEvent.KEYCODE_HOME}, LONG_PRESS_HOME_ASSIST,
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT,
+                        KeyEvent.KEYCODE_HOME, 0},
+                {"Long press META + ENTER -> Launch assistant",
+                        new int[]{META_KEY, KeyEvent.KEYCODE_ENTER}, LONG_PRESS_HOME_ASSIST,
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT,
+                        KeyEvent.KEYCODE_ENTER, META_ON},
+                {"Long press META + H -> Launch assistant",
+                        new int[]{META_KEY, KeyEvent.KEYCODE_H}, LONG_PRESS_HOME_ASSIST,
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT, KeyEvent.KEYCODE_H,
+                        META_ON},
+                {"Long press HOME key -> Open App Drawer in Accessibility mode",
+                        new int[]{KeyEvent.KEYCODE_HOME}, LONG_PRESS_HOME_ALL_APPS,
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS,
+                        KeyEvent.KEYCODE_HOME, 0},
+                {"Long press META + ENTER -> Open App Drawer in Accessibility mode",
+                        new int[]{META_KEY, KeyEvent.KEYCODE_ENTER}, LONG_PRESS_HOME_ALL_APPS,
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS,
+                        KeyEvent.KEYCODE_ENTER, META_ON},
+                {"Long press META + H -> Open App Drawer in Accessibility mode",
+                        new int[]{META_KEY, KeyEvent.KEYCODE_H},
+                        LONG_PRESS_HOME_ALL_APPS,
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS,
+                        KeyEvent.KEYCODE_H, META_ON}};
+    }
+
+    @Keep
+    private static Object[][] doubleTapOnHomeTestArguments() {
+        // testName, testKeys, doubleTapOnHomeBehavior, expectedSystemShortcut, expectedKey,
+        // expectedModifierState
+        return new Object[][]{
+                {"Double tap HOME -> Open App switcher",
+                        new int[]{KeyEvent.KEYCODE_HOME}, DOUBLE_TAP_HOME_RECENT_SYSTEM_UI,
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_APP_SWITCH, KeyEvent.KEYCODE_HOME,
+                        0},
+                {"Double tap META + ENTER -> Open App switcher",
+                        new int[]{META_KEY, KeyEvent.KEYCODE_ENTER},
+                        DOUBLE_TAP_HOME_RECENT_SYSTEM_UI,
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_APP_SWITCH,
+                        KeyEvent.KEYCODE_ENTER, META_ON},
+                {"Double tap META + H -> Open App switcher",
+                        new int[]{META_KEY, KeyEvent.KEYCODE_H}, DOUBLE_TAP_HOME_RECENT_SYSTEM_UI,
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_APP_SWITCH, KeyEvent.KEYCODE_H,
+                        META_ON}};
+    }
+
+    @Keep
+    private static Object[][] settingsKeyTestArguments() {
+        // testName, testKeys, settingsKeyBehavior, expectedSystemShortcut, expectedKey,
+        // expectedModifierState
+        return new Object[][]{
+                {"SETTINGS key -> Toggle Notification panel", new int[]{KeyEvent.KEYCODE_SETTINGS},
+                        SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL,
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL,
+                        KeyEvent.KEYCODE_SETTINGS, 0}};
+    }
+
+    @Before
+    public void setUp() {
+        setUpPhoneWindowManager(/*supportSettingsUpdate*/ true);
+        mPhoneWindowManager.overrideLaunchHome();
+        mPhoneWindowManager.overrideSearchKeyBehavior(
+                PhoneWindowManager.SEARCH_KEY_BEHAVIOR_TARGET_ACTIVITY);
+        mPhoneWindowManager.overrideEnableBugReportTrigger(true);
+        mPhoneWindowManager.overrideStatusBarManagerInternal();
+        mPhoneWindowManager.overrideStartActivity();
+        mPhoneWindowManager.overrideSendBroadcast();
+        mPhoneWindowManager.overrideUserSetupComplete();
+        mPhoneWindowManager.setupAssistForLaunch();
+        mPhoneWindowManager.overrideTogglePanel();
+        mPhoneWindowManager.overrideInjectKeyEvent();
+    }
+
+    @Test
+    @Parameters(method = "shortcutTestArguments")
+    public void testShortcut(String testName, int[] testKeys,
+            @KeyboardSystemShortcut.SystemShortcut int expectedSystemShortcut, int expectedKey,
+            int expectedModifierState) {
+        testShortcutInternal(testName, testKeys, expectedSystemShortcut, expectedKey,
+                expectedModifierState);
+    }
+
+    @Test
+    @Parameters(method = "longPressOnHomeTestArguments")
+    public void testLongPressOnHome(String testName, int[] testKeys, int longPressOnHomeBehavior,
+            @KeyboardSystemShortcut.SystemShortcut int expectedSystemShortcut, int expectedKey,
+            int expectedModifierState) {
+        mPhoneWindowManager.overrideLongPressOnHomeBehavior(longPressOnHomeBehavior);
+        sendLongPressKeyCombination(testKeys);
+        mPhoneWindowManager.assertKeyboardShortcutTriggered(
+                new int[]{expectedKey}, expectedModifierState, expectedSystemShortcut,
+                "Failed while executing " + testName);
+    }
+
+    @Test
+    @Parameters(method = "doubleTapOnHomeTestArguments")
+    public void testDoubleTapOnHomeBehavior(String testName, int[] testKeys,
+            int doubleTapOnHomeBehavior,
+            @KeyboardSystemShortcut.SystemShortcut int expectedSystemShortcut, int expectedKey,
+            int expectedModifierState) {
+        mPhoneWindowManager.overriderDoubleTapOnHomeBehavior(doubleTapOnHomeBehavior);
+        sendKeyCombination(testKeys, 0 /* duration */);
+        sendKeyCombination(testKeys, 0 /* duration */);
+        mPhoneWindowManager.assertKeyboardShortcutTriggered(
+                new int[]{expectedKey}, expectedModifierState, expectedSystemShortcut,
+                "Failed while executing " + testName);
+    }
+
+    @Test
+    @Parameters(method = "settingsKeyTestArguments")
+    public void testSettingsKey(String testName, int[] testKeys, int settingsKeyBehavior,
+            @KeyboardSystemShortcut.SystemShortcut int expectedSystemShortcut, int expectedKey,
+            int expectedModifierState) {
+        mPhoneWindowManager.overrideSettingsKeyBehavior(settingsKeyBehavior);
+        testShortcutInternal(testName, testKeys, expectedSystemShortcut, expectedKey,
+                expectedModifierState);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT)
+    public void testBugreportShortcutPress() {
+        testShortcutInternal("Meta + Ctrl + Del -> Trigger bug report",
+                new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DEL},
+                KeyboardSystemShortcut.SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT, KeyEvent.KEYCODE_DEL,
+                META_ON | CTRL_ON);
+    }
+
+    private void testShortcutInternal(String testName, int[] testKeys,
+            @KeyboardSystemShortcut.SystemShortcut int expectedSystemShortcut, int expectedKey,
+            int expectedModifierState) {
+        sendKeyCombination(testKeys, 0 /* duration */);
+        mPhoneWindowManager.assertKeyboardShortcutTriggered(
+                new int[]{expectedKey}, expectedModifierState, expectedSystemShortcut,
+                "Failed while executing " + testName);
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
deleted file mode 100644
index aa28147..0000000
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
+++ /dev/null
@@ -1,373 +0,0 @@
-/*
- * Copyright 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.policy;
-
-import static com.android.server.policy.PhoneWindowManager.DOUBLE_TAP_HOME_RECENT_SYSTEM_UI;
-import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_HOME_ALL_APPS;
-import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_HOME_ASSIST;
-import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_HOME_NOTIFICATION_PANEL;
-import static com.android.server.policy.PhoneWindowManager.SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL;
-
-import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-import android.view.KeyEvent;
-
-import androidx.test.filters.MediumTest;
-
-import com.android.internal.annotations.Keep;
-import com.android.server.input.KeyboardMetricsCollector.KeyboardLogEvent;
-
-import junitparams.JUnitParamsRunner;
-import junitparams.Parameters;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@Presubmit
-@MediumTest
-@RunWith(JUnitParamsRunner.class)
-public class ShortcutLoggingTests extends ShortcutKeyTestBase {
-
-    @Rule
-    public final CheckFlagsRule mCheckFlagsRule =
-            DeviceFlagsValueProvider.createCheckFlagsRule();
-
-    private static final int VENDOR_ID = 0x123;
-    private static final int PRODUCT_ID = 0x456;
-    private static final int DEVICE_BUS = 0x789;
-    private static final int META_KEY = KeyEvent.KEYCODE_META_LEFT;
-    private static final int META_ON = MODIFIER.get(KeyEvent.KEYCODE_META_LEFT);
-    private static final int ALT_KEY = KeyEvent.KEYCODE_ALT_LEFT;
-    private static final int ALT_ON = MODIFIER.get(KeyEvent.KEYCODE_ALT_LEFT);
-    private static final int CTRL_KEY = KeyEvent.KEYCODE_CTRL_LEFT;
-    private static final int CTRL_ON = MODIFIER.get(KeyEvent.KEYCODE_CTRL_LEFT);
-    private static final int SHIFT_KEY = KeyEvent.KEYCODE_SHIFT_LEFT;
-    private static final int SHIFT_ON = MODIFIER.get(KeyEvent.KEYCODE_SHIFT_LEFT);
-
-    @Keep
-    private static Object[][] shortcutTestArguments() {
-        // testName, testKeys, expectedLogEvent, expectedKey, expectedModifierState
-        return new Object[][]{
-                {"Meta + H -> Open Home", new int[]{META_KEY, KeyEvent.KEYCODE_H},
-                        KeyboardLogEvent.HOME, KeyEvent.KEYCODE_H, META_ON},
-                {"Meta + Enter -> Open Home", new int[]{META_KEY, KeyEvent.KEYCODE_ENTER},
-                        KeyboardLogEvent.HOME, KeyEvent.KEYCODE_ENTER, META_ON},
-                {"HOME key -> Open Home", new int[]{KeyEvent.KEYCODE_HOME}, KeyboardLogEvent.HOME,
-                        KeyEvent.KEYCODE_HOME, 0},
-                {"RECENT_APPS key -> Open Overview", new int[]{KeyEvent.KEYCODE_RECENT_APPS},
-                        KeyboardLogEvent.RECENT_APPS, KeyEvent.KEYCODE_RECENT_APPS, 0},
-                {"Meta + Tab -> Open OVerview", new int[]{META_KEY, KeyEvent.KEYCODE_TAB},
-                        KeyboardLogEvent.RECENT_APPS, KeyEvent.KEYCODE_TAB, META_ON},
-                {"Alt + Tab -> Open Overview", new int[]{ALT_KEY, KeyEvent.KEYCODE_TAB},
-                        KeyboardLogEvent.RECENT_APPS, KeyEvent.KEYCODE_TAB, ALT_ON},
-                {"BACK key -> Go back", new int[]{KeyEvent.KEYCODE_BACK}, KeyboardLogEvent.BACK,
-                        KeyEvent.KEYCODE_BACK, 0},
-                {"Meta + Escape -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_ESCAPE},
-                        KeyboardLogEvent.BACK, KeyEvent.KEYCODE_ESCAPE, META_ON},
-                {"Meta + Left arrow -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_DPAD_LEFT},
-                        KeyboardLogEvent.BACK, KeyEvent.KEYCODE_DPAD_LEFT, META_ON},
-                {"Meta + Del -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_DEL},
-                        KeyboardLogEvent.BACK, KeyEvent.KEYCODE_DEL, META_ON},
-                {"APP_SWITCH key -> Open App switcher", new int[]{KeyEvent.KEYCODE_APP_SWITCH},
-                        KeyboardLogEvent.APP_SWITCH, KeyEvent.KEYCODE_APP_SWITCH, 0},
-                {"ASSIST key -> Launch assistant", new int[]{KeyEvent.KEYCODE_ASSIST},
-                        KeyboardLogEvent.LAUNCH_ASSISTANT, KeyEvent.KEYCODE_ASSIST, 0},
-                {"Meta + A -> Launch assistant", new int[]{META_KEY, KeyEvent.KEYCODE_A},
-                        KeyboardLogEvent.LAUNCH_ASSISTANT, KeyEvent.KEYCODE_A, META_ON},
-                {"VOICE_ASSIST key -> Launch Voice Assistant",
-                        new int[]{KeyEvent.KEYCODE_VOICE_ASSIST},
-                        KeyboardLogEvent.LAUNCH_VOICE_ASSISTANT, KeyEvent.KEYCODE_VOICE_ASSIST, 0},
-                {"Meta + I -> Launch System Settings", new int[]{META_KEY, KeyEvent.KEYCODE_I},
-                        KeyboardLogEvent.LAUNCH_SYSTEM_SETTINGS, KeyEvent.KEYCODE_I, META_ON},
-                {"Meta + N -> Toggle Notification panel", new int[]{META_KEY, KeyEvent.KEYCODE_N},
-                        KeyboardLogEvent.TOGGLE_NOTIFICATION_PANEL, KeyEvent.KEYCODE_N, META_ON},
-                {"NOTIFICATION key -> Toggle Notification Panel",
-                        new int[]{KeyEvent.KEYCODE_NOTIFICATION},
-                        KeyboardLogEvent.TOGGLE_NOTIFICATION_PANEL, KeyEvent.KEYCODE_NOTIFICATION,
-                        0},
-                {"Meta + Ctrl + S -> Take Screenshot",
-                        new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_S},
-                        KeyboardLogEvent.TAKE_SCREENSHOT, KeyEvent.KEYCODE_S, META_ON | CTRL_ON},
-                {"Meta + / -> Open Shortcut Helper", new int[]{META_KEY, KeyEvent.KEYCODE_SLASH},
-                        KeyboardLogEvent.OPEN_SHORTCUT_HELPER, KeyEvent.KEYCODE_SLASH, META_ON},
-                {"BRIGHTNESS_UP key -> Increase Brightness",
-                        new int[]{KeyEvent.KEYCODE_BRIGHTNESS_UP}, KeyboardLogEvent.BRIGHTNESS_UP,
-                        KeyEvent.KEYCODE_BRIGHTNESS_UP, 0},
-                {"BRIGHTNESS_DOWN key -> Decrease Brightness",
-                        new int[]{KeyEvent.KEYCODE_BRIGHTNESS_DOWN},
-                        KeyboardLogEvent.BRIGHTNESS_DOWN, KeyEvent.KEYCODE_BRIGHTNESS_DOWN, 0},
-                {"KEYBOARD_BACKLIGHT_UP key -> Increase Keyboard Backlight",
-                        new int[]{KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP},
-                        KeyboardLogEvent.KEYBOARD_BACKLIGHT_UP,
-                        KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP, 0},
-                {"KEYBOARD_BACKLIGHT_DOWN key -> Decrease Keyboard Backlight",
-                        new int[]{KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN},
-                        KeyboardLogEvent.KEYBOARD_BACKLIGHT_DOWN,
-                        KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN, 0},
-                {"KEYBOARD_BACKLIGHT_TOGGLE key -> Toggle Keyboard Backlight",
-                        new int[]{KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE},
-                        KeyboardLogEvent.KEYBOARD_BACKLIGHT_TOGGLE,
-                        KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE, 0},
-                {"VOLUME_UP key -> Increase Volume", new int[]{KeyEvent.KEYCODE_VOLUME_UP},
-                        KeyboardLogEvent.VOLUME_UP, KeyEvent.KEYCODE_VOLUME_UP, 0},
-                {"VOLUME_DOWN key -> Decrease Volume", new int[]{KeyEvent.KEYCODE_VOLUME_DOWN},
-                        KeyboardLogEvent.VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_DOWN, 0},
-                {"VOLUME_MUTE key -> Mute Volume", new int[]{KeyEvent.KEYCODE_VOLUME_MUTE},
-                        KeyboardLogEvent.VOLUME_MUTE, KeyEvent.KEYCODE_VOLUME_MUTE, 0},
-                {"ALL_APPS key -> Open App Drawer in Accessibility mode",
-                        new int[]{KeyEvent.KEYCODE_ALL_APPS},
-                        KeyboardLogEvent.ACCESSIBILITY_ALL_APPS, KeyEvent.KEYCODE_ALL_APPS, 0},
-                {"SEARCH key -> Launch Search Activity", new int[]{KeyEvent.KEYCODE_SEARCH},
-                        KeyboardLogEvent.LAUNCH_SEARCH, KeyEvent.KEYCODE_SEARCH, 0},
-                {"LANGUAGE_SWITCH key -> Switch Keyboard Language",
-                        new int[]{KeyEvent.KEYCODE_LANGUAGE_SWITCH},
-                        KeyboardLogEvent.LANGUAGE_SWITCH, KeyEvent.KEYCODE_LANGUAGE_SWITCH, 0},
-                {"META key -> Open App Drawer in Accessibility mode", new int[]{META_KEY},
-                        KeyboardLogEvent.ACCESSIBILITY_ALL_APPS, META_KEY, META_ON},
-                {"Meta + Alt -> Toggle CapsLock", new int[]{META_KEY, ALT_KEY},
-                        KeyboardLogEvent.TOGGLE_CAPS_LOCK, ALT_KEY, META_ON | ALT_ON},
-                {"Alt + Meta -> Toggle CapsLock", new int[]{ALT_KEY, META_KEY},
-                        KeyboardLogEvent.TOGGLE_CAPS_LOCK, META_KEY, META_ON | ALT_ON},
-                {"CAPS_LOCK key -> Toggle CapsLock", new int[]{KeyEvent.KEYCODE_CAPS_LOCK},
-                        KeyboardLogEvent.TOGGLE_CAPS_LOCK, KeyEvent.KEYCODE_CAPS_LOCK, 0},
-                {"MUTE key -> Mute System Microphone", new int[]{KeyEvent.KEYCODE_MUTE},
-                        KeyboardLogEvent.SYSTEM_MUTE, KeyEvent.KEYCODE_MUTE, 0},
-                {"Meta + Ctrl + DPAD_UP -> Split screen navigation",
-                        new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_UP},
-                        KeyboardLogEvent.MULTI_WINDOW_NAVIGATION, KeyEvent.KEYCODE_DPAD_UP,
-                        META_ON | CTRL_ON},
-                {"Meta + Ctrl + DPAD_LEFT -> Split screen navigation",
-                        new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_LEFT},
-                        KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION, KeyEvent.KEYCODE_DPAD_LEFT,
-                        META_ON | CTRL_ON},
-                {"Meta + Ctrl + DPAD_RIGHT -> Split screen navigation",
-                        new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_RIGHT},
-                        KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION, KeyEvent.KEYCODE_DPAD_RIGHT,
-                        META_ON | CTRL_ON},
-                {"Meta + L -> Lock Homescreen", new int[]{META_KEY, KeyEvent.KEYCODE_L},
-                        KeyboardLogEvent.LOCK_SCREEN, KeyEvent.KEYCODE_L, META_ON},
-                {"Meta + Ctrl + N -> Open Notes", new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_N},
-                        KeyboardLogEvent.OPEN_NOTES, KeyEvent.KEYCODE_N, META_ON | CTRL_ON},
-                {"POWER key -> Toggle Power", new int[]{KeyEvent.KEYCODE_POWER},
-                        KeyboardLogEvent.TOGGLE_POWER, KeyEvent.KEYCODE_POWER, 0},
-                {"TV_POWER key -> Toggle Power", new int[]{KeyEvent.KEYCODE_TV_POWER},
-                        KeyboardLogEvent.TOGGLE_POWER, KeyEvent.KEYCODE_TV_POWER, 0},
-                {"SYSTEM_NAVIGATION_DOWN key -> System Navigation",
-                        new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN},
-                        KeyboardLogEvent.SYSTEM_NAVIGATION, KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN,
-                        0},
-                {"SYSTEM_NAVIGATION_UP key -> System Navigation",
-                        new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP},
-                        KeyboardLogEvent.SYSTEM_NAVIGATION, KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP,
-                        0},
-                {"SYSTEM_NAVIGATION_LEFT key -> System Navigation",
-                        new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT},
-                        KeyboardLogEvent.SYSTEM_NAVIGATION, KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT,
-                        0},
-                {"SYSTEM_NAVIGATION_RIGHT key -> System Navigation",
-                        new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT},
-                        KeyboardLogEvent.SYSTEM_NAVIGATION,
-                        KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT, 0},
-                {"SLEEP key -> System Sleep", new int[]{KeyEvent.KEYCODE_SLEEP},
-                        KeyboardLogEvent.SLEEP, KeyEvent.KEYCODE_SLEEP, 0},
-                {"SOFT_SLEEP key -> System Sleep", new int[]{KeyEvent.KEYCODE_SOFT_SLEEP},
-                        KeyboardLogEvent.SLEEP, KeyEvent.KEYCODE_SOFT_SLEEP, 0},
-                {"WAKEUP key -> System Wakeup", new int[]{KeyEvent.KEYCODE_WAKEUP},
-                        KeyboardLogEvent.WAKEUP, KeyEvent.KEYCODE_WAKEUP, 0},
-                {"MEDIA_PLAY key -> Media Control", new int[]{KeyEvent.KEYCODE_MEDIA_PLAY},
-                        KeyboardLogEvent.MEDIA_KEY, KeyEvent.KEYCODE_MEDIA_PLAY, 0},
-                {"MEDIA_PAUSE key -> Media Control", new int[]{KeyEvent.KEYCODE_MEDIA_PAUSE},
-                        KeyboardLogEvent.MEDIA_KEY, KeyEvent.KEYCODE_MEDIA_PAUSE, 0},
-                {"MEDIA_PLAY_PAUSE key -> Media Control",
-                        new int[]{KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE}, KeyboardLogEvent.MEDIA_KEY,
-                        KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, 0},
-                {"Meta + B -> Launch Default Browser", new int[]{META_KEY, KeyEvent.KEYCODE_B},
-                        KeyboardLogEvent.LAUNCH_DEFAULT_BROWSER, KeyEvent.KEYCODE_B, META_ON},
-                {"EXPLORER key -> Launch Default Browser", new int[]{KeyEvent.KEYCODE_EXPLORER},
-                        KeyboardLogEvent.LAUNCH_DEFAULT_BROWSER, KeyEvent.KEYCODE_EXPLORER, 0},
-                {"Meta + C -> Launch Default Contacts", new int[]{META_KEY, KeyEvent.KEYCODE_C},
-                        KeyboardLogEvent.LAUNCH_DEFAULT_CONTACTS, KeyEvent.KEYCODE_C, META_ON},
-                {"CONTACTS key -> Launch Default Contacts", new int[]{KeyEvent.KEYCODE_CONTACTS},
-                        KeyboardLogEvent.LAUNCH_DEFAULT_CONTACTS, KeyEvent.KEYCODE_CONTACTS, 0},
-                {"Meta + E -> Launch Default Email", new int[]{META_KEY, KeyEvent.KEYCODE_E},
-                        KeyboardLogEvent.LAUNCH_DEFAULT_EMAIL, KeyEvent.KEYCODE_E, META_ON},
-                {"ENVELOPE key -> Launch Default Email", new int[]{KeyEvent.KEYCODE_ENVELOPE},
-                        KeyboardLogEvent.LAUNCH_DEFAULT_EMAIL, KeyEvent.KEYCODE_ENVELOPE, 0},
-                {"Meta + K -> Launch Default Calendar", new int[]{META_KEY, KeyEvent.KEYCODE_K},
-                        KeyboardLogEvent.LAUNCH_DEFAULT_CALENDAR, KeyEvent.KEYCODE_K, META_ON},
-                {"CALENDAR key -> Launch Default Calendar", new int[]{KeyEvent.KEYCODE_CALENDAR},
-                        KeyboardLogEvent.LAUNCH_DEFAULT_CALENDAR, KeyEvent.KEYCODE_CALENDAR, 0},
-                {"Meta + P -> Launch Default Music", new int[]{META_KEY, KeyEvent.KEYCODE_P},
-                        KeyboardLogEvent.LAUNCH_DEFAULT_MUSIC, KeyEvent.KEYCODE_P, META_ON},
-                {"MUSIC key -> Launch Default Music", new int[]{KeyEvent.KEYCODE_MUSIC},
-                        KeyboardLogEvent.LAUNCH_DEFAULT_MUSIC, KeyEvent.KEYCODE_MUSIC, 0},
-                {"Meta + U -> Launch Default Calculator", new int[]{META_KEY, KeyEvent.KEYCODE_U},
-                        KeyboardLogEvent.LAUNCH_DEFAULT_CALCULATOR, KeyEvent.KEYCODE_U, META_ON},
-                {"CALCULATOR key -> Launch Default Calculator",
-                        new int[]{KeyEvent.KEYCODE_CALCULATOR},
-                        KeyboardLogEvent.LAUNCH_DEFAULT_CALCULATOR, KeyEvent.KEYCODE_CALCULATOR, 0},
-                {"Meta + M -> Launch Default Maps", new int[]{META_KEY, KeyEvent.KEYCODE_M},
-                        KeyboardLogEvent.LAUNCH_DEFAULT_MAPS, KeyEvent.KEYCODE_M, META_ON},
-                {"Meta + S -> Launch Default Messaging App",
-                        new int[]{META_KEY, KeyEvent.KEYCODE_S},
-                        KeyboardLogEvent.LAUNCH_DEFAULT_MESSAGING, KeyEvent.KEYCODE_S, META_ON},
-                {"Meta + Ctrl + DPAD_DOWN -> Enter desktop mode",
-                        new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_DOWN},
-                        KeyboardLogEvent.DESKTOP_MODE, KeyEvent.KEYCODE_DPAD_DOWN,
-                        META_ON | CTRL_ON}};
-    }
-
-    @Keep
-    private static Object[][] longPressOnHomeTestArguments() {
-        // testName, testKeys, longPressOnHomeBehavior, expectedLogEvent, expectedKey,
-        // expectedModifierState
-        return new Object[][]{
-                {"Long press HOME key -> Toggle Notification panel",
-                        new int[]{KeyEvent.KEYCODE_HOME}, LONG_PRESS_HOME_NOTIFICATION_PANEL,
-                        KeyboardLogEvent.TOGGLE_NOTIFICATION_PANEL, KeyEvent.KEYCODE_HOME, 0},
-                {"Long press META + ENTER -> Toggle Notification panel",
-                        new int[]{META_KEY, KeyEvent.KEYCODE_ENTER},
-                        LONG_PRESS_HOME_NOTIFICATION_PANEL,
-                        KeyboardLogEvent.TOGGLE_NOTIFICATION_PANEL, KeyEvent.KEYCODE_ENTER,
-                        META_ON},
-                {"Long press META + H -> Toggle Notification panel",
-                        new int[]{META_KEY, KeyEvent.KEYCODE_H}, LONG_PRESS_HOME_NOTIFICATION_PANEL,
-                        KeyboardLogEvent.TOGGLE_NOTIFICATION_PANEL, KeyEvent.KEYCODE_H, META_ON},
-                {"Long press HOME key -> Launch assistant",
-                        new int[]{KeyEvent.KEYCODE_HOME}, LONG_PRESS_HOME_ASSIST,
-                        KeyboardLogEvent.LAUNCH_ASSISTANT, KeyEvent.KEYCODE_HOME, 0},
-                {"Long press META + ENTER -> Launch assistant",
-                        new int[]{META_KEY, KeyEvent.KEYCODE_ENTER}, LONG_PRESS_HOME_ASSIST,
-                        KeyboardLogEvent.LAUNCH_ASSISTANT, KeyEvent.KEYCODE_ENTER, META_ON},
-                {"Long press META + H -> Launch assistant",
-                        new int[]{META_KEY, KeyEvent.KEYCODE_H}, LONG_PRESS_HOME_ASSIST,
-                        KeyboardLogEvent.LAUNCH_ASSISTANT, KeyEvent.KEYCODE_H, META_ON},
-                {"Long press HOME key -> Open App Drawer in Accessibility mode",
-                        new int[]{KeyEvent.KEYCODE_HOME}, LONG_PRESS_HOME_ALL_APPS,
-                        KeyboardLogEvent.ACCESSIBILITY_ALL_APPS, KeyEvent.KEYCODE_HOME, 0},
-                {"Long press META + ENTER -> Open App Drawer in Accessibility mode",
-                        new int[]{META_KEY, KeyEvent.KEYCODE_ENTER}, LONG_PRESS_HOME_ALL_APPS,
-                        KeyboardLogEvent.ACCESSIBILITY_ALL_APPS, KeyEvent.KEYCODE_ENTER, META_ON},
-                {"Long press META + H -> Open App Drawer in Accessibility mode",
-                        new int[]{META_KEY, KeyEvent.KEYCODE_H},
-                        LONG_PRESS_HOME_ALL_APPS, KeyboardLogEvent.ACCESSIBILITY_ALL_APPS,
-                        KeyEvent.KEYCODE_H, META_ON}};
-    }
-
-    @Keep
-    private static Object[][] doubleTapOnHomeTestArguments() {
-        // testName, testKeys, doubleTapOnHomeBehavior, expectedLogEvent, expectedKey,
-        // expectedModifierState
-        return new Object[][]{
-                {"Double tap HOME -> Open App switcher",
-                        new int[]{KeyEvent.KEYCODE_HOME}, DOUBLE_TAP_HOME_RECENT_SYSTEM_UI,
-                        KeyboardLogEvent.APP_SWITCH, KeyEvent.KEYCODE_HOME, 0},
-                {"Double tap META + ENTER -> Open App switcher",
-                        new int[]{META_KEY, KeyEvent.KEYCODE_ENTER},
-                        DOUBLE_TAP_HOME_RECENT_SYSTEM_UI, KeyboardLogEvent.APP_SWITCH,
-                        KeyEvent.KEYCODE_ENTER, META_ON},
-                {"Double tap META + H -> Open App switcher",
-                        new int[]{META_KEY, KeyEvent.KEYCODE_H}, DOUBLE_TAP_HOME_RECENT_SYSTEM_UI,
-                        KeyboardLogEvent.APP_SWITCH, KeyEvent.KEYCODE_H, META_ON}};
-    }
-
-    @Keep
-    private static Object[][] settingsKeyTestArguments() {
-        // testName, testKeys, settingsKeyBehavior, expectedLogEvent, expectedKey,
-        // expectedModifierState
-        return new Object[][]{
-                {"SETTINGS key -> Toggle Notification panel", new int[]{KeyEvent.KEYCODE_SETTINGS},
-                        SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL,
-                        KeyboardLogEvent.TOGGLE_NOTIFICATION_PANEL, KeyEvent.KEYCODE_SETTINGS, 0}};
-    }
-
-    @Before
-    public void setUp() {
-        setUpPhoneWindowManager(/*supportSettingsUpdate*/ true);
-        mPhoneWindowManager.overrideKeyEventSource(VENDOR_ID, PRODUCT_ID, DEVICE_BUS);
-        mPhoneWindowManager.overrideLaunchHome();
-        mPhoneWindowManager.overrideSearchKeyBehavior(
-                PhoneWindowManager.SEARCH_KEY_BEHAVIOR_TARGET_ACTIVITY);
-        mPhoneWindowManager.overrideEnableBugReportTrigger(true);
-        mPhoneWindowManager.overrideStatusBarManagerInternal();
-        mPhoneWindowManager.overrideStartActivity();
-        mPhoneWindowManager.overrideSendBroadcast();
-        mPhoneWindowManager.overrideUserSetupComplete();
-        mPhoneWindowManager.setupAssistForLaunch();
-        mPhoneWindowManager.overrideTogglePanel();
-        mPhoneWindowManager.overrideInjectKeyEvent();
-    }
-
-    @Test
-    @Parameters(method = "shortcutTestArguments")
-    public void testShortcuts(String testName, int[] testKeys, KeyboardLogEvent expectedLogEvent,
-            int expectedKey, int expectedModifierState) {
-        sendKeyCombination(testKeys, 0 /* duration */);
-        mPhoneWindowManager.assertShortcutLogged(VENDOR_ID, PRODUCT_ID, expectedLogEvent,
-                expectedKey, expectedModifierState, DEVICE_BUS,
-                "Failed while executing " + testName);
-    }
-
-    @Test
-    @Parameters(method = "longPressOnHomeTestArguments")
-    public void testLongPressOnHome(String testName, int[] testKeys, int longPressOnHomeBehavior,
-            KeyboardLogEvent expectedLogEvent, int expectedKey, int expectedModifierState) {
-        mPhoneWindowManager.overrideLongPressOnHomeBehavior(longPressOnHomeBehavior);
-        sendLongPressKeyCombination(testKeys);
-        mPhoneWindowManager.assertShortcutLogged(VENDOR_ID, PRODUCT_ID, expectedLogEvent,
-                expectedKey, expectedModifierState, DEVICE_BUS,
-                "Failed while executing " + testName);
-    }
-
-    @Test
-    @Parameters(method = "doubleTapOnHomeTestArguments")
-    public void testDoubleTapOnHomeBehavior(String testName, int[] testKeys,
-            int doubleTapOnHomeBehavior, KeyboardLogEvent expectedLogEvent, int expectedKey,
-            int expectedModifierState) {
-        mPhoneWindowManager.overriderDoubleTapOnHomeBehavior(doubleTapOnHomeBehavior);
-        sendKeyCombination(testKeys, 0 /* duration */);
-        sendKeyCombination(testKeys, 0 /* duration */);
-        mPhoneWindowManager.assertShortcutLogged(VENDOR_ID, PRODUCT_ID, expectedLogEvent,
-                expectedKey, expectedModifierState, DEVICE_BUS,
-                "Failed while executing " + testName);
-    }
-
-    @Test
-    @Parameters(method = "settingsKeyTestArguments")
-    public void testSettingsKey(String testName, int[] testKeys,
-            int settingsKeyBehavior, KeyboardLogEvent expectedLogEvent, int expectedKey,
-            int expectedModifierState) {
-        mPhoneWindowManager.overrideSettingsKeyBehavior(settingsKeyBehavior);
-        sendKeyCombination(testKeys, 0 /* duration */);
-        mPhoneWindowManager.assertShortcutLogged(VENDOR_ID, PRODUCT_ID, expectedLogEvent,
-                expectedKey, expectedModifierState, DEVICE_BUS,
-                "Failed while executing " + testName);
-    }
-
-    @Test
-    @RequiresFlagsEnabled(com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT)
-    public void testBugreportShortcutPress() {
-        sendKeyCombination(new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DEL}, 0);
-        mPhoneWindowManager.assertShortcutLogged(VENDOR_ID, PRODUCT_ID,
-                KeyboardLogEvent.TRIGGER_BUG_REPORT, KeyEvent.KEYCODE_DEL, META_ON | CTRL_ON,
-                DEVICE_BUS, "Failed to log bugreport shortcut.");
-    }
-}
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 6f8c91c..f9b5c2a 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -26,7 +26,6 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyLong;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyString;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.description;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -50,6 +49,7 @@
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.CALLS_REAL_METHODS;
 import static org.mockito.Mockito.after;
+import static org.mockito.Mockito.description;
 import static org.mockito.Mockito.mockingDetails;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.withSettings;
@@ -70,6 +70,7 @@
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.input.InputManager;
+import android.hardware.input.KeyboardSystemShortcut;
 import android.media.AudioManagerInternal;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -85,7 +86,6 @@
 import android.service.dreams.DreamManagerInternal;
 import android.telecom.TelecomManager;
 import android.view.Display;
-import android.view.InputDevice;
 import android.view.KeyEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.view.autofill.AutofillManagerInternal;
@@ -93,11 +93,9 @@
 import com.android.dx.mockito.inline.extended.StaticMockitoSession;
 import com.android.internal.accessibility.AccessibilityShortcutController;
 import com.android.internal.policy.KeyInterceptionInfo;
-import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.GestureLauncherService;
 import com.android.server.LocalServices;
 import com.android.server.input.InputManagerInternal;
-import com.android.server.input.KeyboardMetricsCollector.KeyboardLogEvent;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.policy.keyguard.KeyguardServiceDelegate;
@@ -269,7 +267,6 @@
         // Return mocked services: LocalServices.getService
         mMockitoSession = mockitoSession()
                 .mockStatic(LocalServices.class, spyStubOnly)
-                .mockStatic(FrameworkStatsLog.class)
                 .strictness(Strictness.LENIENT)
                 .startMocking();
 
@@ -583,19 +580,6 @@
         doReturn(mPackageManager).when(mContext).getPackageManager();
     }
 
-    void overrideKeyEventSource(int vendorId, int productId, int deviceBus) {
-        InputDevice device = new InputDevice.Builder()
-                .setId(1)
-                .setVendorId(vendorId)
-                .setProductId(productId)
-                .setDeviceBus(deviceBus)
-                .setSources(InputDevice.SOURCE_KEYBOARD)
-                .setKeyboardType(InputDevice.KEYBOARD_TYPE_ALPHABETIC)
-                .build();
-        doReturn(mInputManager).when(mContext).getSystemService(eq(InputManager.class));
-        doReturn(device).when(mInputManager).getInputDevice(anyInt());
-    }
-
     void overrideInjectKeyEvent() {
         doReturn(true).when(mInputManager).injectInputEvent(any(KeyEvent.class), anyInt());
     }
@@ -820,12 +804,11 @@
         Assert.assertEquals(targetActivity, intentCaptor.getValue().getComponent());
     }
 
-    void assertShortcutLogged(int vendorId, int productId, KeyboardLogEvent logEvent,
-            int expectedKey, int expectedModifierState, int deviceBus, String errorMsg) {
+    void assertKeyboardShortcutTriggered(int[] keycodes, int modifierState, int systemShortcut,
+            String errorMsg) {
         mTestLooper.dispatchAll();
-        verify(() -> FrameworkStatsLog.write(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED,
-                        vendorId, productId, logEvent.getIntValue(), new int[]{expectedKey},
-                        expectedModifierState, deviceBus), description(errorMsg));
+        verify(mInputManagerInternal, description(errorMsg)).notifyKeyboardShortcutTriggered(
+                anyInt(), eq(keycodes), eq(modifierState), eq(systemShortcut));
     }
 
     void assertSwitchToTask(int persistentId) throws RemoteException {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java
index 03d3029..2a53df9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java
@@ -258,6 +258,6 @@
                 Surface.ROTATION_0, new Point(100, 100), new Rect() /* contentInsets */,
                 new Rect() /* letterboxInsets*/, false /* isLowResolution */,
                 true /* isRealSnapshot */, WINDOWING_MODE_FULLSCREEN, 0 /* mSystemUiVisibility */,
-                false /* isTranslucent */, false /* hasImeSurface */);
+                false /* isTranslucent */, false /* hasImeSurface */, 0 /* uiMode */);
     }
 }
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 f8cf97e..a745724 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
@@ -36,9 +36,12 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
+import android.graphics.Rect;
 import android.view.Surface;
 
+import androidx.annotation.CallSuper;
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import com.android.server.wm.utils.TestComponentStack;
 
@@ -74,19 +77,36 @@
     private final int mDisplayHeight;
     private DisplayContent mDisplayContent;
 
+    @Nullable
+    private Consumer<ActivityRecord> mOnPostActivityCreation;
+
+    @Nullable
+    private Consumer<DisplayContent> mOnPostDisplayContentCreation;
+
     AppCompatActivityRobot(@NonNull WindowManagerService wm,
             @NonNull ActivityTaskManagerService atm, @NonNull ActivityTaskSupervisor supervisor,
-            int displayWidth, int displayHeight) {
+            int displayWidth, int displayHeight,
+            @Nullable Consumer<ActivityRecord> onPostActivityCreation,
+            @Nullable Consumer<DisplayContent> onPostDisplayContentCreation) {
         mAtm = atm;
         mSupervisor = supervisor;
         mDisplayWidth = displayWidth;
         mDisplayHeight = displayHeight;
         mActivityStack = new TestComponentStack<>();
         mTaskStack = new TestComponentStack<>();
+        mOnPostActivityCreation = onPostActivityCreation;
+        mOnPostDisplayContentCreation = onPostDisplayContentCreation;
         createNewDisplay();
     }
 
     AppCompatActivityRobot(@NonNull WindowManagerService wm,
+            @NonNull ActivityTaskManagerService atm, @NonNull ActivityTaskSupervisor supervisor,
+            int displayWidth, int displayHeight) {
+        this(wm, atm, supervisor, displayWidth, displayHeight, /* onPostActivityCreation */ null,
+                /* onPostDisplayContentCreation */ null);
+    }
+
+    AppCompatActivityRobot(@NonNull WindowManagerService wm,
             @NonNull ActivityTaskManagerService atm, @NonNull ActivityTaskSupervisor supervisor) {
         this(wm, atm, supervisor, DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT);
     }
@@ -96,6 +116,10 @@
                 /* inNewDisplay */ false);
     }
 
+    void createActivityWithComponentWithoutTask() {
+        createActivityWithComponentInNewTask(/* inNewTask */ false, /* inNewDisplay */ false);
+    }
+
     void createActivityWithComponentInNewTask() {
         createActivityWithComponentInNewTask(/* inNewTask */ true, /* inNewDisplay */ false);
     }
@@ -104,7 +128,6 @@
         createActivityWithComponentInNewTask(/* inNewTask */ true, /* inNewDisplay */ true);
     }
 
-
     void configureTopActivity(float minAspect, float maxAspect, int screenOrientation,
             boolean isUnresizable) {
         prepareLimitedBounds(mActivityStack.top(), minAspect, maxAspect, screenOrientation,
@@ -130,6 +153,14 @@
         doReturn(naturalOrientation).when(mDisplayContent).getNaturalOrientation();
     }
 
+    void configureTaskBounds(@NonNull Rect taskBounds) {
+        doReturn(taskBounds).when(mTaskStack.top()).getBounds();
+    }
+
+    void configureTopActivityBounds(@NonNull Rect activityBounds) {
+        doReturn(activityBounds).when(mActivityStack.top()).getBounds();
+    }
+
     @NonNull
     ActivityRecord top() {
         return mActivityStack.top();
@@ -169,6 +200,10 @@
                 .isActivityEligibleForOrientationOverride(eq(mActivityStack.top()));
     }
 
+    void setTopActivityInTransition(boolean inTransition) {
+        doReturn(inTransition).when(mActivityStack.top()).isInTransition();
+    }
+
     void setShouldApplyUserMinAspectRatioOverride(boolean enabled) {
         doReturn(enabled).when(mActivityStack.top().mAppCompatController
                 .getAppCompatAspectRatioOverrides()).shouldApplyUserMinAspectRatioOverride();
@@ -238,21 +273,20 @@
     void createNewDisplay() {
         mDisplayContent = new TestDisplayContent.Builder(mAtm, mDisplayWidth, mDisplayHeight)
                 .build();
-        spyOn(mDisplayContent);
-        spyOnAppCompatCameraPolicy();
+        onPostDisplayContentCreation(mDisplayContent);
     }
 
     void createNewTask() {
         final Task newTask = new WindowTestsBase.TaskBuilder(mSupervisor)
                 .setDisplay(mDisplayContent).build();
-        pushTask(newTask);
+        mTaskStack.push(newTask);
     }
 
     void createNewTaskWithBaseActivity() {
         final Task newTask = new WindowTestsBase.TaskBuilder(mSupervisor)
                 .setCreateActivity(true)
                 .setDisplay(mDisplayContent).build();
-        pushTask(newTask);
+        mTaskStack.push(newTask);
         pushActivity(newTask.getTopNonFinishingActivity());
     }
 
@@ -378,6 +412,34 @@
         pushActivity(newActivity);
     }
 
+    /**
+     * Specific Robots can override this method to add operation to run on a newly created
+     * {@link ActivityRecord}. Common case is to invoke spyOn().
+     *
+     * @param activity The newly created {@link ActivityRecord}.
+     */
+    @CallSuper
+    void onPostActivityCreation(@NonNull ActivityRecord activity) {
+        spyOn(activity.mLetterboxUiController);
+        if (mOnPostActivityCreation != null) {
+            mOnPostActivityCreation.accept(activity);
+        }
+    }
+
+    /**
+     * Specific Robots can override this method to add operation to run on a newly created
+     * {@link DisplayContent}. Common case is to invoke spyOn().
+     *
+     * @param displayContent The newly created {@link DisplayContent}.
+     */
+    @CallSuper
+    void onPostDisplayContentCreation(@NonNull DisplayContent displayContent) {
+        spyOn(mDisplayContent);
+        if (mOnPostDisplayContentCreation != null) {
+            mOnPostDisplayContentCreation.accept(mDisplayContent);
+        }
+    }
+
     private void createActivityWithComponentInNewTask(boolean inNewTask, boolean inNewDisplay) {
         if (inNewDisplay) {
             createNewDisplay();
@@ -385,14 +447,16 @@
         if (inNewTask) {
             createNewTask();
         }
-        final ActivityRecord activity = new WindowTestsBase.ActivityBuilder(mAtm)
-                .setOnTop(true)
-                .setTask(mTaskStack.top())
+        final WindowTestsBase.ActivityBuilder activityBuilder =
+                new WindowTestsBase.ActivityBuilder(mAtm).setOnTop(true)
                 // Set the component to be that of the test class in order
                 // to enable compat changes
-                .setComponent(ComponentName.createRelative(mAtm.mContext, TEST_COMPONENT_NAME))
-                .build();
-        pushActivity(activity);
+                .setComponent(ComponentName.createRelative(mAtm.mContext, TEST_COMPONENT_NAME));
+        if (!mTaskStack.isEmpty()) {
+            // We put the Activity in the current task if any.
+            activityBuilder.setTask(mTaskStack.top());
+        }
+        pushActivity(activityBuilder.build());
     }
 
     /**
@@ -438,28 +502,6 @@
     // We add the activity to the stack and spyOn() on its properties.
     private void pushActivity(@NonNull ActivityRecord activity) {
         mActivityStack.push(activity);
-        spyOn(activity);
-        // TODO (b/351763164): Use these spyOn calls only when necessary.
-        spyOn(activity.mAppCompatController.getTransparentPolicy());
-        spyOn(activity.mAppCompatController.getAppCompatAspectRatioOverrides());
-        spyOn(activity.mAppCompatController.getAppCompatAspectRatioPolicy());
-        spyOn(activity.mAppCompatController.getAppCompatFocusOverrides());
-        spyOn(activity.mAppCompatController.getAppCompatResizeOverrides());
-        spyOn(activity.mLetterboxUiController);
-    }
-
-    private void pushTask(@NonNull Task task) {
-        spyOn(task);
-        mTaskStack.push(task);
-    }
-
-    private void spyOnAppCompatCameraPolicy() {
-        spyOn(mDisplayContent.mAppCompatCameraPolicy);
-        if (mDisplayContent.mAppCompatCameraPolicy.hasDisplayRotationCompatPolicy()) {
-            spyOn(mDisplayContent.mAppCompatCameraPolicy.mDisplayRotationCompatPolicy);
-        }
-        if (mDisplayContent.mAppCompatCameraPolicy.hasCameraCompatFreeformPolicy()) {
-            spyOn(mDisplayContent.mAppCompatCameraPolicy.mCameraCompatFreeformPolicy);
-        }
+        onPostActivityCreation(activity);
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java
index a6fd112..1e40aa0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java
@@ -291,7 +291,6 @@
      * Runs a test scenario providing a Robot.
      */
     void runTestScenario(@NonNull Consumer<AspectRatioOverridesRobotTest> consumer) {
-        spyOn(mWm.mAppCompatConfiguration);
         final AspectRatioOverridesRobotTest robot =
                 new AspectRatioOverridesRobotTest(mWm, mAtm, mSupervisor);
         consumer.accept(robot);
@@ -305,6 +304,18 @@
             super(wm, atm, supervisor);
         }
 
+        @Override
+        void onPostDisplayContentCreation(@NonNull DisplayContent displayContent) {
+            super.onPostDisplayContentCreation(displayContent);
+            spyOn(displayContent.mAppCompatCameraPolicy);
+        }
+
+        @Override
+        void onPostActivityCreation(@NonNull ActivityRecord activity) {
+            super.onPostActivityCreation(activity);
+            spyOn(activity.mAppCompatController.getAppCompatAspectRatioOverrides());
+        }
+
         void checkShouldApplyUserFullscreenOverride(boolean expected) {
             assertEquals(expected, getTopActivityAppCompatAspectRatioOverrides()
                     .shouldApplyUserFullscreenOverride());
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
index de99f54..84ffcb8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
@@ -387,6 +387,12 @@
             super(wm, atm, supervisor);
         }
 
+        @Override
+        void onPostDisplayContentCreation(@NonNull DisplayContent displayContent) {
+            super.onPostDisplayContentCreation(displayContent);
+            spyOn(displayContent.mAppCompatCameraPolicy);
+        }
+
         void checkShouldRefreshActivityForCameraCompat(boolean expected) {
             Assert.assertEquals(getAppCompatCameraOverrides()
                     .shouldRefreshActivityForCameraCompat(), expected);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java
index 0b1bb0f..c42228d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java
@@ -150,6 +150,12 @@
             super(wm, atm, supervisor);
         }
 
+        @Override
+        void onPostDisplayContentCreation(@NonNull DisplayContent displayContent) {
+            super.onPostDisplayContentCreation(displayContent);
+            spyOn(displayContent.mAppCompatCameraPolicy);
+        }
+
         void checkTopActivityHasDisplayRotationCompatPolicy(boolean exists) {
             Assert.assertEquals(exists, activity().top().mDisplayContent
                     .mAppCompatCameraPolicy.hasDisplayRotationCompatPolicy());
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java
index 6592f26..40a5347 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java
@@ -19,6 +19,9 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import androidx.annotation.NonNull;
@@ -80,4 +83,34 @@
         doReturn(aspectRatio).when(mAppCompatConfiguration)
                 .getFixedOrientationLetterboxAspectRatio();
     }
+
+    void setThinLetterboxWidthPx(int thinWidthPx) {
+        doReturn(thinWidthPx).when(mAppCompatConfiguration)
+                .getThinLetterboxWidthPx();
+    }
+
+    void setThinLetterboxHeightPx(int thinHeightPx) {
+        doReturn(thinHeightPx).when(mAppCompatConfiguration)
+                .getThinLetterboxHeightPx();
+    }
+
+    void checkToNextLeftStop(boolean invoked) {
+        verify(mAppCompatConfiguration, times(invoked ? 1 : 0))
+                .movePositionForHorizontalReachabilityToNextLeftStop(anyBoolean());
+    }
+
+    void checkToNextRightStop(boolean invoked) {
+        verify(mAppCompatConfiguration, times(invoked ? 1 : 0))
+                .movePositionForHorizontalReachabilityToNextRightStop(anyBoolean());
+    }
+
+    void checkToNextBottomStop(boolean invoked) {
+        verify(mAppCompatConfiguration, times(invoked ? 1 : 0))
+                .movePositionForVerticalReachabilityToNextBottomStop(anyBoolean());
+    }
+
+    void checkToNextTopStop(boolean invoked) {
+        verify(mAppCompatConfiguration, times(invoked ? 1 : 0))
+                .movePositionForVerticalReachabilityToNextTopStop(anyBoolean());
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java
index 6c0d8c4..d9b5f37 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java
@@ -250,6 +250,12 @@
             mTestCurrentTimeMillisSupplier = new CurrentTimeMillisSupplierFake();
         }
 
+        @Override
+        void onPostActivityCreation(@NonNull ActivityRecord activity) {
+            super.onPostActivityCreation(activity);
+            spyOn(activity.mAppCompatController.getAppCompatAspectRatioPolicy());
+        }
+
         // Useful to reduce timeout during tests
         void prepareMockedTime() {
             getTopOrientationOverrides().mOrientationOverridesState.mCurrentTimeMillisSupplier =
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
index ad34a6b..f6d0744 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
@@ -536,6 +536,25 @@
             }
         }
 
+        @Override
+        void onPostActivityCreation(@NonNull ActivityRecord activity) {
+            super.onPostActivityCreation(activity);
+            spyOn(activity.mAppCompatController.getAppCompatAspectRatioOverrides());
+            spyOn(activity.mAppCompatController.getAppCompatAspectRatioPolicy());
+        }
+
+        @Override
+        void onPostDisplayContentCreation(@NonNull DisplayContent displayContent) {
+            super.onPostDisplayContentCreation(displayContent);
+            spyOn(displayContent.mAppCompatCameraPolicy);
+            if (displayContent.mAppCompatCameraPolicy.hasDisplayRotationCompatPolicy()) {
+                spyOn(displayContent.mAppCompatCameraPolicy.mDisplayRotationCompatPolicy);
+            }
+            if (displayContent.mAppCompatCameraPolicy.hasCameraCompatFreeformPolicy()) {
+                spyOn(displayContent.mAppCompatCameraPolicy.mCameraCompatFreeformPolicy);
+            }
+        }
+
         void prepareRelaunchingAfterRequestedOrientationChanged(boolean enabled) {
             getTopOrientationOverrides().setRelaunchingAfterRequestedOrientationChanged(enabled);
         }
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityOverridesTest.java
new file mode 100644
index 0000000..5ff8f02
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityOverridesTest.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
+import android.compat.testing.PlatformCompatChangeRule;
+import android.graphics.Rect;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.annotation.NonNull;
+
+import com.android.window.flags.Flags;
+
+import junit.framework.Assert;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/**
+ * Test class for {@link AppCompatReachabilityOverrides}.
+ * <p>
+ * Build/Install/Run:
+ * atest WmTests:AppCompatReachabilityOverridesTest
+ */
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class AppCompatReachabilityOverridesTest extends WindowTestsBase {
+
+    @Rule
+    public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
+    @Test
+    public void testIsThinLetterboxed_NegativePx_returnsFalse() {
+        runTestScenario((robot) -> {
+            robot.activity().createActivityWithComponentWithoutTask();
+            robot.conf().setThinLetterboxHeightPx(/* thinHeightPx */ -1);
+            robot.checkIsVerticalThinLetterboxed(/* expected */ false);
+
+            robot.conf().setThinLetterboxWidthPx(/* thinHeightPx */ -1);
+            robot.checkIsHorizontalThinLetterboxed(/* expected */ false);
+        });
+    }
+
+    @Test
+    public void testIsThinLetterboxed_noTask_returnsFalse() {
+        runTestScenario((robot) -> {
+            robot.activity().createActivityWithComponentWithoutTask();
+            robot.conf().setThinLetterboxHeightPx(/* thinHeightPx */ 10);
+            robot.checkIsVerticalThinLetterboxed(/* expected */ false);
+
+            robot.conf().setThinLetterboxWidthPx(/* thinHeightPx */ 10);
+            robot.checkIsHorizontalThinLetterboxed(/* expected */ false);
+        });
+    }
+
+    @Test
+    public void testIsVerticalThinLetterboxed() {
+        runTestScenario((robot) -> {
+            robot.conf().setThinLetterboxHeightPx(/* thinHeightPx */ 10);
+            robot.applyOnActivity((a) -> {
+                a.createActivityWithComponent();
+                a.configureTaskBounds(new Rect(0, 0, 100, 100));
+
+                // (task.width() - act.width()) / 2  = 5 < 10
+                a.configureTopActivityBounds(new Rect(5, 5, 95, 95));
+                robot.checkIsVerticalThinLetterboxed(/* expected */ true);
+
+                // (task.width() - act.width()) / 2  = 10 = 10
+                a.configureTopActivityBounds(new Rect(10, 10, 90, 90));
+                robot.checkIsVerticalThinLetterboxed(/* expected */ true);
+
+                // (task.width() - act.width()) / 2  = 11 > 10
+                a.configureTopActivityBounds(new Rect(11, 11, 89, 89));
+                robot.checkIsVerticalThinLetterboxed(/* expected */ false);
+            });
+        });
+    }
+
+    @Test
+    public void testIsHorizontalThinLetterboxed() {
+        runTestScenario((robot) -> {
+            robot.conf().setThinLetterboxWidthPx(/* thinHeightPx */ 10);
+            robot.applyOnActivity((a) -> {
+                a.createActivityWithComponent();
+                a.configureTaskBounds(new Rect(0, 0, 100, 100));
+
+                // (task.height() - act.height()) / 2  = 5 < 10
+                a.configureTopActivityBounds(new Rect(5, 5, 95, 95));
+                robot.checkIsHorizontalThinLetterboxed(/* expected */ true);
+
+                // (task.height() - act.height()) / 2  = 10 = 10
+                a.configureTopActivityBounds(new Rect(10, 10, 90, 90));
+                robot.checkIsHorizontalThinLetterboxed(/* expected */ true);
+
+                // (task.height() - act.height()) / 2  = 11 > 10
+                a.configureTopActivityBounds(new Rect(11, 11, 89, 89));
+                robot.checkIsHorizontalThinLetterboxed(/* expected */ false);
+            });
+        });
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_DISABLE_THIN_LETTERBOXING_POLICY)
+    public void testAllowReachabilityForThinLetterboxWithFlagEnabled() {
+        runTestScenario((robot) -> {
+            robot.activity().createActivityWithComponent();
+
+            robot.configureIsVerticalThinLetterboxed(/* isThin */ true);
+            robot.checkAllowVerticalReachabilityForThinLetterbox(/* expected */ false);
+            robot.configureIsHorizontalThinLetterboxed(/* isThin */ true);
+            robot.checkAllowHorizontalReachabilityForThinLetterbox(/* expected */ false);
+
+            robot.configureIsVerticalThinLetterboxed(/* isThin */ false);
+            robot.checkAllowVerticalReachabilityForThinLetterbox(/* expected */ true);
+            robot.configureIsHorizontalThinLetterboxed(/* isThin */ false);
+            robot.checkAllowHorizontalReachabilityForThinLetterbox(/* expected */ true);
+        });
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_DISABLE_THIN_LETTERBOXING_POLICY)
+    public void testAllowReachabilityForThinLetterboxWithFlagDisabled() {
+        runTestScenario((robot) -> {
+            robot.activity().createActivityWithComponent();
+
+            robot.configureIsVerticalThinLetterboxed(/* isThin */ true);
+            robot.checkAllowVerticalReachabilityForThinLetterbox(/* expected */ true);
+            robot.configureIsHorizontalThinLetterboxed(/* isThin */ true);
+            robot.checkAllowHorizontalReachabilityForThinLetterbox(/* expected */ true);
+
+            robot.configureIsVerticalThinLetterboxed(/* isThin */ false);
+            robot.checkAllowVerticalReachabilityForThinLetterbox(/* expected */ true);
+            robot.configureIsHorizontalThinLetterboxed(/* isThin */ false);
+            robot.checkAllowHorizontalReachabilityForThinLetterbox(/* expected */ true);
+        });
+    }
+
+    /**
+     * Runs a test scenario providing a Robot.
+     */
+    void runTestScenario(@NonNull Consumer<ReachabilityOverridesRobotTest> consumer) {
+        spyOn(mWm.mAppCompatConfiguration);
+        final ReachabilityOverridesRobotTest robot =
+                new ReachabilityOverridesRobotTest(mWm, mAtm, mSupervisor);
+        consumer.accept(robot);
+    }
+
+    private static class ReachabilityOverridesRobotTest extends AppCompatRobotBase {
+
+        private final Supplier<Rect> mLetterboxInnerBoundsSupplier = spy(Rect::new);
+
+        ReachabilityOverridesRobotTest(@NonNull WindowManagerService wm,
+                @NonNull ActivityTaskManagerService atm,
+                @NonNull ActivityTaskSupervisor supervisor) {
+            super(wm, atm, supervisor);
+        }
+
+        @Override
+        void onPostActivityCreation(@NonNull ActivityRecord activity) {
+            super.onPostActivityCreation(activity);
+            spyOn(activity.mAppCompatController.getAppCompatReachabilityOverrides());
+            activity.mAppCompatController.getAppCompatReachabilityPolicy()
+                    .setLetterboxInnerBoundsSupplier(mLetterboxInnerBoundsSupplier);
+        }
+
+        void configureIsVerticalThinLetterboxed(boolean isThin) {
+            doReturn(isThin).when(getAppCompatReachabilityOverrides())
+                    .isVerticalThinLetterboxed();
+        }
+
+        void configureIsHorizontalThinLetterboxed(boolean isThin) {
+            doReturn(isThin).when(getAppCompatReachabilityOverrides())
+                    .isHorizontalThinLetterboxed();
+        }
+
+        void checkIsVerticalThinLetterboxed(boolean expected) {
+            Assert.assertEquals(expected,
+                    getAppCompatReachabilityOverrides().isVerticalThinLetterboxed());
+        }
+
+        void checkIsHorizontalThinLetterboxed(boolean expected) {
+            Assert.assertEquals(expected,
+                    getAppCompatReachabilityOverrides().isHorizontalThinLetterboxed());
+        }
+
+        void checkAllowVerticalReachabilityForThinLetterbox(boolean expected) {
+            Assert.assertEquals(expected, getAppCompatReachabilityOverrides()
+                    .allowVerticalReachabilityForThinLetterbox());
+        }
+
+        void checkAllowHorizontalReachabilityForThinLetterbox(boolean expected) {
+            Assert.assertEquals(expected, getAppCompatReachabilityOverrides()
+                    .allowHorizontalReachabilityForThinLetterbox());
+        }
+
+        @NonNull
+        private AppCompatReachabilityOverrides getAppCompatReachabilityOverrides() {
+            return activity().top().mAppCompatController.getAppCompatReachabilityOverrides();
+        }
+
+    }
+
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityPolicyTest.java
new file mode 100644
index 0000000..96734b3
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityPolicyTest.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.annotation.NonNull;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/**
+ * Test class for {@link AppCompatReachabilityPolicy}.
+ * <p/>
+ * Build/Install/Run:
+ * atest WmTests:AppCompatReachabilityPolicyTest
+ */
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class AppCompatReachabilityPolicyTest extends WindowTestsBase {
+
+    @Test
+    public void handleHorizontalDoubleTap_reachabilityDisabled_nothingHappen() {
+        runTestScenario((robot) -> {
+            robot.activity().createActivityWithComponent();
+            robot.enableHorizontalReachability(/* enabled */ false);
+            robot.activity().setTopActivityInTransition(/* inTransition */ true);
+            robot.doubleTapAt(100, 100);
+
+            robot.checkLetterboxInnerFrameProvidedInvoked(/* invoked */ false);
+        });
+    }
+
+    @Test
+    public void handleHorizontalDoubleTap_reachabilityEnabledInTransition_nothingHappen() {
+        runTestScenario((robot) -> {
+            robot.activity().createActivityWithComponent();
+            robot.enableHorizontalReachability(/* enabled */ true);
+            robot.activity().setTopActivityInTransition(/* inTransition */ true);
+            robot.doubleTapAt(100, 100);
+
+            robot.checkLetterboxInnerFrameProvidedInvoked(/* invoked */ false);
+        });
+    }
+
+    @Test
+    public void handleHorizontalDoubleTap_reachabilityDisabledNotInTransition_nothingHappen() {
+        runTestScenario((robot) -> {
+            robot.activity().createActivityWithComponent();
+            robot.enableHorizontalReachability(/* enabled */ false);
+            robot.activity().setTopActivityInTransition(/* inTransition */ false);
+            robot.doubleTapAt(100, 100);
+
+            robot.checkLetterboxInnerFrameProvidedInvoked(/* invoked */ false);
+        });
+    }
+
+    @Test
+    public void handleHorizontalDoubleTap_leftInnerFrame_moveToLeft() {
+        runTestScenario((robot) -> {
+            robot.activity().createActivityWithComponent();
+            robot.enableHorizontalReachability(/* enabled */ true);
+            robot.activity().setTopActivityInTransition(/* inTransition */ false);
+
+            robot.configureLetterboxInnerFrameWidth(/* left */ 100, /* right */ 200);
+            robot.doubleTapAt(99, 100);
+
+            robot.checkLetterboxInnerFrameProvidedInvoked(/* invoked */ true);
+            robot.applyOnConf((c) -> {
+                c.checkToNextLeftStop(/* invoked */ true);
+                c.checkToNextRightStop(/* invoked */ false);
+            });
+        });
+    }
+
+    @Test
+    public void handleHorizontalDoubleTap_rightInnerFrame_moveToRight() {
+        runTestScenario((robot) -> {
+            robot.activity().createActivityWithComponent();
+            robot.enableHorizontalReachability(/* enabled */ true);
+            robot.activity().setTopActivityInTransition(/* inTransition */ false);
+
+            robot.configureLetterboxInnerFrameWidth(/* left */ 100, /* right */ 200);
+            robot.doubleTapAt(201, 100);
+
+            robot.checkLetterboxInnerFrameProvidedInvoked(/* invoked */ true);
+            robot.applyOnConf((c) -> {
+                c.checkToNextLeftStop(/* invoked */ false);
+                c.checkToNextRightStop(/* invoked */ true);
+            });
+        });
+    }
+
+    @Test
+    public void handleHorizontalDoubleTap_intoInnerFrame_noMove() {
+        runTestScenario((robot) -> {
+            robot.activity().createActivityWithComponent();
+            robot.enableHorizontalReachability(/* enabled */ true);
+            robot.activity().setTopActivityInTransition(/* inTransition */ false);
+
+            robot.configureLetterboxInnerFrameWidth(/* left */ 100, /* right */ 200);
+            robot.doubleTapAt(150, 100);
+
+            robot.checkLetterboxInnerFrameProvidedInvoked(/* invoked */ true);
+            robot.applyOnConf((c) -> {
+                c.checkToNextLeftStop(/* invoked */ false);
+                c.checkToNextRightStop(/* invoked */ false);
+            });
+        });
+    }
+
+
+    @Test
+    public void handleVerticalDoubleTap_reachabilityDisabled_nothingHappen() {
+        runTestScenario((robot) -> {
+            robot.activity().createActivityWithComponent();
+            robot.enableVerticalReachability(/* enabled */ false);
+            robot.activity().setTopActivityInTransition(/* inTransition */ true);
+            robot.doubleTapAt(100, 100);
+
+            robot.checkLetterboxInnerFrameProvidedInvoked(/* invoked */ false);
+        });
+    }
+
+    @Test
+    public void handleVerticalDoubleTap_reachabilityEnabledInTransition_nothingHappen() {
+        runTestScenario((robot) -> {
+            robot.activity().createActivityWithComponent();
+            robot.enableVerticalReachability(/* enabled */ true);
+            robot.activity().setTopActivityInTransition(/* inTransition */ true);
+
+            robot.checkLetterboxInnerFrameProvidedInvoked(/* invoked */ false);
+        });
+    }
+
+    @Test
+    public void handleVerticalDoubleTap_reachabilityDisabledNotInTransition_nothingHappen() {
+        runTestScenario((robot) -> {
+            robot.activity().createActivityWithComponent();
+            robot.enableVerticalReachability(/* enabled */ false);
+            robot.activity().setTopActivityInTransition(/* inTransition */ false);
+
+            robot.checkLetterboxInnerFrameProvidedInvoked(/* invoked */ false);
+        });
+    }
+
+    @Test
+    public void handleVerticalDoubleTap_topInnerFrame_moveToTop() {
+        runTestScenario((robot) -> {
+            robot.activity().createActivityWithComponent();
+            robot.enableVerticalReachability(/* enabled */ true);
+            robot.activity().setTopActivityInTransition(/* inTransition */ false);
+
+            robot.configureLetterboxInnerFrameHeight(/* top */ 100, /* bottom */ 200);
+            robot.doubleTapAt(100, 99);
+
+            robot.checkLetterboxInnerFrameProvidedInvoked(/* invoked */ true);
+            robot.applyOnConf((c) -> {
+                c.checkToNextTopStop(/* invoked */ true);
+                c.checkToNextBottomStop(/* invoked */ false);
+            });
+        });
+    }
+
+    @Test
+    public void handleVerticalDoubleTap_bottomInnerFrame_moveToBottom() {
+        runTestScenario((robot) -> {
+            robot.activity().createActivityWithComponent();
+            robot.enableVerticalReachability(/* enabled */ true);
+            robot.activity().setTopActivityInTransition(/* inTransition */ false);
+
+            robot.configureLetterboxInnerFrameHeight(/* top */ 100, /* bottom */ 200);
+            robot.doubleTapAt(100, 201);
+
+            robot.checkLetterboxInnerFrameProvidedInvoked(/* invoked */ true);
+            robot.applyOnConf((c) -> {
+                c.checkToNextTopStop(/* invoked */ false);
+                c.checkToNextBottomStop(/* invoked */ true);
+            });
+        });
+    }
+
+    @Test
+    public void handleVerticalDoubleTap_intoInnerFrame_noMove() {
+        runTestScenario((robot) -> {
+            robot.activity().createActivityWithComponent();
+            robot.enableVerticalReachability(/* enabled */ true);
+            robot.activity().setTopActivityInTransition(/* inTransition */ false);
+
+            robot.configureLetterboxInnerFrameHeight(/* top */ 100, /* bottom */ 200);
+            robot.doubleTapAt(100, 150);
+
+            robot.checkLetterboxInnerFrameProvidedInvoked(/* invoked */ true);
+            robot.applyOnConf((c) -> {
+                c.checkToNextTopStop(/* invoked */ false);
+                c.checkToNextBottomStop(/* invoked */ false);
+            });
+        });
+    }
+
+
+    /**
+     * Runs a test scenario providing a Robot.
+     */
+    void runTestScenario(@NonNull Consumer<ReachabilityPolicyRobotTest> consumer) {
+        spyOn(mWm.mAppCompatConfiguration);
+        final ReachabilityPolicyRobotTest robot =
+                new ReachabilityPolicyRobotTest(mWm, mAtm, mSupervisor);
+        consumer.accept(robot);
+    }
+
+    private static class ReachabilityPolicyRobotTest extends AppCompatRobotBase {
+
+        private final Supplier<Rect> mLetterboxInnerBoundsSupplier = spy(Rect::new);
+
+        ReachabilityPolicyRobotTest(@NonNull WindowManagerService wm,
+                @NonNull ActivityTaskManagerService atm,
+                @NonNull ActivityTaskSupervisor supervisor) {
+            super(wm, atm, supervisor);
+        }
+
+        @Override
+        void onPostActivityCreation(@NonNull ActivityRecord activity) {
+            super.onPostActivityCreation(activity);
+            spyOn(activity.mAppCompatController.getAppCompatReachabilityOverrides());
+            activity.mAppCompatController.getAppCompatReachabilityPolicy()
+                    .setLetterboxInnerBoundsSupplier(mLetterboxInnerBoundsSupplier);
+        }
+
+        void configureLetterboxInnerFrameWidth(int left, int right) {
+            doReturn(new Rect(left, /* top */ 0, right, /* bottom */ 100))
+                    .when(mLetterboxInnerBoundsSupplier).get();
+        }
+
+        void configureLetterboxInnerFrameHeight(int top, int bottom) {
+            doReturn(new Rect(/* left */ 0, top, /* right */ 100, bottom))
+                    .when(mLetterboxInnerBoundsSupplier).get();
+        }
+
+        void enableHorizontalReachability(boolean enabled) {
+            doReturn(enabled).when(getAppCompatReachabilityOverrides())
+                    .isHorizontalReachabilityEnabled();
+        }
+
+        void enableVerticalReachability(boolean enabled) {
+            doReturn(enabled).when(getAppCompatReachabilityOverrides())
+                    .isVerticalReachabilityEnabled();
+        }
+
+        void doubleTapAt(int x, int y) {
+            getAppCompatReachabilityPolicy().handleDoubleTap(x, y);
+        }
+
+        void checkLetterboxInnerFrameProvidedInvoked(boolean invoked) {
+            verify(mLetterboxInnerBoundsSupplier, times(invoked ? 1 : 0)).get();
+        }
+
+        @NonNull
+        private AppCompatReachabilityOverrides getAppCompatReachabilityOverrides() {
+            return activity().top().mAppCompatController.getAppCompatReachabilityOverrides();
+        }
+
+        @NonNull
+        private AppCompatReachabilityPolicy getAppCompatReachabilityPolicy() {
+            return activity().top().mAppCompatController.getAppCompatReachabilityPolicy();
+        }
+
+    }
+
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatResizeOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatResizeOverridesTest.java
index 8fc1a77..cade213 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatResizeOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatResizeOverridesTest.java
@@ -39,7 +39,7 @@
 
 /**
  * Test class for {@link AppCompatResizeOverrides}.
- * <p>
+ * <p/>
  * Build/Install/Run:
  * atest WmTests:AppCompatResizeOverridesTest
  */
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatRobotBase.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatRobotBase.java
index 6939f97..4e58e1d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatRobotBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatRobotBase.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import androidx.annotation.CallSuper;
 import androidx.annotation.NonNull;
 
 import java.util.function.Consumer;
@@ -42,7 +43,8 @@
             @NonNull ActivityTaskSupervisor supervisor,
             int displayWidth, int displayHeight) {
         mActivityRobot = new AppCompatActivityRobot(wm, atm, supervisor,
-                displayWidth, displayHeight);
+                displayWidth, displayHeight, this::onPostActivityCreation,
+                this::onPostDisplayContentCreation);
         mConfigurationRobot =
                 new AppCompatConfigurationRobot(wm.mAppCompatConfiguration);
         mOptPropRobot = new AppCompatComponentPropRobot(wm);
@@ -54,6 +56,26 @@
         this(wm, atm, supervisor, DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT);
     }
 
+    /**
+     * Specific Robots can override this method to add operation to run on a newly created
+     * {@link ActivityRecord}. Common case is to invoke spyOn().
+     *
+     * @param activity THe newly created {@link ActivityRecord}.
+     */
+    @CallSuper
+    void onPostActivityCreation(@NonNull ActivityRecord activity) {
+    }
+
+    /**
+     * Specific Robots can override this method to add operation to run on a newly created
+     * {@link DisplayContent}. Common case is to invoke spyOn().
+     *
+     * @param displayContent THe newly created {@link DisplayContent}.
+     */
+    @CallSuper
+    void onPostDisplayContentCreation(@NonNull DisplayContent displayContent) {
+    }
+
     @NonNull
     AppCompatConfigurationRobot conf() {
         return mConfigurationRobot;
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatTransparentActivityRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatTransparentActivityRobot.java
index 3cfbb9e..5af7093 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatTransparentActivityRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatTransparentActivityRobot.java
@@ -62,6 +62,10 @@
         consumer.accept(mActivityRobot);
     }
 
+    void setDisplayContentBounds(int left, int top, int right, int bottom) {
+        mActivityRobot.displayContent().setBounds(left, top, right, bottom);
+    }
+
     void launchTransparentActivity() {
         mActivityRobot.launchActivity(/*minAspectRatio */ -1, /* maxAspectRatio */ -1,
                 SCREEN_ORIENTATION_PORTRAIT, /* transparent */ true,
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java
index 9e242ee..21fac9b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.wm;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
 import static org.mockito.Mockito.when;
 
 import android.platform.test.annotations.Presubmit;
@@ -42,7 +44,10 @@
     @Test
     public void getLetterboxReasonString_inSizeCompatMode() {
         runTestScenario((robot) -> {
-            robot.activity().setTopActivityInSizeCompatMode(/* inScm */ true);
+            robot.applyOnActivity((a) -> {
+                a.createActivityWithComponent();
+                a.setTopActivityInSizeCompatMode(/* inScm */ true);
+            });
 
             robot.checkTopActivityLetterboxReason(/* expected */ "SIZE_COMPAT_MODE");
         });
@@ -51,7 +56,10 @@
     @Test
     public void getLetterboxReasonString_fixedOrientation() {
         runTestScenario((robot) -> {
-            robot.activity().checkTopActivityInSizeCompatMode(/* inScm */ false);
+            robot.applyOnActivity((a) -> {
+                a.createActivityWithComponent();
+                a.checkTopActivityInSizeCompatMode(/* inScm */ false);
+            });
             robot.setIsLetterboxedForFixedOrientationAndAspectRatio(
                     /* forFixedOrientationAndAspectRatio */ true);
 
@@ -62,7 +70,10 @@
     @Test
     public void getLetterboxReasonString_isLetterboxedForDisplayCutout() {
         runTestScenario((robot) -> {
-            robot.activity().checkTopActivityInSizeCompatMode(/* inScm */ false);
+            robot.applyOnActivity((a) -> {
+                a.createActivityWithComponent();
+                a.checkTopActivityInSizeCompatMode(/* inScm */ false);
+            });
             robot.setIsLetterboxedForFixedOrientationAndAspectRatio(
                     /* forFixedOrientationAndAspectRatio */ false);
             robot.setIsLetterboxedForDisplayCutout(/* displayCutout */ true);
@@ -74,7 +85,10 @@
     @Test
     public void getLetterboxReasonString_aspectRatio() {
         runTestScenario((robot) -> {
-            robot.activity().checkTopActivityInSizeCompatMode(/* inScm */ false);
+            robot.applyOnActivity((a) -> {
+                a.createActivityWithComponent();
+                a.checkTopActivityInSizeCompatMode(/* inScm */ false);
+            });
             robot.setIsLetterboxedForFixedOrientationAndAspectRatio(
                     /* forFixedOrientationAndAspectRatio */ false);
             robot.setIsLetterboxedForDisplayCutout(/* displayCutout */ false);
@@ -87,7 +101,10 @@
     @Test
     public void getLetterboxReasonString_unknownReason() {
         runTestScenario((robot) -> {
-            robot.activity().checkTopActivityInSizeCompatMode(/* inScm */ false);
+            robot.applyOnActivity((a) -> {
+                a.createActivityWithComponent();
+                a.checkTopActivityInSizeCompatMode(/* inScm */ false);
+            });
             robot.setIsLetterboxedForFixedOrientationAndAspectRatio(
                     /* forFixedOrientationAndAspectRatio */ false);
             robot.setIsLetterboxedForDisplayCutout(/* displayCutout */ false);
@@ -97,7 +114,6 @@
         });
     }
 
-
     /**
      * Runs a test scenario providing a Robot.
      */
@@ -114,10 +130,15 @@
                 @NonNull ActivityTaskManagerService atm,
                 @NonNull ActivityTaskSupervisor supervisor) {
             super(wm, atm, supervisor);
-            activity().createActivityWithComponent();
             mWindowState = Mockito.mock(WindowState.class);
         }
 
+        @Override
+        void onPostActivityCreation(@NonNull ActivityRecord activity) {
+            super.onPostActivityCreation(activity);
+            spyOn(activity.mAppCompatController.getAppCompatAspectRatioPolicy());
+        }
+
         void setIsLetterboxedForFixedOrientationAndAspectRatio(
                 boolean forFixedOrientationAndAspectRatio) {
             when(activity().top().mAppCompatController.getAppCompatAspectRatioPolicy()
diff --git a/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java b/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java
index 12f5714f..d223272 100644
--- a/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java
@@ -43,10 +43,10 @@
 import java.util.concurrent.Executor;
 
 /**
- * Tests for {@link DisplayRotationCompatPolicy}.
+ * Tests for {@link CameraStateMonitor}.
  *
  * Build/Install/Run:
- *  atest WmTests:DisplayRotationCompatPolicyTests
+ *  atest WmTests:CameraStateMonitorTests
  */
 @SmallTest
 @Presubmit
@@ -68,23 +68,14 @@
     private ActivityRecord mActivity;
     private Task mTask;
 
-    // Simulates a listener which will not react to the change on a particular activity.
-    private final FakeCameraCompatStateListener mNotInterestedListener =
-            new FakeCameraCompatStateListener(
-                    /*onCameraOpenedReturnValue=*/ false,
-                    /*simulateUnsuccessfulCloseOnce=*/ false);
     // Simulates a listener which will react to the change on a particular activity - for example
     // put the activity in a camera compat mode.
-    private final FakeCameraCompatStateListener mInterestedListener =
-            new FakeCameraCompatStateListener(
-                    /*onCameraOpenedReturnValue=*/ true,
-                    /*simulateUnsuccessfulCloseOnce=*/ false);
+    private final FakeCameraCompatStateListener mListener =
+            new FakeCameraCompatStateListener(/* simulateUnsuccessfulCloseOnce= */ false);
     // Simulates a listener which for some reason cannot process `onCameraClosed` event once it
     // first arrives - this means that the update needs to be postponed.
     private final FakeCameraCompatStateListener mListenerCannotClose =
-            new FakeCameraCompatStateListener(
-                    /*onCameraOpenedReturnValue=*/ true,
-                    /*simulateUnsuccessfulCloseOnce=*/ true);
+            new FakeCameraCompatStateListener(/* simulateUnsuccessfulCloseOnce= */ true);
 
     @Before
     public void setUp() throws Exception {
@@ -129,44 +120,31 @@
     @After
     public void tearDown() {
         // Remove all listeners.
-        mCameraStateMonitor.removeCameraStateListener(mNotInterestedListener);
-        mCameraStateMonitor.removeCameraStateListener(mInterestedListener);
+        mCameraStateMonitor.removeCameraStateListener(mListener);
         mCameraStateMonitor.removeCameraStateListener(mListenerCannotClose);
 
         // Reset the listener's state.
-        mNotInterestedListener.resetCounters();
-        mInterestedListener.resetCounters();
+        mListener.resetCounters();
         mListenerCannotClose.resetCounters();
     }
 
     @Test
     public void testOnCameraOpened_listenerAdded_notifiesCameraOpened() {
-        mCameraStateMonitor.addCameraStateListener(mNotInterestedListener);
+        mCameraStateMonitor.addCameraStateListener(mListener);
         mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
 
-        assertEquals(1, mNotInterestedListener.mOnCameraOpenedCounter);
+        assertEquals(1, mListener.mOnCameraOpenedCounter);
     }
 
     @Test
-    public void testOnCameraOpened_listenerReturnsFalse_doesNotNotifyCameraClosed() {
-        mCameraStateMonitor.addCameraStateListener(mNotInterestedListener);
-        // Listener returns false on `onCameraOpened`.
-        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
-
-        mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
-
-        assertEquals(0, mNotInterestedListener.mOnCameraClosedCounter);
-    }
-
-    @Test
-    public void testOnCameraOpened_listenerReturnsTrue_notifyCameraClosed() {
-        mCameraStateMonitor.addCameraStateListener(mInterestedListener);
+    public void testOnCameraOpened_cameraClosed_notifyCameraClosed() {
+        mCameraStateMonitor.addCameraStateListener(mListener);
         // Listener returns true on `onCameraOpened`.
         mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
 
         mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
 
-        assertEquals(1, mInterestedListener.mOnCameraClosedCounter);
+        assertEquals(1, mListener.mOnCameraClosedCounter);
     }
 
     @Test
@@ -182,32 +160,22 @@
 
     @Test
     public void testReconnectedToDifferentCamera_notifiesListener() {
-        mCameraStateMonitor.addCameraStateListener(mInterestedListener);
+        mCameraStateMonitor.addCameraStateListener(mListener);
         mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
         mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
         mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_2, TEST_PACKAGE_1);
 
-        assertEquals(2, mInterestedListener.mOnCameraOpenedCounter);
+        assertEquals(2, mListener.mOnCameraOpenedCounter);
     }
 
     @Test
     public void testDifferentAppConnectedToCamera_notifiesListener() {
-        mCameraStateMonitor.addCameraStateListener(mInterestedListener);
+        mCameraStateMonitor.addCameraStateListener(mListener);
         mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
         mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
         mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_2);
 
-        assertEquals(2, mInterestedListener.mOnCameraOpenedCounter);
-    }
-
-    @Test
-    public void testCameraAlreadyClosed_notifiesListenerOnce() {
-        mCameraStateMonitor.addCameraStateListener(mInterestedListener);
-        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
-        mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
-        mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
-
-        assertEquals(1, mInterestedListener.mOnCameraClosedCounter);
+        assertEquals(2, mListener.mOnCameraOpenedCounter);
     }
 
     private void configureActivity(@NonNull String packageName) {
@@ -232,7 +200,6 @@
         int mOnCameraOpenedCounter = 0;
         int mOnCameraClosedCounter = 0;
 
-        boolean mOnCameraOpenedReturnValue = true;
         private boolean mOnCameraClosedReturnValue = true;
 
         /**
@@ -242,17 +209,14 @@
          *                                      subsequent calls. This fake implementation tests the
          *                                      retry mechanism in {@link CameraStateMonitor}.
          */
-        FakeCameraCompatStateListener(boolean onCameraOpenedReturnValue,
-                boolean simulateUnsuccessfulCloseOnce) {
-            mOnCameraOpenedReturnValue = onCameraOpenedReturnValue;
+        FakeCameraCompatStateListener(boolean simulateUnsuccessfulCloseOnce) {
             mOnCameraClosedReturnValue = !simulateUnsuccessfulCloseOnce;
         }
 
         @Override
-        public boolean onCameraOpened(@NonNull ActivityRecord cameraActivity,
+        public void onCameraOpened(@NonNull ActivityRecord cameraActivity,
                 @NonNull String cameraId) {
             mOnCameraOpenedCounter++;
-            return mOnCameraOpenedReturnValue;
         }
 
         @Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
index b687042..07e95d8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
@@ -31,6 +31,7 @@
 import static android.util.DisplayMetrics.DENSITY_DEFAULT;
 
 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.DesktopModeBoundsCalculator.DESKTOP_MODE_INITIAL_BOUNDS_SCALE;
 import static com.android.server.wm.DesktopModeBoundsCalculator.DESKTOP_MODE_LANDSCAPE_APP_PADDING;
 import static com.android.server.wm.DesktopModeBoundsCalculator.calculateAspectRatio;
@@ -231,6 +232,56 @@
     }
 
     @Test
+    @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+            Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS})
+    public void testDefaultLandscapeBounds_landscapeDevice_userFullscreenOverride() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
+                LANDSCAPE_DISPLAY_BOUNDS);
+        final Task task = createTask(display, SCREEN_ORIENTATION_LANDSCAPE, true);
+
+        spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
+        doReturn(true).when(
+                        mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
+                .isUserFullscreenOverrideEnabled();
+
+        final int desiredWidth =
+                (int) (LANDSCAPE_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredHeight =
+                (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+            Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS})
+    public void testDefaultLandscapeBounds_landscapeDevice_systemFullscreenOverride() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
+                LANDSCAPE_DISPLAY_BOUNDS);
+        final Task task = createTask(display, SCREEN_ORIENTATION_LANDSCAPE, true);
+
+        spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
+        doReturn(true).when(
+                        mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
+                .isSystemOverrideToFullscreenEnabled();
+
+        final int desiredWidth =
+                (int) (LANDSCAPE_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredHeight =
+                (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
     @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
     public void testResizablePortraitBounds_landscapeDevice_resizable_portraitOrientation() {
         setupDesktopModeLaunchParamsModifier();
@@ -332,6 +383,56 @@
     }
 
     @Test
+    @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+            Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS})
+    public void testDefaultPortraitBounds_portraitDevice_userFullscreenOverride() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
+                PORTRAIT_DISPLAY_BOUNDS);
+        final Task task = createTask(display, SCREEN_ORIENTATION_PORTRAIT, true);
+
+        spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
+        doReturn(true).when(
+                        mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
+                .isUserFullscreenOverrideEnabled();
+
+        final int desiredWidth =
+                (int) (PORTRAIT_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredHeight =
+                (int) (PORTRAIT_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+            Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS})
+    public void testDefaultPortraitBounds_portraitDevice_systemFullscreenOverride() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
+                PORTRAIT_DISPLAY_BOUNDS);
+        final Task task = createTask(display, SCREEN_ORIENTATION_PORTRAIT, true);
+
+        spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
+        doReturn(true).when(
+                        mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
+                .isSystemOverrideToFullscreenEnabled();
+
+        final int desiredWidth =
+                (int) (PORTRAIT_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredHeight =
+                (int) (PORTRAIT_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
     @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
     public void testResizableLandscapeBounds_portraitDevice_resizable_landscapeOrientation() {
         setupDesktopModeLaunchParamsModifier();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
index 771e290..e57e36d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
@@ -59,6 +59,7 @@
 
         TestWindowContainer(WindowManagerService wm) {
             super(wm);
+            setVisibleRequested(true);
         }
 
         @Override
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 e2524a2..ddadbc4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
@@ -115,6 +115,17 @@
     }
 
     @Test
+    public void testPrimaryDisplayUnchanged_whenWindowingModeAlreadySet_NoFreeformSupport() {
+        mPrimaryDisplay.getDefaultTaskDisplayArea().setWindowingMode(
+                WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW);
+
+        mDisplayWindowSettings.applySettingsToDisplayLocked(mPrimaryDisplay);
+
+        assertEquals(WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW,
+                mPrimaryDisplay.getDefaultTaskDisplayArea().getWindowingMode());
+    }
+
+    @Test
     public void testPrimaryDisplayDefaultToFullscreen_HasFreeformSupport_NonPc_NoDesktopMode() {
         mWm.mAtmService.mSupportsFreeformWindowManagement = true;
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index 4a9d5c7..f339d29 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -218,16 +218,6 @@
     }
 
     @Test
-    public void testPerformDrag_NullDataToOtherUser() {
-        final WindowState otherUsersWindow =
-                createDropTargetWindow("Other user's window", 1 * UserHandle.PER_USER_RANGE);
-        doReturn(otherUsersWindow).when(mDisplayContent).getTouchableWinAtPointLocked(10, 10);
-
-        doDragAndDrop(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ, null, 10, 10);
-        mToken = otherUsersWindow.mClient.asBinder();
-    }
-
-    @Test
     public void testPrivateInterceptGlobalDragDropFlagChecksPermission() {
         DisplayPolicy policy = mDisplayContent.getDisplayPolicy();
         WindowManager.LayoutParams attrs = new WindowManager.LayoutParams();
diff --git a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
index e77c14a..eacb8e9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
@@ -82,12 +82,15 @@
     public void setUp() {
         DisplayInfo di = new DisplayInfo(mDisplayInfo);
         Mode defaultMode = di.getDefaultMode();
-        di.supportedModes = new Mode[] {
-                new Mode(1, defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), 90),
-                new Mode(2, defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), 70),
-                new Mode(LOW_MODE_ID,
-                        defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), 60),
-        };
+        Mode hiMode = new Mode(1,
+                defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), 90);
+        Mode midMode = new Mode(2,
+                defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), 70);
+        Mode lowMode = new Mode(LOW_MODE_ID,
+                defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), 60);
+
+        di.supportedModes = new Mode[] { hiMode, midMode };
+        di.appsSupportedModes = new Mode[] { hiMode, midMode, lowMode };
         di.defaultModeId = 1;
         mRefreshRatePolicy = new RefreshRatePolicy(mWm, di, mDenylist);
         when(mDisplayPolicy.getRefreshRatePolicy()).thenReturn(mRefreshRatePolicy);
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
index ffaa2d8..400fe8b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
@@ -24,6 +24,7 @@
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
@@ -45,6 +46,12 @@
 
 import java.util.function.Supplier;
 
+/**
+ * Test class for {@link Letterbox}.
+ * <p>
+ * Build/Install/Run:
+ * atest WmTests:LetterboxTest
+ */
 @SmallTest
 @Presubmit
 public class LetterboxTest {
@@ -53,21 +60,21 @@
     SurfaceControlMocker mSurfaces;
     SurfaceControl.Transaction mTransaction;
 
-    private boolean mAreCornersRounded = false;
-    private int mColor = Color.BLACK;
-    private boolean mHasWallpaperBackground = false;
-    private int mBlurRadius = 0;
-    private float mDarkScrimAlpha = 0.5f;
     private SurfaceControl mParentSurface = mock(SurfaceControl.class);
+    private AppCompatLetterboxOverrides mLetterboxOverrides;
 
     @Before
     public void setUp() throws Exception {
         mSurfaces = new SurfaceControlMocker();
+        mLetterboxOverrides =  mock(AppCompatLetterboxOverrides.class);
+        doReturn(false).when(mLetterboxOverrides).shouldLetterboxHaveRoundedCorners();
+        doReturn(Color.valueOf(Color.BLACK)).when(mLetterboxOverrides)
+                .getLetterboxBackgroundColor();
+        doReturn(false).when(mLetterboxOverrides).hasWallpaperBackgroundForLetterbox();
+        doReturn(0).when(mLetterboxOverrides).getLetterboxWallpaperBlurRadiusPx();
+        doReturn(0.5f).when(mLetterboxOverrides).getLetterboxWallpaperDarkScrimAlpha();
         mLetterbox = new Letterbox(mSurfaces, StubTransaction::new,
-                () -> mAreCornersRounded, () -> Color.valueOf(mColor),
-                () -> mHasWallpaperBackground, () -> mBlurRadius, () -> mDarkScrimAlpha,
-                mock(AppCompatReachabilityPolicy.class),
-                () -> mParentSurface);
+                mock(AppCompatReachabilityPolicy.class), mLetterboxOverrides, () -> mParentSurface);
         mTransaction = spy(StubTransaction.class);
     }
 
@@ -183,7 +190,8 @@
 
         verify(mTransaction).setColor(mSurfaces.top, new float[]{0, 0, 0});
 
-        mColor = Color.GREEN;
+        doReturn(Color.valueOf(Color.GREEN)).when(mLetterboxOverrides)
+                .getLetterboxBackgroundColor();
 
         assertTrue(mLetterbox.needsApplySurfaceChanges());
 
@@ -200,12 +208,12 @@
         verify(mTransaction).setAlpha(mSurfaces.top, 1.0f);
         assertFalse(mLetterbox.needsApplySurfaceChanges());
 
-        mHasWallpaperBackground = true;
+        doReturn(true).when(mLetterboxOverrides).hasWallpaperBackgroundForLetterbox();
 
         assertTrue(mLetterbox.needsApplySurfaceChanges());
 
         applySurfaceChanges();
-        verify(mTransaction).setAlpha(mSurfaces.fullWindowSurface, mDarkScrimAlpha);
+        verify(mTransaction).setAlpha(mSurfaces.fullWindowSurface, /* alpha */ 0.5f);
     }
 
     @Test
@@ -234,7 +242,7 @@
 
     @Test
     public void testApplySurfaceChanges_cornersRounded_surfaceFullWindowSurfaceCreated() {
-        mAreCornersRounded = true;
+        doReturn(true).when(mLetterboxOverrides).shouldLetterboxHaveRoundedCorners();
         mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
         applySurfaceChanges();
 
@@ -243,7 +251,7 @@
 
     @Test
     public void testApplySurfaceChanges_wallpaperBackground_surfaceFullWindowSurfaceCreated() {
-        mHasWallpaperBackground = true;
+        doReturn(true).when(mLetterboxOverrides).hasWallpaperBackgroundForLetterbox();
         mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
         applySurfaceChanges();
 
@@ -252,7 +260,7 @@
 
     @Test
     public void testNotIntersectsOrFullyContains_cornersRounded() {
-        mAreCornersRounded = true;
+        doReturn(true).when(mLetterboxOverrides).shouldLetterboxHaveRoundedCorners();
         mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(0, 0));
         applySurfaceChanges();
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 33df5d8..04997f8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -23,11 +23,9 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 
 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.assertNull;
-import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -36,8 +34,6 @@
 import android.content.ComponentName;
 import android.content.res.Resources;
 import android.graphics.Rect;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
 import android.view.InsetsSource;
 import android.view.InsetsState;
@@ -49,7 +45,6 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.R;
-import com.android.window.flags.Flags;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -128,7 +123,7 @@
 
         // Do not apply crop if taskbar is collapsed
         taskbar.setFrame(TASKBAR_COLLAPSED_BOUNDS);
-        assertNull(mController.getExpandedTaskbarOrNull(mainWindow));
+        assertNull(AppCompatUtils.getExpandedTaskbarOrNull(mainWindow));
 
         mLetterboxedPortraitTaskBounds.set(SCREEN_WIDTH / 4, SCREEN_HEIGHT / 4,
                 SCREEN_WIDTH - SCREEN_WIDTH / 4, SCREEN_HEIGHT - SCREEN_HEIGHT / 4);
@@ -150,7 +145,7 @@
 
         // Apply crop if taskbar is expanded
         taskbar.setFrame(TASKBAR_EXPANDED_BOUNDS);
-        assertNotNull(mController.getExpandedTaskbarOrNull(mainWindow));
+        assertNotNull(AppCompatUtils.getExpandedTaskbarOrNull(mainWindow));
 
         mLetterboxedPortraitTaskBounds.set(SCREEN_WIDTH / 4, 0, SCREEN_WIDTH - SCREEN_WIDTH / 4,
                 SCREEN_HEIGHT);
@@ -174,7 +169,7 @@
 
         // Apply crop if taskbar is expanded
         taskbar.setFrame(TASKBAR_EXPANDED_BOUNDS);
-        assertNotNull(mController.getExpandedTaskbarOrNull(mainWindow));
+        assertNotNull(AppCompatUtils.getExpandedTaskbarOrNull(mainWindow));
         // With SizeCompat scaling
         doReturn(true).when(mActivity).inSizeCompatMode();
         mainWindow.mInvGlobalScale = scaling;
@@ -296,106 +291,6 @@
     }
 
     @Test
-    public void testIsVerticalThinLetterboxed() {
-        // Vertical thin letterbox disabled
-        doReturn(-1).when(mActivity.mWmService.mAppCompatConfiguration)
-                .getThinLetterboxHeightPx();
-        final AppCompatReachabilityOverrides reachabilityOverrides = mActivity.mAppCompatController
-                .getAppCompatReachabilityOverrides();
-        assertFalse(reachabilityOverrides.isVerticalThinLetterboxed());
-        // Define a Task 100x100
-        final Task task = mock(Task.class);
-        doReturn(new Rect(0, 0, 100, 100)).when(task).getBounds();
-        doReturn(10).when(mActivity.mWmService.mAppCompatConfiguration)
-                .getThinLetterboxHeightPx();
-
-        // Vertical thin letterbox disabled without Task
-        doReturn(null).when(mActivity).getTask();
-        assertFalse(reachabilityOverrides.isVerticalThinLetterboxed());
-        // Assign a Task for the Activity
-        doReturn(task).when(mActivity).getTask();
-
-        // (task.width() - act.width()) / 2  = 5 < 10
-        doReturn(new Rect(5, 5, 95, 95)).when(mActivity).getBounds();
-        assertTrue(reachabilityOverrides.isVerticalThinLetterboxed());
-
-        // (task.width() - act.width()) / 2  = 10 = 10
-        doReturn(new Rect(10, 10, 90, 90)).when(mActivity).getBounds();
-        assertTrue(reachabilityOverrides.isVerticalThinLetterboxed());
-
-        // (task.width() - act.width()) / 2  = 11 > 10
-        doReturn(new Rect(11, 11, 89, 89)).when(mActivity).getBounds();
-        assertFalse(reachabilityOverrides.isVerticalThinLetterboxed());
-    }
-
-    @Test
-    public void testIsHorizontalThinLetterboxed() {
-        // Horizontal thin letterbox disabled
-        doReturn(-1).when(mActivity.mWmService.mAppCompatConfiguration)
-                .getThinLetterboxWidthPx();
-        final AppCompatReachabilityOverrides reachabilityOverrides = mActivity.mAppCompatController
-                .getAppCompatReachabilityOverrides();
-        assertFalse(reachabilityOverrides.isHorizontalThinLetterboxed());
-        // Define a Task 100x100
-        final Task task = mock(Task.class);
-        doReturn(new Rect(0, 0, 100, 100)).when(task).getBounds();
-        doReturn(10).when(mActivity.mWmService.mAppCompatConfiguration)
-                .getThinLetterboxWidthPx();
-
-        // Vertical thin letterbox disabled without Task
-        doReturn(null).when(mActivity).getTask();
-        assertFalse(reachabilityOverrides.isHorizontalThinLetterboxed());
-        // Assign a Task for the Activity
-        doReturn(task).when(mActivity).getTask();
-
-        // (task.height() - act.height()) / 2  = 5 < 10
-        doReturn(new Rect(5, 5, 95, 95)).when(mActivity).getBounds();
-        assertTrue(reachabilityOverrides.isHorizontalThinLetterboxed());
-
-        // (task.height() - act.height()) / 2  = 10 = 10
-        doReturn(new Rect(10, 10, 90, 90)).when(mActivity).getBounds();
-        assertTrue(reachabilityOverrides.isHorizontalThinLetterboxed());
-
-        // (task.height() - act.height()) / 2  = 11 > 10
-        doReturn(new Rect(11, 11, 89, 89)).when(mActivity).getBounds();
-        assertFalse(reachabilityOverrides.isHorizontalThinLetterboxed());
-    }
-
-    @Test
-    @EnableFlags(Flags.FLAG_DISABLE_THIN_LETTERBOXING_POLICY)
-    public void testAllowReachabilityForThinLetterboxWithFlagEnabled() {
-        final AppCompatReachabilityOverrides reachabilityOverrides =
-                mActivity.mAppCompatController.getAppCompatReachabilityOverrides();
-        spyOn(reachabilityOverrides);
-        doReturn(true).when(reachabilityOverrides).isVerticalThinLetterboxed();
-        assertFalse(reachabilityOverrides.allowVerticalReachabilityForThinLetterbox());
-        doReturn(true).when(reachabilityOverrides).isHorizontalThinLetterboxed();
-        assertFalse(reachabilityOverrides.allowHorizontalReachabilityForThinLetterbox());
-
-        doReturn(false).when(reachabilityOverrides).isVerticalThinLetterboxed();
-        assertTrue(reachabilityOverrides.allowVerticalReachabilityForThinLetterbox());
-        doReturn(false).when(reachabilityOverrides).isHorizontalThinLetterboxed();
-        assertTrue(reachabilityOverrides.allowHorizontalReachabilityForThinLetterbox());
-    }
-
-    @Test
-    @DisableFlags(Flags.FLAG_DISABLE_THIN_LETTERBOXING_POLICY)
-    public void testAllowReachabilityForThinLetterboxWithFlagDisabled() {
-        final AppCompatReachabilityOverrides reachabilityOverrides =
-                mActivity.mAppCompatController.getAppCompatReachabilityOverrides();
-        spyOn(reachabilityOverrides);
-        doReturn(true).when(reachabilityOverrides).isVerticalThinLetterboxed();
-        assertTrue(reachabilityOverrides.allowVerticalReachabilityForThinLetterbox());
-        doReturn(true).when(reachabilityOverrides).isHorizontalThinLetterboxed();
-        assertTrue(reachabilityOverrides.allowHorizontalReachabilityForThinLetterbox());
-
-        doReturn(false).when(reachabilityOverrides).isVerticalThinLetterboxed();
-        assertTrue(reachabilityOverrides.allowVerticalReachabilityForThinLetterbox());
-        doReturn(false).when(reachabilityOverrides).isHorizontalThinLetterboxed();
-        assertTrue(reachabilityOverrides.allowHorizontalReachabilityForThinLetterbox());
-    }
-
-    @Test
     public void testIsLetterboxEducationEnabled() {
         mController.isLetterboxEducationEnabled();
         verify(mAppCompatConfiguration).getIsEducationEnabled();
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index 33f7035..b95f621 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -1413,7 +1413,7 @@
                 Surface.ROTATION_0, taskSize, new Rect() /* contentInsets */,
                 new Rect() /* letterboxInsets*/, false /* isLowResolution */,
                 true /* isRealSnapshot */, WINDOWING_MODE_FULLSCREEN, 0 /* mSystemUiVisibility */,
-                false /* isTranslucent */, false /* hasImeSurface */);
+                false /* isTranslucent */, false /* hasImeSurface */, 0 /* uiMode */);
     }
 
     /**
diff --git a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
index 7ebf9ac..3fa38bf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
@@ -66,7 +66,6 @@
 
     private RefreshRatePolicy mPolicy;
     private HighRefreshRateDenylist mDenylist = mock(HighRefreshRateDenylist.class);
-    private FrameRateVote mTempFrameRateVote = new FrameRateVote();
 
     private static final FrameRateVote FRAME_RATE_VOTE_NONE = new FrameRateVote();
     private static final FrameRateVote FRAME_RATE_VOTE_DENY_LIST =
@@ -98,18 +97,14 @@
     @Before
     public void setUp() {
         Mode defaultMode = mDisplayInfo.getDefaultMode();
-        mDisplayInfo.supportedModes = new Mode[] {
-                new Mode(HI_MODE_ID,
-                        defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(),
-                        HI_REFRESH_RATE),
-                new Mode(MID_MODE_ID,
-                        defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(),
-                        MID_REFRESH_RATE),
-                new Mode(LOW_MODE_ID,
-                        defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(),
-                        LOW_REFRESH_RATE),
-        };
-        mDisplayInfo.appsSupportedModes = mDisplayInfo.supportedModes;
+        Mode hiMode = new Mode(HI_MODE_ID,
+                defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), HI_REFRESH_RATE);
+        Mode midMode = new Mode(MID_MODE_ID,
+                defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), MID_REFRESH_RATE);
+        Mode lowMode = new Mode(LOW_MODE_ID,
+                defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), LOW_REFRESH_RATE);
+        mDisplayInfo.supportedModes = new Mode[] { hiMode, midMode };
+        mDisplayInfo.appsSupportedModes = new Mode[] { hiMode, midMode, lowMode };
         mDisplayInfo.defaultModeId = HI_MODE_ID;
         mPolicy = new RefreshRatePolicy(mWm, mDisplayInfo, mDenylist);
     }
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 aa997ac..3148808 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -247,9 +247,9 @@
                 .build();
         mTask.addChild(translucentActivity);
 
-        spyOn(translucentActivity.mLetterboxUiController);
-        doReturn(true).when(translucentActivity.mLetterboxUiController)
-                .shouldShowLetterboxUi(any());
+        spyOn(translucentActivity.mAppCompatController.getAppCompatLetterboxPolicy());
+        doReturn(true).when(translucentActivity.mAppCompatController
+                .getAppCompatLetterboxPolicy()).shouldShowLetterboxUi(any());
 
         addWindowToActivity(translucentActivity);
         translucentActivity.mRootWindowContainer.performSurfacePlacement();
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 b92af87..1e39f0b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -139,7 +139,6 @@
     private ActivityTaskManagerService mAtmService;
     private WindowManagerService mWmService;
     private InputManagerService mImService;
-    private InputChannel mInputChannel;
     private Runnable mOnBeforeServicesCreated;
     /**
      * Spied {@link SurfaceControl.Transaction} class than can be used to verify calls.
@@ -326,12 +325,15 @@
 
         // InputManagerService
         mImService = mock(InputManagerService.class);
-        // InputChannel cannot be mocked because it may pass to InputEventReceiver.
-        final InputChannel[] inputChannels = InputChannel.openInputChannelPair(TAG);
-        inputChannels[0].dispose();
-        mInputChannel = inputChannels[1];
-        doReturn(mInputChannel).when(mImService).monitorInput(anyString(), anyInt());
-        doReturn(mInputChannel).when(mImService).createInputChannel(anyString());
+        // InputChannel cannot be mocked because it may be passed to InputEventReceiver.
+        Answer<InputChannel> newInputChannel = invocation -> {
+            String name = invocation.getArgument(0);
+            final InputChannel[] channels = InputChannel.openInputChannelPair(name);
+            channels[0].dispose();
+            return channels[1];
+        };
+        when(mImService.monitorInput(anyString(), anyInt())).thenAnswer(newInputChannel);
+        when(mImService.createInputChannel(anyString())).thenAnswer(newInputChannel);
 
         // StatusBarManagerInternal
         final StatusBarManagerInternal sbmi = mock(StatusBarManagerInternal.class);
@@ -464,9 +466,6 @@
         SurfaceAnimationThread.dispose();
         AnimationThread.dispose();
         UiThread.dispose();
-        if (mInputChannel != null) {
-            mInputChannel.dispose();
-        }
 
         tearDownLocalServices();
         // Reset priority booster because animation thread has been changed.
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 c53addc..6fd5faf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
@@ -131,6 +131,7 @@
         final Task adjacentRootTask = createTask(
                 mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
         adjacentRootTask.mCreatedByOrganizer = true;
+        createActivityRecord(adjacentRootTask);
         final TaskDisplayArea taskDisplayArea = rootTask.getDisplayArea();
         adjacentRootTask.setAdjacentTaskFragment(rootTask);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
index 84c0696..1e0cef0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
@@ -227,7 +227,7 @@
                     // disk.
                     false /* isLowResolution */,
                     mIsRealSnapshot, mWindowingMode, mSystemUiVisibility, mIsTranslucent,
-                    false /* hasImeSurface */);
+                    false /* hasImeSurface */, 0 /* uiMode */);
         }
     }
 }
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 4b0668f..d62c626 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -270,12 +270,6 @@
     }
 
     @Override
-    public boolean performHapticFeedback(int uid, String packageName, int effectId, String reason,
-            int flags, int privFlags) {
-        return false;
-    }
-
-    @Override
     public void keepScreenOnStartedLw() {
     }
 
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 cbf17c4..a0641cd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java
@@ -22,8 +22,11 @@
 import static android.view.Surface.ROTATION_0;
 import static android.view.Surface.ROTATION_90;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
 import static org.mockito.Mockito.clearInvocations;
 
+import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.annotation.NonNull;
@@ -207,6 +210,25 @@
         });
     }
 
+    @EnableFlags(com.android.window.flags.Flags.FLAG_RESPECT_NON_TOP_VISIBLE_FIXED_ORIENTATION)
+    @Test
+    public void testNotRunStrategyToTranslucentActivitiesIfRespectOrientation() {
+        runTestScenario(robot -> robot.transparentActivity(ta -> ta.applyOnActivity((a) -> {
+            a.configureTopActivityIgnoreOrientationRequest(false);
+            // The translucent activity is SCREEN_ORIENTATION_PORTRAIT.
+            ta.launchTransparentActivityInTask();
+            // Though TransparentPolicyState will be started, it won't be considered as running.
+            ta.checkTopActivityTransparentPolicyStateIsRunning(/* running */ false);
+
+            // If the display changes to ignore orientation request, e.g. unfold, the policy should
+            // take effect.
+            a.configureTopActivityIgnoreOrientationRequest(true);
+            ta.checkTopActivityTransparentPolicyStateIsRunning(/* running */ true);
+            ta.setDisplayContentBounds(0, 0, 900, 1800);
+            ta.checkTopActivityHasInheritedBoundsFrom(/* fromTop */ 1);
+        })), /* displayWidth */ 500,  /* displayHeight */ 1000);
+    }
+
     @Test
     public void testTranslucentActivitiesDontGoInSizeCompatMode() {
         runTestScenario((robot) -> {
@@ -343,6 +365,12 @@
             activity().createNewTaskWithBaseActivity();
         }
 
+        @Override
+        void onPostActivityCreation(@NonNull ActivityRecord activity) {
+            super.onPostActivityCreation(activity);
+            spyOn(activity.mAppCompatController.getTransparentPolicy());
+        }
+
         void transparentActivity(@NonNull Consumer<AppCompatTransparentActivityRobot> consumer) {
             consumer.accept(mTransparentActivityRobot);
         }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 2b611b7..18255b8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -1488,7 +1488,7 @@
     @Test
     public void testReorderWithParents() {
         /*
-                  root
+                  default TDA
                ____|______
                |         |
            firstTda    secondTda
@@ -1496,10 +1496,12 @@
          firstRootTask    secondRootTask
 
          */
-        final TaskDisplayArea firstTaskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea();
-        final TaskDisplayArea secondTaskDisplayArea = createTaskDisplayArea(
-                mDisplayContent, mRootWindowContainer.mWmService, "TestTaskDisplayArea",
+        final TaskDisplayArea firstTaskDisplayArea = createTaskDisplayArea(
+                mDisplayContent, mRootWindowContainer.mWmService, "FirstTaskDisplayArea",
                 FEATURE_VENDOR_FIRST);
+        final TaskDisplayArea secondTaskDisplayArea = createTaskDisplayArea(
+                mDisplayContent, mRootWindowContainer.mWmService, "SecondTaskDisplayArea",
+                FEATURE_VENDOR_FIRST + 1);
         final Task firstRootTask = firstTaskDisplayArea.createRootTask(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
         final Task secondRootTask = secondTaskDisplayArea.createRootTask(
@@ -1508,9 +1510,6 @@
                 .setTask(firstRootTask).build();
         final ActivityRecord secondActivity = new ActivityBuilder(mAtm)
                 .setTask(secondRootTask).build();
-        // This assertion is just a defense to ensure that firstRootTask is not the top most
-        // by default
-        assertThat(mDisplayContent.getTopRootTask()).isEqualTo(secondRootTask);
         WindowContainerTransaction wct = new WindowContainerTransaction();
 
         // Reorder to top
@@ -1533,6 +1532,67 @@
     }
 
     @Test
+    public void testReorderDisplayArea() {
+        /*
+                  defaultTda
+               ____|______
+               |         |
+           firstTda    secondTda
+               |             |
+         firstRootTask    secondRootTask
+
+         */
+        final TaskDisplayArea firstTaskDisplayArea = createTaskDisplayArea(
+                mDisplayContent, mRootWindowContainer.mWmService, "FirstTaskDisplayArea",
+                FEATURE_VENDOR_FIRST);
+        final TaskDisplayArea secondTaskDisplayArea = createTaskDisplayArea(
+                mDisplayContent, mRootWindowContainer.mWmService, "SecondTaskDisplayArea",
+                FEATURE_VENDOR_FIRST + 1);
+        final Task firstRootTask = firstTaskDisplayArea.createRootTask(
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
+        final Task secondRootTask = secondTaskDisplayArea.createRootTask(
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
+        final ActivityRecord firstActivity = new ActivityBuilder(mAtm)
+                .setTask(firstRootTask).build();
+        final ActivityRecord secondActivity = new ActivityBuilder(mAtm)
+                .setTask(secondRootTask).build();
+
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+
+        // Reorder to top
+        wct.reorder(firstTaskDisplayArea.mRemoteToken.toWindowContainerToken(), true /* onTop */,
+                true /* includingParents */);
+        mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
+        assertThat(mDisplayContent.getTopRootTask()).isEqualTo(firstRootTask);
+
+        // Reorder to bottom
+        wct.reorder(firstTaskDisplayArea.mRemoteToken.toWindowContainerToken(), false /* onTop */,
+                true /* includingParents */);
+        mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
+        assertThat(mDisplayContent.getBottomMostTask()).isEqualTo(firstRootTask);
+    }
+
+    @Test
+    public void testReparentDisplayAreaUnsupported() {
+        final TaskDisplayArea firstTaskDisplayArea = createTaskDisplayArea(
+                mDisplayContent, mRootWindowContainer.mWmService, "FirstTaskDisplayArea",
+                FEATURE_VENDOR_FIRST);
+        final TaskDisplayArea secondTaskDisplayArea = createTaskDisplayArea(
+                mDisplayContent, mRootWindowContainer.mWmService, "SecondTaskDisplayArea",
+                FEATURE_VENDOR_FIRST + 1);
+
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        wct.reparent(firstTaskDisplayArea.mRemoteToken.toWindowContainerToken(),
+                secondTaskDisplayArea.mRemoteToken.toWindowContainerToken(),
+                true /* onTop */
+        );
+
+        assertThrows(UnsupportedOperationException.class, () ->
+                mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct)
+        );
+    }
+
+    @Test
     public void testAppearDeferThenVanish() {
         final ITaskOrganizer organizer = registerMockOrganizer();
         final Task rootTask = createRootTask();
diff --git a/telephony/java/android/service/euicc/EuiccService.java b/telephony/java/android/service/euicc/EuiccService.java
index 5524541..a01a720 100644
--- a/telephony/java/android/service/euicc/EuiccService.java
+++ b/telephony/java/android/service/euicc/EuiccService.java
@@ -267,6 +267,17 @@
             "android.service.euicc.extra.RESOLUTION_CONFIRMATION_CODE_RETRIED";
 
     /**
+     * Bundle key for the {@code resolvedBundle} passed to {@link #onDownloadSubscription(
+     * int, int, DownloadableSubscription, boolean, boolean, Bundle)}. The value is a
+     * {@link String} for the package name of the app calling the
+     * {@link EuiccManager#downloadSubscription(int, DownloadableSubscription, PendingIntent)} API.
+     * This is to be used by LPA to determine the app that is requesting the download.
+     *
+     * @hide
+     */
+    public static final String EXTRA_PACKAGE_NAME = "android.service.euicc.extra.PACKAGE_NAME";
+
+    /**
      * Intent extra set for resolution requests containing an int indicating the current card Id.
      */
     public static final String EXTRA_RESOLUTION_CARD_ID =
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index b9a001d..afd5720 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9992,6 +9992,16 @@
     @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
     public static final String KEY_SATELLITE_ESOS_SUPPORTED_BOOL = "satellite_esos_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.
+     *
+     * @hide
+     */
+    public static final String KEY_SATELLITE_NIDD_APN_NAME_STRING =
+            "satellite_nidd_apn_name_string";
+
     /** @hide */
     @IntDef({
             CARRIER_ROAMING_NTN_CONNECT_AUTOMATIC,
@@ -11224,6 +11234,7 @@
         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.putString(KEY_SATELLITE_NIDD_APN_NAME_STRING, "");
         sDefaults.putInt(KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT, 0);
         sDefaults.putInt(KEY_CARRIER_ROAMING_NTN_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_INT,
                 SatelliteManager.EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911);
diff --git a/telephony/java/android/telephony/PcoData.java b/telephony/java/android/telephony/PcoData.java
index 39e4f2f..3cc32c6 100644
--- a/telephony/java/android/telephony/PcoData.java
+++ b/telephony/java/android/telephony/PcoData.java
@@ -19,6 +19,8 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.internal.telephony.uicc.IccUtils;
+
 import java.util.Arrays;
 import java.util.Objects;
 
@@ -84,8 +86,8 @@
 
     @Override
     public String toString() {
-        return "PcoData(" + cid + ", " + bearerProto + ", " + pcoId + ", contents[" +
-                contents.length + "])";
+        return "PcoData(" + cid + ", " + bearerProto + ", " + pcoId + " "
+                + IccUtils.bytesToHexString(contents) + ")";
     }
 
     @Override
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index db167c0..127bbff 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -1211,6 +1211,8 @@
                     .append(", mIsDataRoamingFromRegistration=")
                     .append(mIsDataRoamingFromRegistration)
                     .append(", mIsIwlanPreferred=").append(mIsIwlanPreferred)
+                    .append(", mIsUsingNonTerrestrialNetwork=")
+                    .append(isUsingNonTerrestrialNetwork())
                     .append("}").toString();
         }
     }
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index dea10b70..f0850af 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -1177,6 +1177,16 @@
      */
     public static final String SATELLITE_ESOS_SUPPORTED = SimInfo.COLUMN_SATELLITE_ESOS_SUPPORTED;
 
+    /**
+     * TelephonyProvider column name for satellite provisioned status. The value of this
+     * column is set based on whether carrier roaming NB-IOT satellite service is provisioned or
+     * not. By default, it's disabled.
+     *
+     * @hide
+     */
+    public static final String IS_SATELLITE_PROVISIONED_FOR_NON_IP_DATAGRAM =
+            SimInfo.COLUMN_IS_SATELLITE_PROVISIONED_FOR_NON_IP_DATAGRAM;
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"USAGE_SETTING_"},
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 0bd9270..e657d7f 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -254,7 +254,6 @@
      */
     public static final String KEY_PROVISION_SATELLITE_TOKENS = "provision_satellite";
 
-
     /**
      * The request was successfully processed.
      */
@@ -412,6 +411,14 @@
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_LOCATION_NOT_AVAILABLE = 26;
 
+    /**
+     * Emergency call is in progress.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public static final int SATELLITE_RESULT_EMERGENCY_CALL_IN_PROGRESS = 27;
+
     /** @hide */
     @IntDef(prefix = {"SATELLITE_RESULT_"}, value = {
             SATELLITE_RESULT_SUCCESS,
@@ -440,7 +447,8 @@
             SATELLITE_RESULT_ILLEGAL_STATE,
             SATELLITE_RESULT_MODEM_TIMEOUT,
             SATELLITE_RESULT_LOCATION_DISABLED,
-            SATELLITE_RESULT_LOCATION_NOT_AVAILABLE
+            SATELLITE_RESULT_LOCATION_NOT_AVAILABLE,
+            SATELLITE_RESULT_EMERGENCY_CALL_IN_PROGRESS
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface SatelliteResult {}
@@ -2634,7 +2642,7 @@
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
     public void requestProvisionSubscriberIds(@NonNull @CallbackExecutor Executor executor,
-            @NonNull OutcomeReceiver<List<ProvisionSubscriberId>, SatelliteException> callback) {
+            @NonNull OutcomeReceiver<List<SatelliteSubscriberInfo>, SatelliteException> callback) {
         Objects.requireNonNull(executor);
         Objects.requireNonNull(callback);
 
@@ -2646,10 +2654,10 @@
                     protected void onReceiveResult(int resultCode, Bundle resultData) {
                         if (resultCode == SATELLITE_RESULT_SUCCESS) {
                             if (resultData.containsKey(KEY_REQUEST_PROVISION_SUBSCRIBER_ID_TOKEN)) {
-                                List<ProvisionSubscriberId> list =
+                                List<SatelliteSubscriberInfo> list =
                                         resultData.getParcelableArrayList(
                                                 KEY_REQUEST_PROVISION_SUBSCRIBER_ID_TOKEN,
-                                                ProvisionSubscriberId.class);
+                                                SatelliteSubscriberInfo.class);
                                 executor.execute(() -> Binder.withCleanCallingIdentity(() ->
                                         callback.onResult(list)));
                             } else {
@@ -2734,9 +2742,9 @@
     }
 
     /**
-     * Deliver the list of provisioned satellite subscriber ids.
+     * Deliver the list of provisioned satellite subscriber infos.
      *
-     * @param list List of ProvisionSubscriberId.
+     * @param list The list of provisioned satellite subscriber infos.
      * @param executor The executor on which the callback will be called.
      * @param callback The callback object to which the result will be delivered.
      *
@@ -2745,7 +2753,7 @@
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
-    public void provisionSatellite(@NonNull List<ProvisionSubscriberId> list,
+    public void provisionSatellite(@NonNull List<SatelliteSubscriberInfo> list,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
         Objects.requireNonNull(executor);
diff --git a/telephony/java/android/telephony/satellite/ProvisionSubscriberId.aidl b/telephony/java/android/telephony/satellite/SatelliteSubscriberInfo.aidl
similarity index 94%
rename from telephony/java/android/telephony/satellite/ProvisionSubscriberId.aidl
rename to telephony/java/android/telephony/satellite/SatelliteSubscriberInfo.aidl
index fe46db8..992c9ae 100644
--- a/telephony/java/android/telephony/satellite/ProvisionSubscriberId.aidl
+++ b/telephony/java/android/telephony/satellite/SatelliteSubscriberInfo.aidl
@@ -16,4 +16,4 @@
 
 package android.telephony.satellite;
 
-parcelable ProvisionSubscriberId;
+parcelable SatelliteSubscriberInfo;
diff --git a/telephony/java/android/telephony/satellite/ProvisionSubscriberId.java b/telephony/java/android/telephony/satellite/SatelliteSubscriberInfo.java
similarity index 83%
rename from telephony/java/android/telephony/satellite/ProvisionSubscriberId.java
rename to telephony/java/android/telephony/satellite/SatelliteSubscriberInfo.java
index 3e6f743..f26219b 100644
--- a/telephony/java/android/telephony/satellite/ProvisionSubscriberId.java
+++ b/telephony/java/android/telephony/satellite/SatelliteSubscriberInfo.java
@@ -26,7 +26,7 @@
 import java.util.Objects;
 
 /**
- * ProvisionSubscriberId
+ * SatelliteSubscriberInfo
  *
  * Satellite Gateway client will use these subscriber ids to register with satellite gateway service
  * which identify user subscription with unique subscriber ids. These subscriber ids can be any
@@ -35,7 +35,7 @@
  * @hide
  */
 @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
-public final class ProvisionSubscriberId implements Parcelable {
+public final class SatelliteSubscriberInfo implements Parcelable {
     /** provision subscriberId */
     @NonNull
     private String mSubscriberId;
@@ -49,14 +49,14 @@
     /**
      * @hide
      */
-    public ProvisionSubscriberId(@NonNull String subscriberId, @NonNull int carrierId,
+    public SatelliteSubscriberInfo(@NonNull String subscriberId, @NonNull int carrierId,
             @NonNull String niddApn) {
         this.mCarrierId = carrierId;
         this.mSubscriberId = subscriberId;
         this.mNiddApn = niddApn;
     }
 
-    private ProvisionSubscriberId(Parcel in) {
+    private SatelliteSubscriberInfo(Parcel in) {
         readFromParcel(in);
     }
 
@@ -72,16 +72,16 @@
     }
 
     @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
-    public static final @android.annotation.NonNull Creator<ProvisionSubscriberId> CREATOR =
-            new Creator<ProvisionSubscriberId>() {
+    public static final @android.annotation.NonNull Creator<SatelliteSubscriberInfo> CREATOR =
+            new Creator<SatelliteSubscriberInfo>() {
                 @Override
-                public ProvisionSubscriberId createFromParcel(Parcel in) {
-                    return new ProvisionSubscriberId(in);
+                public SatelliteSubscriberInfo createFromParcel(Parcel in) {
+                    return new SatelliteSubscriberInfo(in);
                 }
 
                 @Override
-                public ProvisionSubscriberId[] newArray(int size) {
-                    return new ProvisionSubscriberId[size];
+                public SatelliteSubscriberInfo[] newArray(int size) {
+                    return new SatelliteSubscriberInfo[size];
                 }
             };
 
@@ -148,7 +148,7 @@
     public boolean equals(Object o) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
-        ProvisionSubscriberId that = (ProvisionSubscriberId) o;
+        SatelliteSubscriberInfo that = (SatelliteSubscriberInfo) o;
         return mSubscriberId.equals(that.mSubscriberId) && mCarrierId
                 == that.mCarrierId && mNiddApn.equals(that.mNiddApn);
     }
diff --git a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
index b82396e..e66a082 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
@@ -206,77 +206,6 @@
     void stopSendingSatellitePointingInfo(in IIntegerConsumer resultCallback);
 
     /**
-     * Provision the device with a satellite provider.
-     * This is needed if the provider allows dynamic registration.
-     * Once provisioned, ISatelliteListener#onSatelliteProvisionStateChanged should report true.
-     *
-     * @param token The token to be used as a unique identifier for provisioning with satellite
-     *              gateway.
-     * @param provisionData Data from the provisioning app that can be used by provisioning server
-     * @param resultCallback The callback to receive the error code result of the operation.
-     *
-     * Valid result codes returned:
-     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
-     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
-     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
-     *   SatelliteResult:SATELLITE_RESULT_NETWORK_ERROR
-     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
-     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
-     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
-     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
-     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
-     *   SatelliteResult:SATELLITE_RESULT_REQUEST_ABORTED
-     *   SatelliteResult:SATELLITE_RESULT_NETWORK_TIMEOUT
-     */
-    void provisionSatelliteService(in String token, in byte[] provisionData,
-            in IIntegerConsumer resultCallback);
-
-    /**
-     * Deprovision the device with the satellite provider.
-     * This is needed if the provider allows dynamic registration.
-     * Once deprovisioned, ISatelliteListener#onSatelliteProvisionStateChanged should report false.
-     *
-     * @param token The token of the device/subscription to be deprovisioned.
-     * @param resultCallback The callback to receive the error code result of the operation.
-     *
-     * Valid result codes returned:
-     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
-     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
-     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
-     *   SatelliteResult:SATELLITE_RESULT_NETWORK_ERROR
-     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
-     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
-     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
-     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
-     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
-     *   SatelliteResult:SATELLITE_RESULT_REQUEST_ABORTED
-     *   SatelliteResult:SATELLITE_RESULT_NETWORK_TIMEOUT
-     */
-    void deprovisionSatelliteService(in String token, in IIntegerConsumer resultCallback);
-
-    /**
-     * Request to get whether this device is provisioned with a satellite provider.
-     *
-     * @param resultCallback The callback to receive the error code result of the operation.
-     *                       This must only be sent when the result is not
-     *                       SatelliteResult#SATELLITE_RESULT_SUCCESS.
-     * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to
-     *                 receive whether this device is provisioned with a satellite provider.
-     *
-     * Valid result codes returned:
-     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
-     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
-     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
-     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
-     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
-     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
-     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
-     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
-     */
-    void requestIsSatelliteProvisioned(in IIntegerConsumer resultCallback,
-            in IBooleanConsumer callback);
-
-    /**
      * Poll the pending datagrams to be received over satellite.
      * The satellite service should check if there are any pending datagrams to be received over
      * satellite and report them via ISatelliteListener#onSatelliteDatagramsReceived.
diff --git a/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl b/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
index b4eb15fd..3f2fce2 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
@@ -28,13 +28,6 @@
  */
 oneway interface ISatelliteListener {
     /**
-     * Indicates that the satellite provision state has changed.
-     *
-     * @param provisioned True means the service is provisioned and false means it is not.
-     */
-    void onSatelliteProvisionStateChanged(in boolean provisioned);
-
-    /**
      * Indicates that new datagrams have been received on the device.
      *
      * @param datagram New datagram that was received.
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
index d8b4974..c50e469 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
@@ -142,32 +142,6 @@
         }
 
         @Override
-        public void provisionSatelliteService(String token, byte[] provisionData,
-                IIntegerConsumer resultCallback) throws RemoteException {
-            executeMethodAsync(
-                    () -> SatelliteImplBase.this
-                            .provisionSatelliteService(token, provisionData, resultCallback),
-                    "provisionSatelliteService");
-        }
-
-        @Override
-        public void deprovisionSatelliteService(String token, IIntegerConsumer resultCallback)
-                throws RemoteException {
-            executeMethodAsync(
-                    () -> SatelliteImplBase.this.deprovisionSatelliteService(token, resultCallback),
-                    "deprovisionSatelliteService");
-        }
-
-        @Override
-        public void requestIsSatelliteProvisioned(IIntegerConsumer resultCallback,
-                IBooleanConsumer callback) throws RemoteException {
-            executeMethodAsync(
-                    () -> SatelliteImplBase.this
-                            .requestIsSatelliteProvisioned(resultCallback, callback),
-                    "requestIsSatelliteProvisioned");
-        }
-
-        @Override
         public void pollPendingSatelliteDatagrams(IIntegerConsumer resultCallback)
                 throws RemoteException {
             executeMethodAsync(
@@ -487,85 +461,6 @@
     }
 
     /**
-     * Provision the device with a satellite provider.
-     * This is needed if the provider allows dynamic registration.
-     * Once provisioned, ISatelliteListener#onSatelliteProvisionStateChanged should report true.
-     *
-     * @param token The token to be used as a unique identifier for provisioning with satellite
-     *              gateway.
-     * @param provisionData Data from the provisioning app that can be used by provisioning
-     *                      server
-     * @param resultCallback The callback to receive the error code result of the operation.
-     *
-     * Valid result codes returned:
-     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
-     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
-     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
-     *   SatelliteResult:SATELLITE_RESULT_NETWORK_ERROR
-     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
-     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
-     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
-     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
-     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
-     *   SatelliteResult:SATELLITE_RESULT_REQUEST_ABORTED
-     *   SatelliteResult:SATELLITE_RESULT_NETWORK_TIMEOUT
-     */
-    public void provisionSatelliteService(@NonNull String token, @NonNull byte[] provisionData,
-            @NonNull IIntegerConsumer resultCallback) {
-        // stub implementation
-    }
-
-    /**
-     * Deprovision the device with the satellite provider.
-     * This is needed if the provider allows dynamic registration.
-     * Once deprovisioned, ISatelliteListener#onSatelliteProvisionStateChanged should report false.
-     *
-     * @param token The token of the device/subscription to be deprovisioned.
-     * @param resultCallback The callback to receive the error code result of the operation.
-     *
-     * Valid result codes returned:
-     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
-     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
-     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
-     *   SatelliteResult:SATELLITE_RESULT_NETWORK_ERROR
-     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
-     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
-     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
-     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
-     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
-     *   SatelliteResult:SATELLITE_RESULT_REQUEST_ABORTED
-     *   SatelliteResult:SATELLITE_RESULT_NETWORK_TIMEOUT
-     */
-    public void deprovisionSatelliteService(@NonNull String token,
-            @NonNull IIntegerConsumer resultCallback) {
-        // stub implementation
-    }
-
-    /**
-     * Request to get whether this device is provisioned with a satellite provider.
-     *
-     * @param resultCallback The callback to receive the error code result of the operation.
-     *                       This must only be sent when the result is not
-     *                       SatelliteResult#SATELLITE_RESULT_SUCCESS.
-     * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to
-     *                 receive whether this device is provisioned with a satellite provider.
-     *
-     * Valid result codes returned:
-     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
-     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
-     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
-     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
-     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
-     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
-     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
-     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
-     */
-    public void requestIsSatelliteProvisioned(@NonNull IIntegerConsumer resultCallback,
-            @NonNull IBooleanConsumer callback) {
-        // stub implementation
-    }
-
-    /**
      * Poll the pending datagrams to be received over satellite.
      * The satellite service should check if there are any pending datagrams to be received over
      * satellite and report them via ISatelliteListener#onSatelliteDatagramsReceived.
diff --git a/telephony/java/android/telephony/satellite/stub/ProvisionSubscriberId.aidl b/telephony/java/android/telephony/satellite/stub/SatelliteSubscriberInfo.aidl
similarity index 95%
rename from telephony/java/android/telephony/satellite/stub/ProvisionSubscriberId.aidl
rename to telephony/java/android/telephony/satellite/stub/SatelliteSubscriberInfo.aidl
index 460de8c..fb44f87 100644
--- a/telephony/java/android/telephony/satellite/stub/ProvisionSubscriberId.aidl
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteSubscriberInfo.aidl
@@ -19,7 +19,7 @@
 /**
  * {@hide}
  */
-parcelable ProvisionSubscriberId {
+parcelable SatelliteSubscriberInfo {
     /** provision subscriberId */
     String subscriberId;
 
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 3dbda7a..8919703 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -78,7 +78,7 @@
 import android.telephony.satellite.NtnSignalStrength;
 import android.telephony.satellite.SatelliteCapabilities;
 import android.telephony.satellite.SatelliteDatagram;
-import android.telephony.satellite.ProvisionSubscriberId;
+import android.telephony.satellite.SatelliteSubscriberInfo;
 import com.android.ims.internal.IImsServiceFeatureCallback;
 import com.android.internal.telephony.CellNetworkScanResult;
 import com.android.internal.telephony.IBooleanConsumer;
@@ -3007,16 +3007,18 @@
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void setDeviceAlignedWithSatellite(int subId, in boolean isAligned);
+    void setDeviceAlignedWithSatellite(int subId, boolean isAligned);
 
     /**
      * This API can be used by only CTS to update satellite vendor service package name.
      *
      * @param servicePackageName The package name of the satellite vendor service.
+     * @param provisioned Whether satellite should be provisioned or not.
+     *
      * @return {@code true} if the satellite vendor service is set successfully,
      * {@code false} otherwise.
      */
-    boolean setSatelliteServicePackageName(in String servicePackageName);
+    boolean setSatelliteServicePackageName(in String servicePackageName, in String provisioned);
 
     /**
      * This API can be used by only CTS to update satellite gateway service package name.
@@ -3426,13 +3428,13 @@
     void requestIsProvisioned(in String satelliteSubscriberId, in ResultReceiver result);
 
     /**
-     * Deliver the list of provisioned satellite subscriber ids.
+     * Deliver the list of provisioned satellite subscriber infos.
      *
-     * @param list List of provisioned satellite subscriber ids.
+     * @param list The list of provisioned satellite subscriber infos.
      * @param result The result receiver that returns whether deliver success or fail.
      * @hide
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void provisionSatellite(in List<ProvisionSubscriberId> list, in ResultReceiver result);
+    void provisionSatellite(in List<SatelliteSubscriberInfo> list, in ResultReceiver result);
 }
diff --git a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
index f518d53..9676bd7 100644
--- a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
+++ b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
@@ -967,7 +967,7 @@
         byte[] serializedFplmns = new byte[dataLength];
         int offset = 0;
         for (String fplmn : fplmns) {
-            if (offset >= dataLength) break;
+            if (offset + FPLMN_BYTE_SIZE > dataLength) break;
             stringToBcdPlmn(fplmn, serializedFplmns, offset);
             offset += FPLMN_BYTE_SIZE;
         }
diff --git a/test-mock/Android.bp b/test-mock/Android.bp
index 5976657..71f3033 100644
--- a/test-mock/Android.bp
+++ b/test-mock/Android.bp
@@ -47,6 +47,10 @@
     compile_dex: true,
     default_to_stubs: true,
     dist_group: "android",
+
+    // This module cannot generate stubs from the api signature files as stubs depends on the
+    // private APIs, which are not visible in the api signature files.
+    build_from_text_stub: false,
 }
 
 java_library {
diff --git a/tests/FlickerTests/ActivityEmbedding/Android.bp b/tests/FlickerTests/ActivityEmbedding/Android.bp
index e09fbf6..c681ce9 100644
--- a/tests/FlickerTests/ActivityEmbedding/Android.bp
+++ b/tests/FlickerTests/ActivityEmbedding/Android.bp
@@ -24,6 +24,9 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
+////////////////////////////////////////////////////////////////////////////////
+// Begin to cleanup after CL merges
+
 filegroup {
     name: "FlickerTestsOtherCommon-src",
     srcs: ["src/**/ActivityEmbeddingTestBase.kt"],
@@ -82,3 +85,123 @@
         ":FlickerTestsOtherCommon-src",
     ],
 }
+
+// End to cleanup after CL merges
+////////////////////////////////////////////////////////////////////////////////
+
+android_test {
+    name: "FlickerTestsActivityEmbedding",
+    defaults: ["FlickerTestsDefault"],
+    manifest: "AndroidManifest.xml",
+    package_name: "com.android.server.wm.flicker",
+    instrumentation_target_package: "com.android.server.wm.flicker",
+    test_config_template: "AndroidTestTemplate.xml",
+    srcs: ["src/**/*"],
+    static_libs: [
+        "FlickerTestsBase",
+        "FlickerTestsOtherCommon",
+    ],
+    data: ["trace_config/*"],
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Begin breakdowns for FlickerTestsActivityEmbedding module
+
+test_module_config {
+    name: "FlickerTestsActivityEmbedding-CatchAll",
+    base: "FlickerTestsActivityEmbedding",
+    exclude_filters: [
+        "com.android.server.wm.flicker.activityembedding.close.CloseSecondaryActivityInSplitTest",
+        "com.android.server.wm.flicker.activityembedding.layoutchange.HorizontalSplitChangeRatioTest",
+        "com.android.server.wm.flicker.activityembedding.open.MainActivityStartsSecondaryWithAlwaysExpandTest",
+        "com.android.server.wm.flicker.activityembedding.open.OpenActivityEmbeddingPlaceholderSplitTest",
+        "com.android.server.wm.flicker.activityembedding.open.OpenActivityEmbeddingSecondaryToSplitTest",
+        "com.android.server.wm.flicker.activityembedding.open.OpenThirdActivityOverSplitTest",
+        "com.android.server.wm.flicker.activityembedding.open.OpenTrampolineActivityTest",
+        "com.android.server.wm.flicker.activityembedding.pip.SecondaryActivityEnterPipTest",
+        "com.android.server.wm.flicker.activityembedding.rotation.RotateSplitNoChangeTest",
+        "com.android.server.wm.flicker.activityembedding.rtl.RTLStartSecondaryWithPlaceholderTest",
+        "com.android.server.wm.flicker.activityembedding.splitscreen.EnterSystemSplitTest",
+    ],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "FlickerTestsActivityEmbedding-Close-CloseSecondaryActivityInSplitTest",
+    base: "FlickerTestsActivityEmbedding",
+    include_filters: ["com.android.server.wm.flicker.activityembedding.close.CloseSecondaryActivityInSplitTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "FlickerTestsActivityEmbedding-LayoutChange-HorizontalSplitChangeRatioTest",
+    base: "FlickerTestsActivityEmbedding",
+    include_filters: ["com.android.server.wm.flicker.activityembedding.layoutchange.HorizontalSplitChangeRatioTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "FlickerTestsActivityEmbedding-Open-MainActivityStartsSecondaryWithAlwaysExpandTest",
+    base: "FlickerTestsActivityEmbedding",
+    include_filters: ["com.android.server.wm.flicker.activityembedding.open.MainActivityStartsSecondaryWithAlwaysExpandTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "FlickerTestsActivityEmbedding-Open-OpenActivityEmbeddingPlaceholderSplitTest",
+    base: "FlickerTestsActivityEmbedding",
+    include_filters: ["com.android.server.wm.flicker.activityembedding.open.OpenActivityEmbeddingPlaceholderSplitTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "FlickerTestsActivityEmbedding-Open-OpenActivityEmbeddingSecondaryToSplitTest",
+    base: "FlickerTestsActivityEmbedding",
+    include_filters: ["com.android.server.wm.flicker.activityembedding.open.OpenActivityEmbeddingSecondaryToSplitTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "FlickerTestsActivityEmbedding-Open-OpenThirdActivityOverSplitTest",
+    base: "FlickerTestsActivityEmbedding",
+    include_filters: ["com.android.server.wm.flicker.activityembedding.open.OpenThirdActivityOverSplitTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "FlickerTestsActivityEmbedding-Open-OpenTrampolineActivityTest",
+    base: "FlickerTestsActivityEmbedding",
+    include_filters: ["com.android.server.wm.flicker.activityembedding.open.OpenTrampolineActivityTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "FlickerTestsActivityEmbedding-Pip-SecondaryActivityEnterPipTest",
+    base: "FlickerTestsActivityEmbedding",
+    include_filters: ["com.android.server.wm.flicker.activityembedding.pip.SecondaryActivityEnterPipTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "FlickerTestsActivityEmbedding-Rotation-RotateSplitNoChangeTest",
+    base: "FlickerTestsActivityEmbedding",
+    include_filters: ["com.android.server.wm.flicker.activityembedding.rotation.RotateSplitNoChangeTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "FlickerTestsActivityEmbedding-Rtl-RTLStartSecondaryWithPlaceholderTest",
+    base: "FlickerTestsActivityEmbedding",
+    include_filters: ["com.android.server.wm.flicker.activityembedding.rtl.RTLStartSecondaryWithPlaceholderTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "FlickerTestsActivityEmbedding-SplitScreen-EnterSystemSplitTest",
+    base: "FlickerTestsActivityEmbedding",
+    include_filters: ["com.android.server.wm.flicker.activityembedding.splitscreen.EnterSystemSplitTest"],
+    test_suites: ["device-tests"],
+}
+
+// End breakdowns for FlickerTestsActivityEmbedding module
+////////////////////////////////////////////////////////////////////////////////
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotationTransition.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotationTransition.kt
index ee2c05e..06326f8 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotationTransition.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotationTransition.kt
@@ -36,7 +36,7 @@
         teardown { testApp.exit(wmHelper) }
         transitions {
             this.setRotation(flicker.scenario.endRotation)
-            if (!flicker.scenario.isTablet) {
+            if (!usesTaskbar) {
                 wmHelper.StateSyncBuilder()
                     .add(navBarInPosition(flicker.scenario.isGesturalNavigation))
                     .waitForAndVerify()
diff --git a/tests/FlickerTests/AppLaunch/Android.bp b/tests/FlickerTests/AppLaunch/Android.bp
index 72a9065..b61739f 100644
--- a/tests/FlickerTests/AppLaunch/Android.bp
+++ b/tests/FlickerTests/AppLaunch/Android.bp
@@ -15,6 +15,7 @@
 //
 
 package {
+    default_team: "trendy_team_windowing_animations_transitions",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_license"
@@ -23,6 +24,9 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
+////////////////////////////////////////////////////////////////////////////////
+// Begin to cleanup after CL merges
+
 filegroup {
     name: "FlickerTestsAppLaunchCommon-src",
     srcs: ["src/**/common/*"],
@@ -69,3 +73,122 @@
     ],
     data: ["trace_config/*"],
 }
+
+// End to cleanup after CL merges
+////////////////////////////////////////////////////////////////////////////////
+
+android_test {
+    name: "FlickerTestsAppLaunch",
+    defaults: ["FlickerTestsDefault"],
+    manifest: "AndroidManifest.xml",
+    test_config_template: "AndroidTestTemplate.xml",
+    srcs: ["src/**/*"],
+    static_libs: [
+        "FlickerTestsBase",
+        "FlickerTestsAppLaunchCommon",
+    ],
+    data: ["trace_config/*"],
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Begin breakdowns for FlickerTestsAppLaunch module
+
+test_module_config {
+    name: "FlickerTestsAppLaunch-CatchAll",
+    base: "FlickerTestsAppLaunch",
+    exclude_filters: [
+        "com.android.server.wm.flicker.launch.TaskTransitionTest",
+        "com.android.server.wm.flicker.launch.ActivityTransitionTest",
+        "com.android.server.wm.flicker.launch.OpenAppFromIconColdTest",
+        "com.android.server.wm.flicker.launch.OpenAppFromIntentColdAfterCameraTest",
+        "com.android.server.wm.flicker.launch.OpenAppFromIntentColdTest",
+        "com.android.server.wm.flicker.launch.OpenAppFromIntentWarmTest",
+        "com.android.server.wm.flicker.launch.OpenAppFromLockscreenViaIntentTest",
+        "com.android.server.wm.flicker.launch.OpenAppFromOverviewTest",
+        "com.android.server.wm.flicker.launch.OpenCameraFromHomeOnDoubleClickPowerButtonTest",
+        "com.android.server.wm.flicker.launch.OpenTransferSplashscreenAppFromLauncherTransition",
+        "com.android.server.wm.flicker.launch.OverrideTaskTransitionTest",
+        "com.android.server.wm.flicker.launch.TaskTransitionTest",
+    ],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "FlickerTestsAppLaunch-ActivityTransitionTest",
+    base: "FlickerTestsAppLaunch",
+    include_filters: ["com.android.server.wm.flicker.launch.ActivityTransitionTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "FlickerTestsAppLaunch-OpenAppFromIconColdTest",
+    base: "FlickerTestsAppLaunch",
+    include_filters: ["com.android.server.wm.flicker.launch.OpenAppFromIconColdTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "FlickerTestsAppLaunch-OpenAppFromIntentColdAfterCameraTest",
+    base: "FlickerTestsAppLaunch",
+    include_filters: ["com.android.server.wm.flicker.launch.OpenAppFromIntentColdAfterCameraTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "FlickerTestsAppLaunch-OpenAppFromIntentColdTest",
+    base: "FlickerTestsAppLaunch",
+    include_filters: ["com.android.server.wm.flicker.launch.OpenAppFromIntentColdTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "FlickerTestsAppLaunch-OpenAppFromIntentWarmTest",
+    base: "FlickerTestsAppLaunch",
+    include_filters: ["com.android.server.wm.flicker.launch.OpenAppFromIntentWarmTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "FlickerTestsAppLaunch-OpenAppFromLockscreenViaIntentTest",
+    base: "FlickerTestsAppLaunch",
+    include_filters: ["com.android.server.wm.flicker.launch.OpenAppFromLockscreenViaIntentTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "FlickerTestsAppLaunch-OpenAppFromOverviewTest",
+    base: "FlickerTestsAppLaunch",
+    include_filters: ["com.android.server.wm.flicker.launch.OpenAppFromOverviewTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "FlickerTestsAppLaunch-OpenCameraFromHomeOnDoubleClickPowerButtonTest",
+    base: "FlickerTestsAppLaunch",
+    include_filters: ["com.android.server.wm.flicker.launch.OpenCameraFromHomeOnDoubleClickPowerButtonTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "FlickerTestsAppLaunch-OpenTransferSplashscreenAppFromLauncherTransition",
+    base: "FlickerTestsAppLaunch",
+    include_filters: ["com.android.server.wm.flicker.launch.OpenTransferSplashscreenAppFromLauncherTransition"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "FlickerTestsAppLaunch-OverrideTaskTransitionTest",
+    base: "FlickerTestsAppLaunch",
+    include_filters: ["com.android.server.wm.flicker.launch.OverrideTaskTransitionTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "FlickerTestsAppLaunch-TaskTransitionTest",
+    base: "FlickerTestsAppLaunch",
+    include_filters: ["com.android.server.wm.flicker.launch.TaskTransitionTest"],
+    test_suites: ["device-tests"],
+}
+
+// End breakdowns for FlickerTestsAppLaunch module
+////////////////////////////////////////////////////////////////////////////////
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 44ae27c..adeba72 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
@@ -75,7 +75,7 @@
     @FlakyTest(bugId = 288341660)
     @Test
     fun navBarLayerVisibilityChanges() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
+        Assume.assumeFalse(usesTaskbar)
         flicker.assertLayers {
             this.isInvisible(ComponentNameMatcher.NAV_BAR)
                 .then()
@@ -97,7 +97,7 @@
     @FlakyTest(bugId = 293581770)
     @Test
     fun navBarWindowsVisibilityChanges() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
+        Assume.assumeFalse(usesTaskbar)
         flicker.assertWm {
             this.isNonAppWindowInvisible(ComponentNameMatcher.NAV_BAR)
                 .then()
@@ -112,7 +112,7 @@
     @Presubmit
     @Test
     fun taskBarLayerIsVisibleAtEnd() {
-        Assume.assumeTrue(flicker.scenario.isTablet)
+        Assume.assumeTrue(usesTaskbar)
         flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.TASK_BAR) }
     }
 
@@ -170,7 +170,7 @@
     @Presubmit
     @Test
     fun navBarLayerIsVisibleAtEnd() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
+        Assume.assumeFalse(usesTaskbar)
         flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.NAV_BAR) }
     }
 
@@ -184,7 +184,7 @@
     @Presubmit
     @Test
     override fun appLayerBecomesVisible() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
+        Assume.assumeFalse(usesTaskbar)
         super.appLayerBecomesVisible()
     }
 
@@ -192,7 +192,7 @@
     @FlakyTest(bugId = 227143265)
     @Test
     fun appLayerBecomesVisibleTablet() {
-        Assume.assumeTrue(flicker.scenario.isTablet)
+        Assume.assumeTrue(usesTaskbar)
         super.appLayerBecomesVisible()
     }
 
diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/common/OpenAppFromIconTransition.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/common/OpenAppFromIconTransition.kt
index 8a3304b..b497e30 100644
--- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/common/OpenAppFromIconTransition.kt
+++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/common/OpenAppFromIconTransition.kt
@@ -28,6 +28,7 @@
         get() = {
             super.transition(this)
             setup {
+                // By default, launcher doesn't rotate on phones, but rotates on tablets
                 if (flicker.scenario.isTablet) {
                     tapl.setExpectedRotation(flicker.scenario.startRotation.value)
                 } else {
diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/common/OpenAppFromLockscreenTransition.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/common/OpenAppFromLockscreenTransition.kt
index f8fd3586..a6e31d4 100644
--- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/common/OpenAppFromLockscreenTransition.kt
+++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/common/OpenAppFromLockscreenTransition.kt
@@ -103,7 +103,7 @@
     @Presubmit
     @Test
     open fun navBarLayerPositionAtEnd() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
+        Assume.assumeFalse(usesTaskbar)
         flicker.navBarLayerPositionAtEnd()
     }
 
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt
index dc2bd1b..522c68b 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt
@@ -72,7 +72,7 @@
     @Presubmit
     @Test
     override fun navBarLayerPositionAtStartAndEnd() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
+        Assume.assumeFalse(usesTaskbar)
         Assume.assumeFalse(flicker.scenario.isLandscapeOrSeascapeAtStart)
         flicker.navBarLayerPositionAtStartAndEnd()
     }
@@ -80,7 +80,7 @@
     @Presubmit
     @Test
     fun navBarLayerPositionAtStartAndEndLandscapeOrSeascapeAtStart() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
+        Assume.assumeFalse(usesTaskbar)
         Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart)
         flicker.navBarLayerPositionAtStartAndEnd()
     }
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt
index c96c760..638d594 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt
@@ -93,7 +93,7 @@
     @Presubmit
     @Test
     fun navBarLayerIsVisibleAtStartAndEnd3Button() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
+        Assume.assumeFalse(usesTaskbar)
         Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
         flicker.navBarLayerIsVisibleAtStartAndEnd()
     }
@@ -105,7 +105,7 @@
     @Presubmit
     @Test
     fun navBarLayerIsInvisibleInLandscapeGestural() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
+        Assume.assumeFalse(usesTaskbar)
         Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart)
         Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
         flicker.assertLayersStart { this.isVisible(ComponentNameMatcher.NAV_BAR) }
@@ -121,7 +121,7 @@
     fun statusBarLayerIsInvisibleInLandscapePhone() {
         Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart)
         Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
-        Assume.assumeFalse(flicker.scenario.isTablet)
+        Assume.assumeFalse(usesTaskbar)
         flicker.assertLayersStart { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
         flicker.assertLayersEnd { this.isInvisible(ComponentNameMatcher.STATUS_BAR) }
     }
@@ -135,7 +135,7 @@
     fun statusBarLayerIsInvisibleInLandscapeTablet() {
         Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart)
         Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
-        Assume.assumeTrue(flicker.scenario.isTablet)
+        Assume.assumeTrue(usesTaskbar)
         flicker.statusBarLayerIsVisibleAtStartAndEnd()
     }
 
@@ -174,7 +174,7 @@
     @Test
     fun statusBarLayerIsInvisibleInLandscape() {
         Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart)
-        Assume.assumeFalse(flicker.scenario.isTablet)
+        Assume.assumeFalse(usesTaskbar)
         flicker.assertLayersStart { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
         flicker.assertLayersEnd { this.isInvisible(ComponentNameMatcher.STATUS_BAR) }
     }
diff --git a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt
index 07fc230..ad70757 100644
--- a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt
+++ b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt
@@ -151,7 +151,7 @@
     @Presubmit
     @Test
     open fun taskBarWindowIsVisibleAtEnd() {
-        Assume.assumeTrue(flicker.scenario.isTablet)
+        Assume.assumeTrue(usesTaskbar)
         flicker.taskBarWindowIsVisibleAtEnd()
     }
 
@@ -163,7 +163,7 @@
     @Presubmit
     @Test
     open fun taskBarLayerIsVisibleAtEnd() {
-        Assume.assumeTrue(flicker.scenario.isTablet)
+        Assume.assumeTrue(usesTaskbar)
         flicker.taskBarLayerIsVisibleAtEnd()
     }
 
@@ -171,7 +171,7 @@
     @Presubmit
     @Test
     open fun navBarLayerPositionAtEnd() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
+        Assume.assumeFalse(usesTaskbar)
         flicker.navBarLayerPositionAtEnd()
     }
 
@@ -179,14 +179,14 @@
     @Presubmit
     @Test
     open fun navBarLayerIsVisibleAtEnd() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
+        Assume.assumeFalse(usesTaskbar)
         flicker.navBarLayerIsVisibleAtEnd()
     }
 
     @Presubmit
     @Test
     open fun navBarWindowIsVisibleAtEnd() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
+        Assume.assumeFalse(usesTaskbar)
         flicker.navBarWindowIsVisibleAtEnd()
     }
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
index 060015b..70d762e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
@@ -26,6 +26,7 @@
 import android.util.Log
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.Flags
 import org.junit.Assume
 import org.junit.AssumptionViolatedException
 import org.junit.Test
@@ -48,6 +49,9 @@
 
     private val logTag = this::class.java.simpleName
 
+    protected val usesTaskbar: Boolean
+        get() = flicker.scenario.isTablet || Flags.enableTaskbarOnPhones()
+
     /** Specification of the test transition to execute */
     abstract val transition: FlickerBuilder.() -> Unit
 
@@ -87,7 +91,7 @@
     @Presubmit
     @Test
     open fun navBarLayerIsVisibleAtStartAndEnd() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
+        Assume.assumeFalse(usesTaskbar)
         flicker.navBarLayerIsVisibleAtStartAndEnd()
     }
 
@@ -100,7 +104,7 @@
     @Presubmit
     @Test
     open fun navBarLayerPositionAtStartAndEnd() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
+        Assume.assumeFalse(usesTaskbar)
         flicker.navBarLayerPositionAtStartAndEnd()
     }
 
@@ -112,7 +116,7 @@
     @Presubmit
     @Test
     open fun navBarWindowIsAlwaysVisible() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
+        Assume.assumeFalse(usesTaskbar)
         Assume.assumeFalse(flicker.scenario.isLandscapeOrSeascapeAtStart)
         flicker.navBarWindowIsAlwaysVisible()
     }
@@ -126,7 +130,7 @@
     @Presubmit
     @Test
     open fun navBarWindowIsVisibleAtStartAndEnd() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
+        Assume.assumeFalse(usesTaskbar)
         flicker.navBarWindowIsVisibleAtStartAndEnd()
     }
 
@@ -139,7 +143,7 @@
     @Presubmit
     @Test
     open fun taskBarLayerIsVisibleAtStartAndEnd() {
-        Assume.assumeTrue(flicker.scenario.isTablet)
+        Assume.assumeTrue(usesTaskbar)
         flicker.taskBarLayerIsVisibleAtStartAndEnd()
     }
 
@@ -151,7 +155,7 @@
     @Presubmit
     @Test
     open fun taskBarWindowIsAlwaysVisible() {
-        Assume.assumeTrue(flicker.scenario.isTablet)
+        Assume.assumeTrue(usesTaskbar)
         flicker.taskBarWindowIsAlwaysVisible()
     }
 
diff --git a/tests/Input/src/android/hardware/input/KeyboardSystemShortcutListenerTest.kt b/tests/Input/src/android/hardware/input/KeyboardSystemShortcutListenerTest.kt
new file mode 100644
index 0000000..24d7291
--- /dev/null
+++ b/tests/Input/src/android/hardware/input/KeyboardSystemShortcutListenerTest.kt
@@ -0,0 +1,202 @@
+/*
+ * 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 android.hardware.input
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.os.Handler
+import android.os.HandlerExecutor
+import android.os.test.TestLooper
+import android.platform.test.annotations.Presubmit
+import android.platform.test.flag.junit.SetFlagsRule
+import android.view.KeyEvent
+import androidx.test.core.app.ApplicationProvider
+import com.android.server.testutils.any
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.`when`
+import org.mockito.junit.MockitoJUnitRunner
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+import kotlin.test.fail
+
+/**
+ * Tests for [InputManager.KeyboardSystemShortcutListener].
+ *
+ * Build/Install/Run:
+ * atest InputTests:KeyboardSystemShortcutListenerTest
+ */
+@Presubmit
+@RunWith(MockitoJUnitRunner::class)
+class KeyboardSystemShortcutListenerTest {
+
+    companion object {
+        const val DEVICE_ID = 1
+        val HOME_SHORTCUT = KeyboardSystemShortcut(
+            intArrayOf(KeyEvent.KEYCODE_H),
+            KeyEvent.META_META_ON or KeyEvent.META_META_LEFT_ON,
+            KeyboardSystemShortcut.SYSTEM_SHORTCUT_HOME
+        )
+    }
+
+    @get:Rule
+    val rule = SetFlagsRule()
+
+    private val testLooper = TestLooper()
+    private val executor = HandlerExecutor(Handler(testLooper.looper))
+    private var registeredListener: IKeyboardSystemShortcutListener? = null
+    private lateinit var context: Context
+    private lateinit var inputManager: InputManager
+    private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession
+
+    @Mock
+    private lateinit var iInputManagerMock: IInputManager
+
+    @Before
+    fun setUp() {
+        context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
+        inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManagerMock)
+        inputManager = InputManager(context)
+        `when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE)))
+                .thenReturn(inputManager)
+
+        // Handle keyboard system shortcut listener registration.
+        doAnswer {
+            val listener = it.getArgument(0) as IKeyboardSystemShortcutListener
+            if (registeredListener != null &&
+                    registeredListener!!.asBinder() != listener.asBinder()) {
+                // There can only be one registered keyboard system shortcut listener per process.
+                fail("Trying to register a new listener when one already exists")
+            }
+            registeredListener = listener
+            null
+        }.`when`(iInputManagerMock).registerKeyboardSystemShortcutListener(any())
+
+        // Handle keyboard system shortcut listener being unregistered.
+        doAnswer {
+            val listener = it.getArgument(0) as IKeyboardSystemShortcutListener
+            if (registeredListener == null ||
+                    registeredListener!!.asBinder() != listener.asBinder()) {
+                fail("Trying to unregister a listener that is not registered")
+            }
+            registeredListener = null
+            null
+        }.`when`(iInputManagerMock).unregisterKeyboardSystemShortcutListener(any())
+    }
+
+    @After
+    fun tearDown() {
+        if (this::inputManagerGlobalSession.isInitialized) {
+            inputManagerGlobalSession.close()
+        }
+    }
+
+    private fun notifyKeyboardSystemShortcutTriggered(id: Int, shortcut: KeyboardSystemShortcut) {
+        registeredListener!!.onKeyboardSystemShortcutTriggered(
+            id,
+            shortcut.keycodes,
+            shortcut.modifierState,
+            shortcut.systemShortcut
+        )
+    }
+
+    @Test
+    fun testListenerHasCorrectSystemShortcutNotified() {
+        var callbackCount = 0
+
+        // Add a keyboard system shortcut listener
+        inputManager.registerKeyboardSystemShortcutListener(executor) {
+            deviceId: Int, systemShortcut: KeyboardSystemShortcut ->
+            assertEquals(DEVICE_ID, deviceId)
+            assertEquals(HOME_SHORTCUT, systemShortcut)
+            callbackCount++
+        }
+
+        // Notifying keyboard system shortcut triggered will notify the listener.
+        notifyKeyboardSystemShortcutTriggered(DEVICE_ID, HOME_SHORTCUT)
+        testLooper.dispatchNext()
+        assertEquals(1, callbackCount)
+    }
+
+    @Test
+    fun testAddingListenersRegistersInternalCallbackListener() {
+        // Set up two callbacks.
+        val callback1 = InputManager.KeyboardSystemShortcutListener {_, _ -> }
+        val callback2 = InputManager.KeyboardSystemShortcutListener {_, _ -> }
+
+        assertNull(registeredListener)
+
+        // Adding the listener should register the callback with InputManagerService.
+        inputManager.registerKeyboardSystemShortcutListener(executor, callback1)
+        assertNotNull(registeredListener)
+
+        // Adding another listener should not register new internal listener.
+        val currListener = registeredListener
+        inputManager.registerKeyboardSystemShortcutListener(executor, callback2)
+        assertEquals(currListener, registeredListener)
+    }
+
+    @Test
+    fun testRemovingListenersUnregistersInternalCallbackListener() {
+        // Set up two callbacks.
+        val callback1 = InputManager.KeyboardSystemShortcutListener {_, _ -> }
+        val callback2 = InputManager.KeyboardSystemShortcutListener {_, _ -> }
+
+        inputManager.registerKeyboardSystemShortcutListener(executor, callback1)
+        inputManager.registerKeyboardSystemShortcutListener(executor, callback2)
+
+        // Only removing all listeners should remove the internal callback
+        inputManager.unregisterKeyboardSystemShortcutListener(callback1)
+        assertNotNull(registeredListener)
+        inputManager.unregisterKeyboardSystemShortcutListener(callback2)
+        assertNull(registeredListener)
+    }
+
+    @Test
+    fun testMultipleListeners() {
+        // Set up two callbacks.
+        var callbackCount1 = 0
+        var callbackCount2 = 0
+        val callback1 = InputManager.KeyboardSystemShortcutListener { _, _ -> callbackCount1++ }
+        val callback2 = InputManager.KeyboardSystemShortcutListener { _, _ -> callbackCount2++ }
+
+        // Add both keyboard system shortcut listeners
+        inputManager.registerKeyboardSystemShortcutListener(executor, callback1)
+        inputManager.registerKeyboardSystemShortcutListener(executor, callback2)
+
+        // Notifying keyboard system shortcut triggered, should notify both the callbacks.
+        notifyKeyboardSystemShortcutTriggered(DEVICE_ID, HOME_SHORTCUT)
+        testLooper.dispatchAll()
+        assertEquals(1, callbackCount1)
+        assertEquals(1, callbackCount2)
+
+        inputManager.unregisterKeyboardSystemShortcutListener(callback2)
+        // Notifying keyboard system shortcut triggered, should still trigger callback1 but not
+        // callback2.
+        notifyKeyboardSystemShortcutTriggered(DEVICE_ID, HOME_SHORTCUT)
+        testLooper.dispatchAll()
+        assertEquals(2, callbackCount1)
+        assertEquals(1, callbackCount2)
+    }
+}
diff --git a/tests/Input/src/com/android/server/input/KeyboardShortcutCallbackHandlerTests.kt b/tests/Input/src/com/android/server/input/KeyboardShortcutCallbackHandlerTests.kt
new file mode 100644
index 0000000..5a40a1c
--- /dev/null
+++ b/tests/Input/src/com/android/server/input/KeyboardShortcutCallbackHandlerTests.kt
@@ -0,0 +1,96 @@
+/*
+ * 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
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.hardware.input.IKeyboardSystemShortcutListener
+import android.hardware.input.KeyboardSystemShortcut
+import android.platform.test.annotations.Presubmit
+import android.view.KeyEvent
+import androidx.test.core.app.ApplicationProvider
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mockito
+import org.mockito.junit.MockitoJUnit
+
+/**
+ * Tests for {@link KeyboardShortcutCallbackHandler}.
+ *
+ * Build/Install/Run:
+ * atest InputTests:KeyboardShortcutCallbackHandlerTests
+ */
+@Presubmit
+class KeyboardShortcutCallbackHandlerTests {
+
+    companion object {
+        val DEVICE_ID = 1
+        val HOME_SHORTCUT = KeyboardSystemShortcut(
+            intArrayOf(KeyEvent.KEYCODE_H),
+            KeyEvent.META_META_ON or KeyEvent.META_META_LEFT_ON,
+            KeyboardSystemShortcut.SYSTEM_SHORTCUT_HOME
+        )
+    }
+
+    @get:Rule
+    val rule = MockitoJUnit.rule()!!
+
+    private lateinit var keyboardShortcutCallbackHandler: KeyboardShortcutCallbackHandler
+    private lateinit var context: Context
+    private var lastShortcut: KeyboardSystemShortcut? = null
+
+    @Before
+    fun setup() {
+        context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
+        keyboardShortcutCallbackHandler = KeyboardShortcutCallbackHandler()
+    }
+
+    @Test
+    fun testKeyboardSystemShortcutTriggered_registerUnregisterListener() {
+        val listener = KeyboardSystemShortcutListener()
+
+        // Register keyboard system shortcut listener
+        keyboardShortcutCallbackHandler.registerKeyboardSystemShortcutListener(listener, 0)
+        keyboardShortcutCallbackHandler.onKeyboardSystemShortcutTriggered(DEVICE_ID, HOME_SHORTCUT)
+        assertEquals(
+            "Listener should get callback on keyboard system shortcut triggered",
+            HOME_SHORTCUT,
+            lastShortcut!!
+        )
+
+        // Unregister listener
+        lastShortcut = null
+        keyboardShortcutCallbackHandler.unregisterKeyboardSystemShortcutListener(listener, 0)
+        keyboardShortcutCallbackHandler.onKeyboardSystemShortcutTriggered(DEVICE_ID, HOME_SHORTCUT)
+        assertNull("Listener should not get callback after being unregistered", lastShortcut)
+    }
+
+    inner class KeyboardSystemShortcutListener : IKeyboardSystemShortcutListener.Stub() {
+        override fun onKeyboardSystemShortcutTriggered(
+                deviceId: Int,
+                keycodes: IntArray,
+                modifierState: Int,
+                shortcut: Int
+        ) {
+            assertEquals(DEVICE_ID, deviceId)
+            lastShortcut = KeyboardSystemShortcut(keycodes, modifierState, shortcut)
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/Internal/Android.bp b/tests/Internal/Android.bp
index 827ff4f..ad98e47 100644
--- a/tests/Internal/Android.bp
+++ b/tests/Internal/Android.bp
@@ -24,6 +24,7 @@
         "flickerlib-parsers",
         "perfetto_trace_java_protos",
         "flickerlib-trace_processor_shell",
+        "ravenwood-junit",
     ],
     java_resource_dirs: ["res"],
     certificate: "platform",
@@ -39,6 +40,7 @@
         "platform-test-annotations",
     ],
     srcs: [
+        "src/com/android/internal/graphics/ColorUtilsTest.java",
         "src/com/android/internal/util/ParcellingTests.java",
     ],
     auto_gen_config: true,
diff --git a/tests/Internal/src/com/android/internal/graphics/ColorUtilsTest.java b/tests/Internal/src/com/android/internal/graphics/ColorUtilsTest.java
index d0bb8e3..38a22f2 100644
--- a/tests/Internal/src/com/android/internal/graphics/ColorUtilsTest.java
+++ b/tests/Internal/src/com/android/internal/graphics/ColorUtilsTest.java
@@ -19,14 +19,19 @@
 import static org.junit.Assert.assertTrue;
 
 import android.graphics.Color;
+import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.filters.SmallTest;
 
+import org.junit.Rule;
 import org.junit.Test;
 
 @SmallTest
 public class ColorUtilsTest {
 
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
     @Test
     public void calculateMinimumBackgroundAlpha_satisfiestContrast() {
 
diff --git a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
index fad94d4..7d0c596 100644
--- a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
@@ -126,30 +126,35 @@
                                 .setMessage("My Test Debug Log Message %b")
                                 .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_DEBUG)
                                 .setGroupId(1)
+                                .setLocation("com/test/MyTestClass.java:123")
                 ).addMessages(
                         Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
                                 .setMessageId(2)
                                 .setMessage("My Test Verbose Log Message %b")
                                 .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_VERBOSE)
                                 .setGroupId(1)
+                                .setLocation("com/test/MyTestClass.java:342")
                 ).addMessages(
                         Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
                                 .setMessageId(3)
                                 .setMessage("My Test Warn Log Message %b")
                                 .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_WARN)
                                 .setGroupId(1)
+                                .setLocation("com/test/MyTestClass.java:563")
                 ).addMessages(
                         Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
                                 .setMessageId(4)
                                 .setMessage("My Test Error Log Message %b")
                                 .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_ERROR)
                                 .setGroupId(1)
+                                .setLocation("com/test/MyTestClass.java:156")
                 ).addMessages(
                         Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
                                 .setMessageId(5)
                                 .setMessage("My Test WTF Log Message %b")
                                 .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_WTF)
                                 .setGroupId(1)
+                                .setLocation("com/test/MyTestClass.java:192")
                 );
 
         ViewerConfigInputStreamProvider viewerConfigInputStreamProvider = Mockito.mock(
@@ -465,6 +470,26 @@
                 .isEqualTo("My test message :: test, 2, 4, 6, 0.400000, true");
     }
 
+    @Test
+    public  void supportsLocationInformation() throws IOException {
+        PerfettoTraceMonitor traceMonitor =
+                PerfettoTraceMonitor.newBuilder().enableProtoLog(true).build();
+        try {
+            traceMonitor.start();
+            mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
+                    LogDataType.BOOLEAN, new Object[]{true});
+        } finally {
+            traceMonitor.stop(mWriter);
+        }
+
+        final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+        final ProtoLogTrace protolog = reader.readProtoLogTrace();
+
+        Truth.assertThat(protolog.messages).hasSize(1);
+        Truth.assertThat(protolog.messages.get(0).getLocation())
+                .isEqualTo("com/test/MyTestClass.java:123");
+    }
+
     private long addMessageToConfig(ProtologCommon.ProtoLogLevel logLevel, String message) {
         final long messageId = new Random().nextLong();
         mViewerConfigBuilder.addMessages(Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
diff --git a/tests/Internal/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java b/tests/Internal/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java
new file mode 100644
index 0000000..e3ec62d
--- /dev/null
+++ b/tests/Internal/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.contains;
+import static org.mockito.ArgumentMatchers.endsWith;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.times;
+
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Test class for {@link ProtoLogImpl}.
+ */
+@Presubmit
+@RunWith(MockitoJUnitRunner.class)
+public class ProtoLogCommandHandlerTest {
+
+    @Mock
+    ProtoLogService mProtoLogService;
+    @Mock
+    PrintWriter mPrintWriter;
+
+    @Test
+    public void printsHelpForAllAvailableCommands() {
+        final ProtoLogCommandHandler cmdHandler =
+                new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+
+        cmdHandler.onHelp();
+        validateOnHelpPrinted();
+    }
+
+    @Test
+    public void printsHelpIfCommandIsNull() {
+        final ProtoLogCommandHandler cmdHandler =
+                new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+
+        cmdHandler.onCommand(null);
+        validateOnHelpPrinted();
+    }
+
+    @Test
+    public void handlesGroupListCommand() {
+        Mockito.when(mProtoLogService.getGroups())
+                .thenReturn(new String[] {"MY_TEST_GROUP", "MY_OTHER_GROUP"});
+        final ProtoLogCommandHandler cmdHandler =
+                new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+
+        cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
+                new String[] { "groups", "list" });
+
+        Mockito.verify(mPrintWriter, times(1))
+                .println(contains("MY_TEST_GROUP"));
+        Mockito.verify(mPrintWriter, times(1))
+                .println(contains("MY_OTHER_GROUP"));
+    }
+
+    @Test
+    public void handlesIncompleteGroupsCommand() {
+        final ProtoLogCommandHandler cmdHandler =
+                new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+
+        cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
+                new String[] { "groups" });
+
+        Mockito.verify(mPrintWriter, times(1))
+                .println(contains("Incomplete command"));
+    }
+
+    @Test
+    public void handlesGroupStatusCommand() {
+        Mockito.when(mProtoLogService.getGroups()).thenReturn(new String[] {"MY_GROUP"});
+        Mockito.when(mProtoLogService.isLoggingToLogcat("MY_GROUP")).thenReturn(true);
+        final ProtoLogCommandHandler cmdHandler =
+                new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+
+        cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
+                new String[] { "groups", "status", "MY_GROUP" });
+
+        Mockito.verify(mPrintWriter, times(1))
+                .println(contains("MY_GROUP"));
+        Mockito.verify(mPrintWriter, times(1))
+                .println(contains("LOG_TO_LOGCAT = true"));
+    }
+
+    @Test
+    public void handlesGroupStatusCommandOfUnregisteredGroups() {
+        Mockito.when(mProtoLogService.getGroups()).thenReturn(new String[] {});
+        final ProtoLogCommandHandler cmdHandler =
+                new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+
+        cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
+                new String[] { "groups", "status", "MY_GROUP" });
+
+        Mockito.verify(mPrintWriter, times(1))
+                .println(contains("MY_GROUP"));
+        Mockito.verify(mPrintWriter, times(1))
+                .println(contains("UNREGISTERED"));
+    }
+
+    @Test
+    public void handlesGroupStatusCommandWithNoGroups() {
+        final ProtoLogCommandHandler cmdHandler =
+                new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+
+        cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
+                new String[] { "groups", "status" });
+
+        Mockito.verify(mPrintWriter, times(1))
+                .println(contains("Incomplete command"));
+    }
+
+    @Test
+    public void handlesIncompleteLogcatCommand() {
+        final ProtoLogCommandHandler cmdHandler =
+                new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+
+        cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
+                new String[] { "logcat" });
+
+        Mockito.verify(mPrintWriter, times(1))
+                .println(contains("Incomplete command"));
+    }
+
+    @Test
+    public void handlesLogcatEnableCommand() {
+        final ProtoLogCommandHandler cmdHandler =
+                new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+
+        cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
+                new String[] { "logcat", "enable", "MY_GROUP" });
+        Mockito.verify(mProtoLogService).enableProtoLogToLogcat("MY_GROUP");
+
+        cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
+                new String[] { "logcat", "enable", "MY_GROUP", "MY_OTHER_GROUP" });
+        Mockito.verify(mProtoLogService)
+                .enableProtoLogToLogcat("MY_GROUP", "MY_OTHER_GROUP");
+    }
+
+    @Test
+    public void handlesLogcatDisableCommand() {
+        final ProtoLogCommandHandler cmdHandler =
+                new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+
+        cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
+                new String[] { "logcat", "disable", "MY_GROUP" });
+        Mockito.verify(mProtoLogService).disableProtoLogToLogcat("MY_GROUP");
+
+        cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
+                new String[] { "logcat", "disable", "MY_GROUP", "MY_OTHER_GROUP" });
+        Mockito.verify(mProtoLogService)
+                .disableProtoLogToLogcat("MY_GROUP", "MY_OTHER_GROUP");
+    }
+
+    @Test
+    public void handlesLogcatEnableCommandWithNoGroups() {
+        final ProtoLogCommandHandler cmdHandler =
+                new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+
+        cmdHandler.exec(mProtoLogService, 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);
+
+        cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
+                new String[] { "logcat", "disable" });
+        Mockito.verify(mPrintWriter).println(contains("Incomplete command"));
+    }
+
+    private void validateOnHelpPrinted() {
+        Mockito.verify(mPrintWriter, times(1)).println(endsWith("help"));
+        Mockito.verify(mPrintWriter, times(1))
+                .println(endsWith("groups (list | status)"));
+        Mockito.verify(mPrintWriter, times(1))
+                .println(endsWith("logcat (enable | disable) <group>"));
+        Mockito.verify(mPrintWriter, atLeast(0)).println(anyString());
+    }
+}
diff --git a/tests/Internal/src/com/android/internal/protolog/ProtoLogServiceTest.java b/tests/Internal/src/com/android/internal/protolog/ProtoLogServiceTest.java
new file mode 100644
index 0000000..feac59c
--- /dev/null
+++ b/tests/Internal/src/com/android/internal/protolog/ProtoLogServiceTest.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 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.never;
+
+import static java.io.File.createTempFile;
+import static java.nio.file.Files.createTempDirectory;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.tools.ScenarioBuilder;
+import android.tools.Tag;
+import android.tools.io.ResultArtifactDescriptor;
+import android.tools.io.TraceType;
+import android.tools.traces.TraceConfig;
+import android.tools.traces.TraceConfigs;
+import android.tools.traces.io.ResultReader;
+import android.tools.traces.io.ResultWriter;
+import android.tools.traces.monitors.PerfettoTraceMonitor;
+
+import com.google.common.truth.Truth;
+import com.google.protobuf.InvalidProtocolBufferException;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import perfetto.protos.Protolog.ProtoLogViewerConfig;
+import perfetto.protos.ProtologCommon;
+import perfetto.protos.TraceOuterClass.Trace;
+import perfetto.protos.TracePacketOuterClass.TracePacket;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Test class for {@link ProtoLogImpl}.
+ */
+@Presubmit
+@RunWith(MockitoJUnitRunner.class)
+public class ProtoLogServiceTest {
+
+    private static final String TEST_GROUP = "MY_TEST_GROUP";
+    private static final String OTHER_TEST_GROUP = "MY_OTHER_TEST_GROUP";
+
+    private static final ProtoLogViewerConfig VIEWER_CONFIG =
+            ProtoLogViewerConfig.newBuilder()
+                    .addGroups(
+                            ProtoLogViewerConfig.Group.newBuilder()
+                                    .setId(1)
+                                    .setName(TEST_GROUP)
+                                    .setTag(TEST_GROUP)
+                    ).addMessages(
+                            ProtoLogViewerConfig.MessageData.newBuilder()
+                                    .setMessageId(1)
+                                    .setMessage("My Test Debug Log Message %b")
+                                    .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_DEBUG)
+                                    .setGroupId(1)
+                    ).addMessages(
+                            ProtoLogViewerConfig.MessageData.newBuilder()
+                                    .setMessageId(2)
+                                    .setMessage("My Test Verbose Log Message %b")
+                                    .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_VERBOSE)
+                                    .setGroupId(1)
+                    ).build();
+
+    @Mock
+    IProtoLogClient mMockClient;
+
+    @Mock
+    IProtoLogClient mSecondMockClient;
+
+    @Mock
+    IBinder mMockClientBinder;
+
+    @Mock
+    IBinder mSecondMockClientBinder;
+
+    private final File mTracingDirectory = createTempDirectory("temp").toFile();
+
+    private final ResultWriter mWriter = new ResultWriter()
+            .forScenario(new ScenarioBuilder()
+                    .forClass(createTempFile("temp", "").getName()).build())
+            .withOutputDir(mTracingDirectory)
+            .setRunComplete();
+
+    private final TraceConfigs mTraceConfig = new TraceConfigs(
+            new TraceConfig(false, true, false),
+            new TraceConfig(false, true, false),
+            new TraceConfig(false, true, false),
+            new TraceConfig(false, true, false)
+    );
+
+    @Captor
+    ArgumentCaptor<IBinder.DeathRecipient> mDeathRecipientArgumentCaptor;
+
+    @Captor
+    ArgumentCaptor<IBinder.DeathRecipient> mSecondDeathRecipientArgumentCaptor;
+
+    private File mViewerConfigFile;
+
+    public ProtoLogServiceTest() throws IOException {
+    }
+
+    @Before
+    public void setUp() {
+        Mockito.when(mMockClient.asBinder()).thenReturn(mMockClientBinder);
+        Mockito.when(mSecondMockClient.asBinder()).thenReturn(mSecondMockClientBinder);
+
+        try {
+            mViewerConfigFile = File.createTempFile("viewer-config", ".pb");
+            try (var fos = new FileOutputStream(mViewerConfigFile);
+                    BufferedOutputStream bos = new BufferedOutputStream(fos)) {
+
+                bos.write(VIEWER_CONFIG.toByteArray());
+            }
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Test
+    public void canRegisterClientWithGroupsOnly() throws RemoteException {
+        final ProtoLogService service = new ProtoLogService();
+
+        final ProtoLogService.RegisterClientArgs args = new ProtoLogService.RegisterClientArgs()
+                .setGroups(new ProtoLogService.RegisterClientArgs.GroupConfig(TEST_GROUP, true));
+        service.registerClient(mMockClient, args);
+
+        Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isTrue();
+        Truth.assertThat(service.getGroups()).asList().containsExactly(TEST_GROUP);
+    }
+
+    @Test
+    public void willDumpViewerConfigOnlyOnceOnTraceStop()
+            throws RemoteException, InvalidProtocolBufferException {
+        final ProtoLogService service = new ProtoLogService();
+
+        final ProtoLogService.RegisterClientArgs args = new ProtoLogService.RegisterClientArgs()
+                .setGroups(new ProtoLogService.RegisterClientArgs.GroupConfig(TEST_GROUP, true))
+                .setViewerConfigFile(mViewerConfigFile.getAbsolutePath());
+        service.registerClient(mMockClient, args);
+        service.registerClient(mSecondMockClient, args);
+
+        PerfettoTraceMonitor traceMonitor =
+                PerfettoTraceMonitor.newBuilder().enableProtoLog().build();
+
+        traceMonitor.start();
+        traceMonitor.stop(mWriter);
+        final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+        final byte[] traceData = reader.getArtifact()
+                .readBytes(new ResultArtifactDescriptor(TraceType.PERFETTO, Tag.ALL));
+
+        final Trace trace = Trace.parseFrom(traceData);
+
+        final List<TracePacket> configPackets = trace.getPacketList().stream()
+                .filter(it -> it.hasProtologViewerConfig())
+                // Exclude viewer configs from regular system tracing
+                .filter(it ->
+                        it.getProtologViewerConfig().getGroups(0).getName().equals(TEST_GROUP))
+                .toList();
+        Truth.assertThat(configPackets).hasSize(1);
+        Truth.assertThat(configPackets.get(0).getProtologViewerConfig().toString())
+                .isEqualTo(VIEWER_CONFIG.toString());
+    }
+
+    @Test
+    public void willDumpViewerConfigOnLastClientDisconnected()
+            throws RemoteException, FileNotFoundException {
+        final ProtoLogService.ViewerConfigFileTracer tracer =
+                Mockito.mock(ProtoLogService.ViewerConfigFileTracer.class);
+        final ProtoLogService service = new ProtoLogService(tracer);
+
+        final ProtoLogService.RegisterClientArgs args = new ProtoLogService.RegisterClientArgs()
+                .setGroups(new ProtoLogService.RegisterClientArgs.GroupConfig(
+                        TEST_GROUP, true))
+                .setViewerConfigFile(mViewerConfigFile.getAbsolutePath());
+        service.registerClient(mMockClient, args);
+        service.registerClient(mSecondMockClient, args);
+
+        Mockito.verify(mMockClientBinder)
+                .linkToDeath(mDeathRecipientArgumentCaptor.capture(), anyInt());
+        Mockito.verify(mSecondMockClientBinder)
+                .linkToDeath(mSecondDeathRecipientArgumentCaptor.capture(), anyInt());
+
+        mDeathRecipientArgumentCaptor.getValue().binderDied();
+        Mockito.verify(tracer, never()).trace(any(), any());
+        mSecondDeathRecipientArgumentCaptor.getValue().binderDied();
+        Mockito.verify(tracer).trace(any(), eq(mViewerConfigFile.getAbsolutePath()));
+    }
+
+    @Test
+    public void sendEnableLoggingToLogcatToClient() throws RemoteException {
+        final var service = new ProtoLogService();
+
+        final var args = new ProtoLogService.RegisterClientArgs()
+                .setGroups(new ProtoLogService.RegisterClientArgs.GroupConfig(TEST_GROUP, false));
+        service.registerClient(mMockClient, args);
+
+        Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isFalse();
+        service.enableProtoLogToLogcat(TEST_GROUP);
+        Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isTrue();
+
+        Mockito.verify(mMockClient).toggleLogcat(eq(true),
+                Mockito.argThat(it -> it.length == 1 && it[0].equals(TEST_GROUP)));
+    }
+
+    @Test
+    public void sendDisableLoggingToLogcatToClient() throws RemoteException {
+        final ProtoLogService service = new ProtoLogService();
+
+        final ProtoLogService.RegisterClientArgs args = new ProtoLogService.RegisterClientArgs()
+                .setGroups(new ProtoLogService.RegisterClientArgs.GroupConfig(TEST_GROUP, true));
+        service.registerClient(mMockClient, args);
+
+        Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isTrue();
+        service.disableProtoLogToLogcat(TEST_GROUP);
+        Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isFalse();
+
+        Mockito.verify(mMockClient).toggleLogcat(eq(false),
+                Mockito.argThat(it -> it.length == 1 && it[0].equals(TEST_GROUP)));
+    }
+
+    @Test
+    public void doNotSendLoggingToLogcatToClientWithoutRegisteredGroup() throws RemoteException {
+        final ProtoLogService service = new ProtoLogService();
+
+        final ProtoLogService.RegisterClientArgs args = new ProtoLogService.RegisterClientArgs()
+                .setGroups(new ProtoLogService.RegisterClientArgs.GroupConfig(TEST_GROUP, false));
+        service.registerClient(mMockClient, args);
+
+        Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isFalse();
+        service.enableProtoLogToLogcat(OTHER_TEST_GROUP);
+        Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isFalse();
+
+        Mockito.verify(mMockClient, never()).toggleLogcat(anyBoolean(), any());
+    }
+
+    @Test
+    public void handlesToggleToLogcatBeforeClientIsRegistered() throws RemoteException {
+        final ProtoLogService service = new ProtoLogService();
+
+        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));
+        service.registerClient(mMockClient, args);
+
+        Mockito.verify(mMockClient).toggleLogcat(eq(true),
+                Mockito.argThat(it -> it.length == 1 && it[0].equals(TEST_GROUP)));
+    }
+}
diff --git a/tests/Internal/src/com/android/internal/util/ParcellingTests.java b/tests/Internal/src/com/android/internal/util/ParcellingTests.java
index 65a3436..fb63422 100644
--- a/tests/Internal/src/com/android/internal/util/ParcellingTests.java
+++ b/tests/Internal/src/com/android/internal/util/ParcellingTests.java
@@ -18,6 +18,7 @@
 
 import android.os.Parcel;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.ravenwood.RavenwoodRule;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
@@ -26,6 +27,7 @@
 
 import com.android.internal.util.Parcelling.BuiltIn.ForInstant;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -38,6 +40,9 @@
 @RunWith(JUnit4.class)
 public class ParcellingTests {
 
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
     private Parcel mParcel = Parcel.obtain();
 
     @Test
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index 3f9016b..f43cf52 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -113,6 +113,7 @@
         "io/ZipArchive.cpp",
         "link/AutoVersioner.cpp",
         "link/FeatureFlagsFilter.cpp",
+        "link/FlagDisabledResourceRemover.cpp",
         "link/ManifestFixer.cpp",
         "link/NoDefaultResourceRemover.cpp",
         "link/PrivateAttributeMover.cpp",
@@ -189,6 +190,8 @@
         "integration-tests/CommandTests/**/*",
         "integration-tests/ConvertTest/**/*",
         "integration-tests/DumpTest/**/*",
+        ":resource-flagging-test-app-apk",
+        ":resource-flagging-test-app-r-java",
     ],
 }
 
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 9444dd9..1c85e9f 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -690,9 +690,7 @@
         resource_format = item_iter->second.format;
       }
 
-      // Don't bother parsing the item if it is behind a disabled flag
-      if (out_resource->flag_status != FlagStatus::Disabled &&
-          !ParseItem(parser, out_resource, resource_format)) {
+      if (!ParseItem(parser, out_resource, resource_format)) {
         return false;
       }
       return true;
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index 2e6ad13..b59b165 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -69,13 +69,8 @@
     return TestParse(str, ConfigDescription{});
   }
 
-  ::testing::AssertionResult TestParse(StringPiece str, ResourceParserOptions parserOptions) {
-    return TestParse(str, ConfigDescription{}, parserOptions);
-  }
-
-  ::testing::AssertionResult TestParse(
-      StringPiece str, const ConfigDescription& config,
-      ResourceParserOptions parserOptions = ResourceParserOptions()) {
+  ::testing::AssertionResult TestParse(StringPiece str, const ConfigDescription& config) {
+    ResourceParserOptions parserOptions;
     ResourceParser parser(context_->GetDiagnostics(), &table_, android::Source{"test"}, config,
                           parserOptions);
 
@@ -247,19 +242,6 @@
   EXPECT_FALSE(TestParse(R"(<string name="foo4" translatable="yes">Translate</string>)"));
 }
 
-TEST_F(ResourceParserTest, ParseStringBehindDisabledFlag) {
-  FeatureFlagProperties flag_properties(true, false);
-  ResourceParserOptions options;
-  options.feature_flag_values = {{"falseFlag", flag_properties}};
-  ASSERT_TRUE(TestParse(
-      R"(<string name="foo" android:featureFlag="falseFlag"
-              xmlns:android="http://schemas.android.com/apk/res/android">foo</string>)",
-      options));
-
-  String* str = test::GetValue<String>(&table_, "string/foo");
-  ASSERT_THAT(str, IsNull());
-}
-
 TEST_F(ResourceParserTest, IgnoreXliffTagsOtherThanG) {
   std::string input = R"(
       <string name="foo" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
diff --git a/tools/aapt2/cmd/Convert.cpp b/tools/aapt2/cmd/Convert.cpp
index c132792..6c3eae1 100644
--- a/tools/aapt2/cmd/Convert.cpp
+++ b/tools/aapt2/cmd/Convert.cpp
@@ -244,6 +244,10 @@
     return verbose_;
   }
 
+  void SetVerbose(bool verbose) {
+    verbose_ = verbose;
+  }
+
   int GetMinSdkVersion() override {
     return min_sdk_;
   }
@@ -388,6 +392,8 @@
   }
 
   Context context;
+  context.SetVerbose(verbose_);
+
   StringPiece path = args[0];
   unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(path, context.GetDiagnostics());
   if (apk == nullptr) {
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index 642a561..56f5288 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -57,6 +57,7 @@
 #include "java/ManifestClassGenerator.h"
 #include "java/ProguardRules.h"
 #include "link/FeatureFlagsFilter.h"
+#include "link/FlagDisabledResourceRemover.h"
 #include "link/Linkers.h"
 #include "link/ManifestFixer.h"
 #include "link/NoDefaultResourceRemover.h"
@@ -1840,11 +1841,57 @@
     return validate(attr->value);
   }
 
+  class FlagDisabledStringVisitor : public DescendingValueVisitor {
+   public:
+    using DescendingValueVisitor::Visit;
+
+    explicit FlagDisabledStringVisitor(android::StringPool& string_pool)
+        : string_pool_(string_pool) {
+    }
+
+    void Visit(RawString* value) override {
+      value->value = string_pool_.MakeRef("");
+    }
+
+    void Visit(String* value) override {
+      value->value = string_pool_.MakeRef("");
+    }
+
+    void Visit(StyledString* value) override {
+      value->value = string_pool_.MakeRef(android::StyleString{{""}, {}});
+    }
+
+   private:
+    DISALLOW_COPY_AND_ASSIGN(FlagDisabledStringVisitor);
+    android::StringPool& string_pool_;
+  };
+
   // Writes the AndroidManifest, ResourceTable, and all XML files referenced by the ResourceTable
   // to the IArchiveWriter.
   bool WriteApk(IArchiveWriter* writer, proguard::KeepSet* keep_set, xml::XmlResource* manifest,
                 ResourceTable* table) {
     TRACE_CALL();
+
+    FlagDisabledStringVisitor visitor(table->string_pool);
+
+    for (auto& package : table->packages) {
+      for (auto& type : package->types) {
+        for (auto& entry : type->entries) {
+          for (auto& config_value : entry->values) {
+            if (config_value->flag_status == FlagStatus::Disabled) {
+              config_value->value->Accept(&visitor);
+            }
+          }
+        }
+      }
+    }
+
+    if (!FlagDisabledResourceRemover{}.Consume(context_, table)) {
+      context_->GetDiagnostics()->Error(android::DiagMessage()
+                                        << "failed removing resources behind disabled flags");
+      return 1;
+    }
+
     const bool keep_raw_values = (context_->GetPackageType() == PackageType::kStaticLib)
                                  || options_.keep_raw_values;
     bool result = FlattenXml(context_, *manifest, kAndroidManifestPath, keep_raw_values,
@@ -2331,6 +2378,12 @@
       return 1;
     };
 
+    if (options_.generate_java_class_path || options_.generate_text_symbols_path) {
+      if (!GenerateJavaClasses()) {
+        return 1;
+      }
+    }
+
     if (!WriteApk(archive_writer.get(), &proguard_keep_set, manifest_xml.get(), &final_table_)) {
       return 1;
     }
@@ -2339,12 +2392,6 @@
       return 1;
     }
 
-    if (options_.generate_java_class_path || options_.generate_text_symbols_path) {
-      if (!GenerateJavaClasses()) {
-        return 1;
-      }
-    }
-
     if (!WriteProguardFile(options_.generate_proguard_rules_path, proguard_keep_set)) {
       return 1;
     }
diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp b/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp
new file mode 100644
index 0000000..5932271
--- /dev/null
+++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp
@@ -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 {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+    default_team: "trendy_team_android_resources",
+}
+
+genrule {
+    name: "resource-flagging-test-app-compile",
+    tools: ["aapt2"],
+    srcs: [
+        "res/values/bools.xml",
+        "res/values/bools2.xml",
+        "res/values/strings.xml",
+    ],
+    out: [
+        "values_bools.arsc.flat",
+        "values_bools2.arsc.flat",
+        "values_strings.arsc.flat",
+    ],
+    cmd: "$(location aapt2) compile $(in) -o $(genDir) " +
+        "--feature-flags test.package.falseFlag:ro=false,test.package.trueFlag:ro=true",
+}
+
+genrule {
+    name: "resource-flagging-test-app-apk",
+    tools: ["aapt2"],
+    // The first input file in the list must be the manifest
+    srcs: [
+        "AndroidManifest.xml",
+        ":resource-flagging-test-app-compile",
+    ],
+    out: [
+        "resapp.apk",
+    ],
+    cmd: "$(location aapt2) link -o $(out) --manifest $(in)",
+}
+
+genrule {
+    name: "resource-flagging-test-app-r-java",
+    tools: ["aapt2"],
+    // The first input file in the list must be the manifest
+    srcs: [
+        "AndroidManifest.xml",
+        ":resource-flagging-test-app-compile",
+    ],
+    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)",
+}
+
+java_genrule {
+    name: "resource-flagging-test-app-apk-as-resource",
+    srcs: [
+        ":resource-flagging-test-app-apk",
+    ],
+    out: ["apks_as_resources.res.zip"],
+    tools: ["soong_zip"],
+
+    cmd: "mkdir -p $(genDir)/res/raw && " +
+        "cp $(in) $(genDir)/res/raw/$$(basename $(in)) && " +
+        "$(location soong_zip) -o $(out) -C $(genDir)/res -D $(genDir)/res",
+}
diff --git a/core/tests/resourceflaggingtests/TestAppAndroidManifest.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/AndroidManifest.xml
similarity index 100%
rename from core/tests/resourceflaggingtests/TestAppAndroidManifest.xml
rename to tools/aapt2/integration-tests/FlaggedResourcesTest/AndroidManifest.xml
diff --git a/core/tests/resourceflaggingtests/flagged_resources_res/values/bools.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools.xml
similarity index 82%
rename from core/tests/resourceflaggingtests/flagged_resources_res/values/bools.xml
rename to tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools.xml
index 8d01465..3e094fb 100644
--- a/core/tests/resourceflaggingtests/flagged_resources_res/values/bools.xml
+++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools.xml
@@ -7,4 +7,6 @@
     <bool name="res2" android:featureFlag="test.package.trueFlag">true</bool>
 
     <bool name="res3">false</bool>
+
+    <bool name="res4" android:featureFlag="test.package.falseFlag">true</bool>
 </resources>
\ No newline at end of file
diff --git a/core/tests/resourceflaggingtests/flagged_resources_res/values/bools2.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools2.xml
similarity index 100%
rename from core/tests/resourceflaggingtests/flagged_resources_res/values/bools2.xml
rename to tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools2.xml
diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/strings.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/strings.xml
new file mode 100644
index 0000000..5c0fca1
--- /dev/null
+++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/strings.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <string name="str">plain string</string>
+
+    <string name="str1" android:featureFlag="test.package.falseFlag">DONTFIND</string>
+</resources>
\ No newline at end of file
diff --git a/tools/aapt2/link/FlagDisabledResourceRemover.cpp b/tools/aapt2/link/FlagDisabledResourceRemover.cpp
new file mode 100644
index 0000000..e3289e2
--- /dev/null
+++ b/tools/aapt2/link/FlagDisabledResourceRemover.cpp
@@ -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.
+ */
+
+#include "link/FlagDisabledResourceRemover.h"
+
+#include <algorithm>
+
+#include "ResourceTable.h"
+
+using android::ConfigDescription;
+
+namespace aapt {
+
+static bool KeepResourceEntry(const std::unique_ptr<ResourceEntry>& entry) {
+  if (entry->values.empty()) {
+    return true;
+  }
+  const auto end_iter = entry->values.end();
+  const auto remove_iter =
+      std::stable_partition(entry->values.begin(), end_iter,
+                            [](const std::unique_ptr<ResourceConfigValue>& value) -> bool {
+                              return value->flag_status != FlagStatus::Disabled;
+                            });
+
+  bool keep = remove_iter != entry->values.begin();
+
+  entry->values.erase(remove_iter, end_iter);
+  return keep;
+}
+
+bool FlagDisabledResourceRemover::Consume(IAaptContext* context, ResourceTable* table) {
+  for (auto& pkg : table->packages) {
+    for (auto& type : pkg->types) {
+      const auto end_iter = type->entries.end();
+      const auto remove_iter = std::stable_partition(
+          type->entries.begin(), end_iter, [](const std::unique_ptr<ResourceEntry>& entry) -> bool {
+            return KeepResourceEntry(entry);
+          });
+
+      type->entries.erase(remove_iter, end_iter);
+    }
+  }
+  return true;
+}
+
+}  // namespace aapt
\ No newline at end of file
diff --git a/tools/aapt2/link/FlagDisabledResourceRemover.h b/tools/aapt2/link/FlagDisabledResourceRemover.h
new file mode 100644
index 0000000..2db2cb4
--- /dev/null
+++ b/tools/aapt2/link/FlagDisabledResourceRemover.h
@@ -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.
+ */
+
+#pragma once
+
+#include "android-base/macros.h"
+#include "process/IResourceTableConsumer.h"
+
+namespace aapt {
+
+// Removes any resource that are behind disabled flags.
+class FlagDisabledResourceRemover : public IResourceTableConsumer {
+ public:
+  FlagDisabledResourceRemover() = default;
+
+  bool Consume(IAaptContext* context, ResourceTable* table) override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(FlagDisabledResourceRemover);
+};
+
+}  // namespace aapt
diff --git a/tools/aapt2/link/FlaggedResources_test.cpp b/tools/aapt2/link/FlaggedResources_test.cpp
new file mode 100644
index 0000000..c901b58
--- /dev/null
+++ b/tools/aapt2/link/FlaggedResources_test.cpp
@@ -0,0 +1,101 @@
+/*
+ * 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.
+ */
+
+#include "LoadedApk.h"
+#include "cmd/Dump.h"
+#include "io/StringStream.h"
+#include "test/Test.h"
+#include "text/Printer.h"
+
+using ::aapt::io::StringOutputStream;
+using ::aapt::text::Printer;
+using testing::Eq;
+using testing::Ne;
+
+namespace aapt {
+
+using FlaggedResourcesTest = CommandTestFixture;
+
+static android::NoOpDiagnostics noop_diag;
+
+void DumpStringPoolToString(LoadedApk* loaded_apk, std::string* output) {
+  StringOutputStream output_stream(output);
+  Printer printer(&output_stream);
+
+  DumpStringsCommand command(&printer, &noop_diag);
+  ASSERT_EQ(command.Dump(loaded_apk), 0);
+  output_stream.Flush();
+}
+
+void DumpResourceTableToString(LoadedApk* loaded_apk, std::string* output) {
+  StringOutputStream output_stream(output);
+  Printer printer(&output_stream);
+
+  DumpTableCommand command(&printer, &noop_diag);
+  ASSERT_EQ(command.Dump(loaded_apk), 0);
+  output_stream.Flush();
+}
+
+void DumpChunksToString(LoadedApk* loaded_apk, std::string* output) {
+  StringOutputStream output_stream(output);
+  Printer printer(&output_stream);
+
+  DumpChunks command(&printer, &noop_diag);
+  ASSERT_EQ(command.Dump(loaded_apk), 0);
+  output_stream.Flush();
+}
+
+TEST_F(FlaggedResourcesTest, DisabledStringRemovedFromPool) {
+  auto apk_path = file::BuildPath({android::base::GetExecutableDirectory(), "resapp.apk"});
+  auto loaded_apk = LoadedApk::LoadApkFromPath(apk_path, &noop_diag);
+
+  std::string output;
+  DumpStringPoolToString(loaded_apk.get(), &output);
+
+  std::string excluded = "DONTFIND";
+  ASSERT_EQ(output.find(excluded), std::string::npos);
+}
+
+TEST_F(FlaggedResourcesTest, DisabledResourcesRemovedFromTable) {
+  auto apk_path = file::BuildPath({android::base::GetExecutableDirectory(), "resapp.apk"});
+  auto loaded_apk = LoadedApk::LoadApkFromPath(apk_path, &noop_diag);
+
+  std::string output;
+  DumpResourceTableToString(loaded_apk.get(), &output);
+}
+
+TEST_F(FlaggedResourcesTest, DisabledResourcesRemovedFromTableChunks) {
+  auto apk_path = file::BuildPath({android::base::GetExecutableDirectory(), "resapp.apk"});
+  auto loaded_apk = LoadedApk::LoadApkFromPath(apk_path, &noop_diag);
+
+  std::string output;
+  DumpChunksToString(loaded_apk.get(), &output);
+
+  ASSERT_EQ(output.find("res4"), std::string::npos);
+  ASSERT_EQ(output.find("str1"), std::string::npos);
+}
+
+TEST_F(FlaggedResourcesTest, DisabledResourcesInRJava) {
+  auto r_path = file::BuildPath({android::base::GetExecutableDirectory(), "resource-flagging-java",
+                                 "com", "android", "intenal", "flaggedresources", "R.java"});
+  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 str1"), std::string::npos);
+}
+
+}  // namespace aapt
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
index 73cfd3f..36bfbef 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
@@ -58,17 +58,19 @@
 
         // Dump the classes, if specified.
         options.inputJarDumpFile.ifSet {
-            PrintWriter(it).use { pw -> allClasses.dump(pw) }
-            log.i("Dump file created at $it")
+            log.iTime("Dump file created at $it") {
+                PrintWriter(it).use { pw -> allClasses.dump(pw) }
+            }
         }
 
         options.inputJarAsKeepAllFile.ifSet {
-            PrintWriter(it).use {
-                pw -> allClasses.forEach {
-                    classNode -> printAsTextPolicy(pw, classNode)
+            log.iTime("Dump file created at $it") {
+                PrintWriter(it).use { pw ->
+                    allClasses.forEach { classNode ->
+                        printAsTextPolicy(pw, classNode)
+                    }
                 }
             }
-            log.i("Dump file created at $it")
         }
 
         // Build the filters.
@@ -85,20 +87,24 @@
                 errors,
                 stats,
                 policyFileRemapper,
+                options.numShards.get,
+                options.shard.get,
         )
 
         // Dump statistics, if specified.
         options.statsFile.ifSet {
-            PrintWriter(it).use { pw -> stats.dumpOverview(pw) }
-            log.i("Dump file created at $it")
+            log.iTime("Dump file created at $it") {
+                PrintWriter(it).use { pw -> stats.dumpOverview(pw) }
+            }
         }
         options.apiListFile.ifSet {
-            PrintWriter(it).use { pw ->
-                // TODO, when dumping a jar that's not framework-minus-apex.jar, we need to feed
-                // framework-minus-apex.jar so that we can dump inherited methods from it.
-                ApiDumper(pw, allClasses, null, filter).dump()
+            log.iTime("API list file created at $it") {
+                PrintWriter(it).use { pw ->
+                    // TODO, when dumping a jar that's not framework-minus-apex.jar, we need to feed
+                    // framework-minus-apex.jar so that we can dump inherited methods from it.
+                    ApiDumper(pw, allClasses, null, filter).dump()
+                }
             }
-            log.i("API list file created at $it")
         }
     }
 
@@ -213,35 +219,54 @@
             errors: HostStubGenErrors,
             stats: HostStubGenStats,
             remapper: Remapper?,
+            numShards: Int,
+            shard: Int,
             ) {
         log.i("Converting %s into [stub: %s, impl: %s] ...", inJar, outStubJar, outImplJar)
         log.i("ASM CheckClassAdapter is %s", if (enableChecker) "enabled" else "disabled")
 
-        val start = System.currentTimeMillis()
+        log.iTime("Transforming jar") {
+            val packageRedirector = PackageRedirectRemapper(options.packageRedirects)
 
-        val packageRedirector = PackageRedirectRemapper(options.packageRedirects)
+            var itemIndex = 0
+            var numItemsProcessed = 0
+            var numItems = -1 // == Unknown
 
-        log.withIndent {
-            // Open the input jar file and process each entry.
-            ZipFile(inJar).use { inZip ->
-                maybeWithZipOutputStream(outStubJar) { stubOutStream ->
-                    maybeWithZipOutputStream(outImplJar) { implOutStream ->
-                        val inEntries = inZip.entries()
-                        while (inEntries.hasMoreElements()) {
-                            val entry = inEntries.nextElement()
-                            convertSingleEntry(inZip, entry, stubOutStream, implOutStream,
+            log.withIndent {
+                // Open the input jar file and process each entry.
+                ZipFile(inJar).use { inZip ->
+
+                    numItems = inZip.size()
+                    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)
+                                    enableChecker, classes, errors, stats
+                                )
+                                numItemsProcessed++
+                            }
+                            log.i("Converted all entries.")
                         }
-                        log.i("Converted all entries.")
                     }
+                    outStubJar?.let { log.i("Created stub: $it") }
+                    outImplJar?.let { log.i("Created impl: $it") }
                 }
-                outStubJar?.let { log.i("Created stub: $it") }
-                outImplJar?.let { log.i("Created impl: $it") }
             }
+            log.i("%d / %d item(s) processed.", numItemsProcessed, numItems)
         }
-        val end = System.currentTimeMillis()
-        log.i("Done transforming the jar in %.1f second(s).", (end - start) / 1000.0)
     }
 
     private fun <T> maybeWithZipOutputStream(filename: String?, block: (ZipOutputStream?) -> T): T {
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt
index 18065ba..ee4a06f 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt
@@ -185,6 +185,16 @@
         println(LogLevel.Debug, format, *args)
     }
 
+    inline fun <T> iTime(message: String, block: () -> T): T {
+        val start = System.currentTimeMillis()
+        val ret = block()
+        val end = System.currentTimeMillis()
+
+        log.i("%s: took %.1f second(s).", message, (end - start) / 1000.0)
+
+        return ret
+    }
+
     inline fun forVerbose(block: () -> Unit) {
         if (isEnabled(LogLevel.Verbose)) {
             block()
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
index e192516..2f833a8 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
@@ -112,6 +112,9 @@
         var statsFile: SetOnce<String?> = SetOnce(null),
 
         var apiListFile: SetOnce<String?> = SetOnce(null),
+
+        var numShards: SetOnce<Int> = SetOnce(1),
+        var shard: SetOnce<Int> = SetOnce(0),
 ) {
     companion object {
 
@@ -162,6 +165,13 @@
                 fun SetOnce<String?>.setNextStringArg(): String = nextArg().also { this.set(it) }
                 fun MutableSet<String>.addUniqueAnnotationArg(): String =
                         nextArg().also { this += ensureUniqueAnnotation(it) }
+                fun SetOnce<Int>.setNextIntArg(): String = nextArg().also {
+                    try {
+                        this.set(it.toInt())
+                    } catch (e: NumberFormatException) {
+                        throw ArgumentsException("Invalid integer for $arg: $it")
+                    }
+                }
 
                 try {
                     when (arg) {
@@ -259,6 +269,9 @@
                         "--stats-file" -> ret.statsFile.setNextStringArg()
                         "--supported-api-list-file" -> ret.apiListFile.setNextStringArg()
 
+                        "--num-shards" -> ret.numShards.setNextIntArg()
+                        "--shard-index" -> ret.shard.setNextIntArg()
+
                         else -> throw ArgumentsException("Unknown option: $arg")
                     }
                 } catch (e: SetOnce.SetMoreThanOnceException) {
@@ -396,6 +409,8 @@
               enableNonStubMethodCallDetection=$enableNonStubMethodCallDetection,
               statsFile=$statsFile,
               apiListFile=$apiListFile,
+              numShards=$numShards,
+              shard=$shard,
             }
             """.trimIndent()
     }
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt
index 92906a7..2607df6 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt
@@ -184,49 +184,50 @@
          * Load all the classes, without code.
          */
         fun loadClassStructures(inJar: String): ClassNodes {
-            log.i("Reading class structure from $inJar ...")
-            val start = System.currentTimeMillis()
+            log.iTime("Reading class structure from $inJar") {
+                val allClasses = ClassNodes()
 
-            val allClasses = ClassNodes()
+                log.withIndent {
+                    ZipFile(inJar).use { inZip ->
+                        val inEntries = inZip.entries()
 
-            log.withIndent {
-                ZipFile(inJar).use { inZip ->
-                    val inEntries = inZip.entries()
+                        while (inEntries.hasMoreElements()) {
+                            val entry = inEntries.nextElement()
 
-                    while (inEntries.hasMoreElements()) {
-                        val entry = inEntries.nextElement()
-
-                        BufferedInputStream(inZip.getInputStream(entry)).use { bis ->
-                            if (entry.name.endsWith(".class")) {
-                                val cr = ClassReader(bis)
-                                val cn = ClassNode()
-                                cr.accept(cn, ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG
-                                        or ClassReader.SKIP_FRAMES)
-                                if (!allClasses.addClass(cn)) {
-                                    log.w("Duplicate class found: ${cn.name}")
-                                }
-                            } else if (entry.name.endsWith(".dex")) {
-                                // Seems like it's an ART jar file. We can't process it.
-                                // It's a fatal error.
-                                throw InvalidJarFileException(
-                                    "$inJar is not a desktop jar file. It contains a *.dex file.")
-                            } else {
-                                // Unknown file type. Skip.
-                                while (bis.available() > 0) {
-                                    bis.skip((1024 * 1024).toLong())
+                            BufferedInputStream(inZip.getInputStream(entry)).use { bis ->
+                                if (entry.name.endsWith(".class")) {
+                                    val cr = ClassReader(bis)
+                                    val cn = ClassNode()
+                                    cr.accept(
+                                        cn, ClassReader.SKIP_CODE
+                                                or ClassReader.SKIP_DEBUG
+                                                or ClassReader.SKIP_FRAMES
+                                    )
+                                    if (!allClasses.addClass(cn)) {
+                                        log.w("Duplicate class found: ${cn.name}")
+                                    }
+                                } else if (entry.name.endsWith(".dex")) {
+                                    // Seems like it's an ART jar file. We can't process it.
+                                    // It's a fatal error.
+                                    throw InvalidJarFileException(
+                                        "$inJar is not a desktop jar file."
+                                        + " It contains a *.dex file."
+                                    )
+                                } else {
+                                    // Unknown file type. Skip.
+                                    while (bis.available() > 0) {
+                                        bis.skip((1024 * 1024).toLong())
+                                    }
                                 }
                             }
                         }
                     }
                 }
+                if (allClasses.size == 0) {
+                    log.w("$inJar contains no *.class files.")
+                }
+                return allClasses
             }
-            if (allClasses.size == 0) {
-                log.w("$inJar contains no *.class files.")
-            }
-
-            val end = System.currentTimeMillis()
-            log.i("Done reading class structure in %.1f second(s).", (end - start) / 1000.0)
-            return allClasses
         }
     }
 }
\ No newline at end of file
diff --git a/tools/systemfeatures/Android.bp b/tools/systemfeatures/Android.bp
new file mode 100644
index 0000000..2cebfe9
--- /dev/null
+++ b/tools/systemfeatures/Android.bp
@@ -0,0 +1,63 @@
+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"],
+}
+
+java_library_host {
+    name: "systemfeatures-gen-lib",
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
+    static_libs: [
+        "guava",
+        "javapoet",
+    ],
+}
+
+java_binary_host {
+    name: "systemfeatures-gen-tool",
+    main_class: "com.android.systemfeatures.SystemFeaturesGenerator",
+    static_libs: ["systemfeatures-gen-lib"],
+}
+
+// TODO(b/203143243): Add golden diff test for generated sources.
+// Functional runtime behavior is covered in systemfeatures-gen-tests.
+genrule {
+    name: "systemfeatures-gen-tests-srcs",
+    cmd: "$(location systemfeatures-gen-tool) com.android.systemfeatures.RwNoFeatures --readonly=false > $(location RwNoFeatures.java) && " +
+        "$(location systemfeatures-gen-tool) com.android.systemfeatures.RoNoFeatures --readonly=true > $(location RoNoFeatures.java) && " +
+        "$(location systemfeatures-gen-tool) com.android.systemfeatures.RwFeatures --readonly=false --feature=WATCH:1 --feature=WIFI:0 --feature=VULKAN:-1 --feature=AUTO: > $(location RwFeatures.java) && " +
+        "$(location systemfeatures-gen-tool) com.android.systemfeatures.RoFeatures --readonly=true --feature=WATCH:1 --feature=WIFI:0 --feature=VULKAN:-1 --feature=AUTO: > $(location RoFeatures.java)",
+    out: [
+        "RwNoFeatures.java",
+        "RoNoFeatures.java",
+        "RwFeatures.java",
+        "RoFeatures.java",
+    ],
+    tools: ["systemfeatures-gen-tool"],
+}
+
+java_test_host {
+    name: "systemfeatures-gen-tests",
+    test_suites: ["general-tests"],
+    srcs: [
+        "tests/**/*.java",
+        ":systemfeatures-gen-tests-srcs",
+    ],
+    test_options: {
+        unit_test: true,
+    },
+    static_libs: [
+        "aconfig-annotations-lib",
+        "framework-annotations-lib",
+        "junit",
+        "objenesis",
+        "mockito",
+        "truth",
+    ],
+}
diff --git a/tools/systemfeatures/OWNERS b/tools/systemfeatures/OWNERS
new file mode 100644
index 0000000..66c8506
--- /dev/null
+++ b/tools/systemfeatures/OWNERS
@@ -0,0 +1 @@
+include /PERFORMANCE_OWNERS
diff --git a/tools/systemfeatures/README.md b/tools/systemfeatures/README.md
new file mode 100644
index 0000000..5836f81
--- /dev/null
+++ b/tools/systemfeatures/README.md
@@ -0,0 +1,11 @@
+# Build-time system feature support
+
+## Overview
+
+System features exposed from `PackageManager` are defined and aggregated as
+`<feature>` xml attributes across various partitions, and are currently queried
+at runtime through the framework. This directory contains tooling that will
+support *build-time* queries of select system features, enabling optimizations
+like code stripping and conditionally dependencies when so configured.
+
+### TODO(b/203143243): Expand readme after landing codegen.
diff --git a/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt b/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt
new file mode 100644
index 0000000..9bfda45
--- /dev/null
+++ b/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemfeatures
+
+import com.google.common.base.CaseFormat
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.JavaFile
+import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.TypeSpec
+import javax.lang.model.element.Modifier
+
+/*
+ * Simple Java code generator that takes as input a list of defined features and generates an
+ * accessory class based on the provided versions.
+ *
+ * <p>Example:
+ *
+ * <pre>
+ *   <cmd> com.foo.RoSystemFeatures --readonly=true \
+ *           --feature=WATCH:0 --feature=AUTOMOTIVE: --feature=VULKAN:9348
+ * </pre>
+ *
+ * This generates a class that has the following signature:
+ *
+ * <pre>
+ * package com.foo;
+ * public final class RoSystemFeatures {
+ *     @AssumeTrueForR8
+ *     public static boolean hasFeatureWatch(Context context);
+ *     @AssumeFalseForR8
+ *     public static boolean hasFeatureAutomotive(Context context);
+ *     @AssumeTrueForR8
+ *     public static boolean hasFeatureVulkan(Context context);
+ *     public static Boolean maybeHasFeature(String feature, int version);
+ * }
+ * </pre>
+ */
+object SystemFeaturesGenerator {
+    private const val FEATURE_ARG = "--feature="
+    private const val READONLY_ARG = "--readonly="
+    private val PACKAGEMANAGER_CLASS = ClassName.get("android.content.pm", "PackageManager")
+    private val CONTEXT_CLASS = ClassName.get("android.content", "Context")
+    private val ASSUME_TRUE_CLASS =
+        ClassName.get("com.android.aconfig.annotations", "AssumeTrueForR8")
+    private val ASSUME_FALSE_CLASS =
+        ClassName.get("com.android.aconfig.annotations", "AssumeFalseForR8")
+
+    private fun usage() {
+        println("Usage: SystemFeaturesGenerator <outputClassName> [options]")
+        println(" Options:")
+        println("  --readonly=true|false    Whether to encode features as build-time constants")
+        println("  --feature=\$NAME:\$VER   A feature+version pair (blank version == disabled)")
+    }
+
+    /** Main entrypoint for build-time system feature codegen. */
+    @JvmStatic
+    fun main(args: Array<String>) {
+        if (args.size < 1) {
+            usage()
+            return
+        }
+
+        var readonly = false
+        var outputClassName: ClassName? = null
+        val features = mutableListOf<FeatureInfo>()
+        for (arg in args) {
+            when {
+                arg.startsWith(READONLY_ARG) ->
+                    readonly = arg.substring(READONLY_ARG.length).toBoolean()
+                arg.startsWith(FEATURE_ARG) -> {
+                    features.add(parseFeatureArg(arg))
+                }
+                else -> outputClassName = ClassName.bestGuess(arg)
+            }
+        }
+
+        outputClassName
+            ?: run {
+                println("Output class name must be provided.")
+                usage()
+                return
+            }
+
+        val classBuilder =
+            TypeSpec.classBuilder(outputClassName)
+                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
+                .addJavadoc("@hide")
+
+        addFeatureMethodsToClass(classBuilder, readonly, features)
+        addMaybeFeatureMethodToClass(classBuilder, readonly, features)
+
+        // TODO(b/203143243): Add validation of build vs runtime values to ensure consistency.
+        JavaFile.builder(outputClassName.packageName(), classBuilder.build())
+            .build()
+            .writeTo(System.out)
+    }
+
+    /*
+     * Parses a feature argument of the form "--feature=$NAME:$VER", where "$VER" is optional.
+     *   * "--feature=WATCH:0" -> Feature enabled w/ version 0 (default version when enabled)
+     *   * "--feature=WATCH:7" -> Feature enabled w/ version 7
+     *   * "--feature=WATCH:"  -> Feature disabled
+     */
+    private fun parseFeatureArg(arg: String): FeatureInfo {
+        val featureArgs = arg.substring(FEATURE_ARG.length).split(":")
+        val name = featureArgs[0].let { if (!it.startsWith("FEATURE_")) "FEATURE_$it" else it }
+        val version = featureArgs.getOrNull(1)?.toIntOrNull()
+        return FeatureInfo(name, version)
+    }
+
+    /*
+     * Adds per-feature query methods to the class with the form:
+     * {@code public static boolean hasFeatureX(Context context)},
+     * returning the fallback value from PackageManager if not readonly.
+     */
+    private fun addFeatureMethodsToClass(
+        builder: TypeSpec.Builder,
+        readonly: Boolean,
+        features: List<FeatureInfo>
+    ) {
+        for (feature in features) {
+            // Turn "FEATURE_FOO" into "hasFeatureFoo".
+            val methodName =
+                "has" + CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, feature.name)
+            val methodBuilder =
+                MethodSpec.methodBuilder(methodName)
+                    .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
+                    .returns(Boolean::class.java)
+                    .addParameter(CONTEXT_CLASS, "context")
+
+            if (readonly) {
+                val featureEnabled = compareValues(feature.version, 0) >= 0
+                methodBuilder.addAnnotation(
+                    if (featureEnabled) ASSUME_TRUE_CLASS else ASSUME_FALSE_CLASS
+                )
+                methodBuilder.addStatement("return $featureEnabled")
+            } else {
+                methodBuilder.addStatement(
+                    "return hasFeatureFallback(context, \$T.\$N)",
+                    PACKAGEMANAGER_CLASS,
+                    feature.name
+                )
+            }
+            builder.addMethod(methodBuilder.build())
+        }
+
+        if (!readonly) {
+            builder.addMethod(
+                MethodSpec.methodBuilder("hasFeatureFallback")
+                    .addModifiers(Modifier.PRIVATE, Modifier.STATIC)
+                    .returns(Boolean::class.java)
+                    .addParameter(CONTEXT_CLASS, "context")
+                    .addParameter(String::class.java, "featureName")
+                    .addStatement(
+                        "return context.getPackageManager().hasSystemFeature(featureName, 0)"
+                    )
+                    .build()
+            )
+        }
+    }
+
+    /*
+     * Adds a generic query method to the class with the form: {@code public static boolean
+     * maybeHasFeature(String featureName, int version)}, returning null if the feature version is
+     * undefined or not readonly.
+     *
+     * This method is useful for internal usage within the framework, e.g., from the implementation
+     * of {@link android.content.pm.PackageManager#hasSystemFeature(Context)}, when we may only
+     * want a valid result if it's defined as readonly, and we want a custom fallback otherwise
+     * (e.g., to the existing runtime binder query).
+     */
+    private fun addMaybeFeatureMethodToClass(
+        builder: TypeSpec.Builder,
+        readonly: Boolean,
+        features: List<FeatureInfo>
+    ) {
+        val methodBuilder =
+            MethodSpec.methodBuilder("maybeHasFeature")
+                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
+                .addAnnotation(ClassName.get("android.annotation", "Nullable"))
+                .returns(Boolean::class.javaObjectType) // Use object type for nullability
+                .addParameter(String::class.java, "featureName")
+                .addParameter(Int::class.java, "version")
+
+        if (readonly) {
+            methodBuilder.beginControlFlow("switch (featureName)")
+            for (feature in features) {
+                methodBuilder.addCode("case \$T.\$N: ", PACKAGEMANAGER_CLASS, feature.name)
+                if (feature.version != null) {
+                    methodBuilder.addStatement("return \$L >= version", feature.version)
+                } else {
+                    methodBuilder.addStatement("return false")
+                }
+            }
+            methodBuilder.addCode("default: ")
+            methodBuilder.addStatement("break")
+            methodBuilder.endControlFlow()
+        }
+        methodBuilder.addStatement("return null")
+        builder.addMethod(methodBuilder.build())
+    }
+
+    private data class FeatureInfo(val name: String, val version: Int?)
+}
diff --git a/telephony/java/android/telephony/satellite/stub/ProvisionSubscriberId.aidl b/tools/systemfeatures/tests/Context.java
similarity index 72%
copy from telephony/java/android/telephony/satellite/stub/ProvisionSubscriberId.aidl
copy to tools/systemfeatures/tests/Context.java
index 460de8c..630bc07 100644
--- a/telephony/java/android/telephony/satellite/stub/ProvisionSubscriberId.aidl
+++ b/tools/systemfeatures/tests/Context.java
@@ -14,18 +14,14 @@
  * limitations under the License.
  */
 
-package android.telephony.satellite.stub;
+package android.content;
 
-/**
- * {@hide}
- */
-parcelable ProvisionSubscriberId {
-    /** provision subscriberId */
-    String subscriberId;
+import android.content.pm.PackageManager;
 
-    /** carrier id */
-    int mCarrierId;
-
-    /** apn */
-    String mNiddApn;
+/** Stub for testing. */
+public class Context {
+    /** @hide */
+    public PackageManager getPackageManager() {
+        return null;
+    }
 }
diff --git a/tools/systemfeatures/tests/PackageManager.java b/tools/systemfeatures/tests/PackageManager.java
new file mode 100644
index 0000000..645d500
--- /dev/null
+++ b/tools/systemfeatures/tests/PackageManager.java
@@ -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 android.content.pm;
+
+/** Stub for testing */
+public class PackageManager {
+    public static final String FEATURE_AUTO = "automotive";
+    public static final String FEATURE_VULKAN = "vulkan";
+    public static final String FEATURE_WATCH = "watch";
+    public static final String FEATURE_WIFI = "wifi";
+
+    /** @hide */
+    public boolean hasSystemFeature(String featureName, int version) {
+        return false;
+    }
+}
diff --git a/tools/systemfeatures/tests/SystemFeaturesGeneratorTest.java b/tools/systemfeatures/tests/SystemFeaturesGeneratorTest.java
new file mode 100644
index 0000000..547d2cb
--- /dev/null
+++ b/tools/systemfeatures/tests/SystemFeaturesGeneratorTest.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemfeatures;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@RunWith(JUnit4.class)
+public class SystemFeaturesGeneratorTest {
+
+    @Rule public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock private Context mContext;
+    @Mock private PackageManager mPackageManager;
+
+    @Before
+    public void setUp() {
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+    }
+
+    @Test
+    public void testReadonlyDisabledNoDefinedFeatures() {
+        // Always report null for conditional queries if readonly codegen is disabled.
+        assertThat(RwNoFeatures.maybeHasFeature(PackageManager.FEATURE_WATCH, 0)).isNull();
+        assertThat(RwNoFeatures.maybeHasFeature(PackageManager.FEATURE_WIFI, 0)).isNull();
+        assertThat(RwNoFeatures.maybeHasFeature(PackageManager.FEATURE_VULKAN, 0)).isNull();
+        assertThat(RwNoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, 0)).isNull();
+        assertThat(RwNoFeatures.maybeHasFeature("com.arbitrary.feature", 0)).isNull();
+    }
+
+    @Test
+    public void testReadonlyNoDefinedFeatures() {
+        // If no features are explicitly declared as readonly available, always report
+        // null for conditional queries.
+        assertThat(RoNoFeatures.maybeHasFeature(PackageManager.FEATURE_WATCH, 0)).isNull();
+        assertThat(RoNoFeatures.maybeHasFeature(PackageManager.FEATURE_WIFI, 0)).isNull();
+        assertThat(RoNoFeatures.maybeHasFeature(PackageManager.FEATURE_VULKAN, 0)).isNull();
+        assertThat(RoNoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, 0)).isNull();
+        assertThat(RoNoFeatures.maybeHasFeature("com.arbitrary.feature", 0)).isNull();
+    }
+
+    @Test
+    public void testReadonlyDisabledWithDefinedFeatures() {
+        // Always fall back to the PackageManager for defined, explicit features queries.
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH, 0)).thenReturn(true);
+        assertThat(RwFeatures.hasFeatureWatch(mContext)).isTrue();
+
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH, 0)).thenReturn(false);
+        assertThat(RwFeatures.hasFeatureWatch(mContext)).isFalse();
+
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI, 0)).thenReturn(true);
+        assertThat(RwFeatures.hasFeatureWifi(mContext)).isTrue();
+
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_VULKAN, 0)).thenReturn(false);
+        assertThat(RwFeatures.hasFeatureVulkan(mContext)).isFalse();
+
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTO, 0)).thenReturn(false);
+        assertThat(RwFeatures.hasFeatureAuto(mContext)).isFalse();
+
+        // For defined and undefined features, conditional queries should report null (unknown).
+        assertThat(RwFeatures.maybeHasFeature(PackageManager.FEATURE_WATCH, 0)).isNull();
+        assertThat(RwFeatures.maybeHasFeature(PackageManager.FEATURE_WIFI, 0)).isNull();
+        assertThat(RwFeatures.maybeHasFeature(PackageManager.FEATURE_VULKAN, 0)).isNull();
+        assertThat(RwFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, 0)).isNull();
+        assertThat(RwFeatures.maybeHasFeature("com.arbitrary.feature", 0)).isNull();
+    }
+
+    @Test
+    public void testReadonlyWithDefinedFeatures() {
+        // Always use the build-time feature version for defined, explicit feature queries, never
+        // falling back to the runtime query.
+        assertThat(RoFeatures.hasFeatureWatch(mContext)).isTrue();
+        assertThat(RoFeatures.hasFeatureWifi(mContext)).isTrue();
+        assertThat(RoFeatures.hasFeatureVulkan(mContext)).isFalse();
+        assertThat(RoFeatures.hasFeatureAuto(mContext)).isFalse();
+        verify(mPackageManager, never()).hasSystemFeature(anyString(), anyInt());
+
+        // For defined feature types, conditional queries should reflect the build-time versions.
+        // VERSION=1
+        assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_WATCH, -1)).isTrue();
+        assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_WATCH, 0)).isTrue();
+        assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_WATCH, 100)).isFalse();
+
+        // VERSION=0
+        assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_WIFI, -1)).isTrue();
+        assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_WIFI, 0)).isTrue();
+        assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_WIFI, 100)).isFalse();
+
+        // VERSION=-1
+        assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_VULKAN, -1)).isTrue();
+        assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_VULKAN, 0)).isFalse();
+        assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_VULKAN, 100)).isFalse();
+
+        // DISABLED
+        assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, -1)).isFalse();
+        assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, 0)).isFalse();
+        assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, 100)).isFalse();
+
+        // For undefined types, conditional queries should report null (unknown).
+        assertThat(RoFeatures.maybeHasFeature("com.arbitrary.feature", -1)).isNull();
+        assertThat(RoFeatures.maybeHasFeature("com.arbitrary.feature", 0)).isNull();
+        assertThat(RoFeatures.maybeHasFeature("com.arbitrary.feature", 100)).isNull();
+    }
+}