Merge "Remove ConstraintBp Flag" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 3620a11..4e34b63 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -172,6 +172,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 +1109,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"],
diff --git a/Ravenwood.bp b/Ravenwood.bp
index 7faa33f..5f32ba0 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -25,13 +25,15 @@
     visibility: ["//visibility:private"],
 }
 
-// Generate the stub/impl from framework-all, with hidden APIs.
+// 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.
+// 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"],
     srcs: [
         ":framework-minus-apex-for-hoststubgen",
@@ -41,148 +43,101 @@
     ],
     out: [
         "ravenwood.jar",
-
-        // Following files are created just as FYI.
-        "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",
     ],
-    visibility: ["//visibility:private"],
 }
 
+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: "$(location hoststubgen) " +
-        "--num-shards 6 --shard-index 0 " + // Only this line differs
-
-        "@$(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) ",
+    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: "$(location hoststubgen) " +
-        "--num-shards 6 --shard-index 1 " + // Only this line differs
-
-        "@$(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) ",
+    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: "$(location hoststubgen) " +
-        "--num-shards 6 --shard-index 2 " + // Only this line differs
-
-        "@$(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) ",
+    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: "$(location hoststubgen) " +
-        "--num-shards 6 --shard-index 3 " + // Only this line differs
-
-        "@$(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) ",
+    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: "$(location hoststubgen) " +
-        "--num-shards 6 --shard-index 4 " + // Only this line differs
-
-        "@$(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) ",
+    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: "$(location hoststubgen) " +
-        "--num-shards 6 --shard-index 5 " + // Only this line differs
+    cmd: framework_minus_apex_cmd + " --num-shards 10 --shard-index 5",
+}
 
-        "@$(location :ravenwood-standard-options) " +
+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",
+}
 
-        "--debug-log $(location hoststubgen_framework-minus-apex.log) " +
+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) " +
 
-        "--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) " +
+        "--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) ",
+    out: [
+        "hoststubgen_framework-minus-apex_keep_all.txt",
+        "hoststubgen_framework-minus-apex_dump.txt",
+        "hoststubgen_framework-minus-apex_stats.csv",
+        "hoststubgen_framework-minus-apex_apis.csv",
+    ],
 }
 
 // Marge all the sharded jars
@@ -198,73 +153,16 @@
         ":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",
     ],
 }
 
-// Merge the sharded text files
-genrule {
-    name: "hoststubgen_framework-minus-apex_stats.csv",
-    defaults: ["ravenwood-internal-only-visibility-genrule"],
-    cmd: "cat $(in) > $(out)",
-    srcs: [
-        ":framework-minus-apex.ravenwood-base_X0{hoststubgen_framework-minus-apex_stats.csv}",
-        ":framework-minus-apex.ravenwood-base_X1{hoststubgen_framework-minus-apex_stats.csv}",
-        ":framework-minus-apex.ravenwood-base_X2{hoststubgen_framework-minus-apex_stats.csv}",
-        ":framework-minus-apex.ravenwood-base_X3{hoststubgen_framework-minus-apex_stats.csv}",
-        ":framework-minus-apex.ravenwood-base_X4{hoststubgen_framework-minus-apex_stats.csv}",
-        ":framework-minus-apex.ravenwood-base_X5{hoststubgen_framework-minus-apex_stats.csv}",
-    ],
-    out: ["hoststubgen_framework-minus-apex_stats.csv"],
-}
-
-genrule {
-    name: "hoststubgen_framework-minus-apex_apis.csv",
-    defaults: ["ravenwood-internal-only-visibility-genrule"],
-    cmd: "cat $(in) > $(out)",
-    srcs: [
-        ":framework-minus-apex.ravenwood-base_X0{hoststubgen_framework-minus-apex_apis.csv}",
-        ":framework-minus-apex.ravenwood-base_X1{hoststubgen_framework-minus-apex_apis.csv}",
-        ":framework-minus-apex.ravenwood-base_X2{hoststubgen_framework-minus-apex_apis.csv}",
-        ":framework-minus-apex.ravenwood-base_X3{hoststubgen_framework-minus-apex_apis.csv}",
-        ":framework-minus-apex.ravenwood-base_X4{hoststubgen_framework-minus-apex_apis.csv}",
-        ":framework-minus-apex.ravenwood-base_X5{hoststubgen_framework-minus-apex_apis.csv}",
-    ],
-    out: ["hoststubgen_framework-minus-apex_apis.csv"],
-}
-
-genrule {
-    name: "hoststubgen_framework-minus-apex_keep_all.txt",
-    defaults: ["ravenwood-internal-only-visibility-genrule"],
-    cmd: "cat $(in) > $(out)",
-    srcs: [
-        ":framework-minus-apex.ravenwood-base_X0{hoststubgen_framework-minus-apex_keep_all.txt}",
-        ":framework-minus-apex.ravenwood-base_X1{hoststubgen_framework-minus-apex_keep_all.txt}",
-        ":framework-minus-apex.ravenwood-base_X2{hoststubgen_framework-minus-apex_keep_all.txt}",
-        ":framework-minus-apex.ravenwood-base_X3{hoststubgen_framework-minus-apex_keep_all.txt}",
-        ":framework-minus-apex.ravenwood-base_X4{hoststubgen_framework-minus-apex_keep_all.txt}",
-        ":framework-minus-apex.ravenwood-base_X5{hoststubgen_framework-minus-apex_keep_all.txt}",
-    ],
-    out: ["hoststubgen_framework-minus-apex_keep_all.txt"],
-}
-
-genrule {
-    name: "hoststubgen_framework-minus-apex_dump.txt",
-    defaults: ["ravenwood-internal-only-visibility-genrule"],
-    cmd: "cat $(in) > $(out)",
-    srcs: [
-        ":framework-minus-apex.ravenwood-base_X0{hoststubgen_framework-minus-apex_dump.txt}",
-        ":framework-minus-apex.ravenwood-base_X1{hoststubgen_framework-minus-apex_dump.txt}",
-        ":framework-minus-apex.ravenwood-base_X2{hoststubgen_framework-minus-apex_dump.txt}",
-        ":framework-minus-apex.ravenwood-base_X3{hoststubgen_framework-minus-apex_dump.txt}",
-        ":framework-minus-apex.ravenwood-base_X4{hoststubgen_framework-minus-apex_dump.txt}",
-        ":framework-minus-apex.ravenwood-base_X5{hoststubgen_framework-minus-apex_dump.txt}",
-    ],
-    out: ["hoststubgen_framework-minus-apex_dump.txt"],
-}
-
 java_library {
     name: "services.core-for-hoststubgen",
     installable: false, // host only jar.
@@ -325,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"],
@@ -337,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/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..ba66ff7 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -4441,6 +4441,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 +4469,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/config/Android.bp b/config/Android.bp
index c9948c3..4a61cc7 100644
--- a/config/Android.bp
+++ b/config/Android.bp
@@ -43,6 +43,6 @@
 
 prebuilt_etc {
     name: "dirty-image-objects",
-    src: "dirty-image-objects",
-    filename: "dirty-image-objects",
+    src: "dirty-image-objects.txt",
+    filename: "dirty-image-objects.txt",
 }
diff --git a/config/OWNERS b/config/OWNERS
index 6a5df76..916bf67d 100644
--- a/config/OWNERS
+++ b/config/OWNERS
@@ -2,7 +2,7 @@
 
 # art-team@ manages the boot image profiles
 per-file boot-* = islamelbanna@google.com, ngeoffray@google.com, vmarko@google.com
-per-file dirty-image-objects = ishcheikin@google.com, ngeoffray@google.com, vmarko@google.com
+per-file dirty-image-objects.txt = ishcheikin@google.com, ngeoffray@google.com, vmarko@google.com
 per-file generate-preloaded-classes.sh = islamelbanna@google.com, ngeoffray@google.com, vmarko@google.com
 per-file preloaded-classes* = islamelbanna@google.com, ngeoffray@google.com, vmarko@google.com
 
diff --git a/config/README.md b/config/README.md
index 450a5c6..0d9cbb6 100644
--- a/config/README.md
+++ b/config/README.md
@@ -5,7 +5,7 @@
 * boot-profile.txt: An ordered list of methods from the boot classpath to be compiled by
   the JIT in the order provided in the file. Used by JIT zygote, when on-device
   signing failed.
-* dirty-image-objects: List of objects in the boot image which are known to
+* dirty-image-objects.txt: List of objects in the boot image which are known to
   become dirty. This helps binning objects in the image file.
 * preloaded-classes: classes that will be allocated in the boot image, and
   initialized by the zygote.
diff --git a/config/dirty-image-objects b/config/dirty-image-objects
deleted file mode 100644
index f2e2b82..0000000
--- a/config/dirty-image-objects
+++ /dev/null
@@ -1,1728 +0,0 @@
-#
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-#
-#
-# Dirty-image-objects file for boot image.
-# The image writer will bin these objects together in the image.
-# More info about dirty objects format and how to collect the data can be
-# found in: art/imgdiag/dirty_image_objects.md
-# This particular file was generated by dumping all pre-installed apps.
-#
-Landroid/text/style/URLSpan; 0
-Landroid/content/res/Resources$NotFoundException; 1
-Landroid/os/PowerManager$WakeLock; 2
-Landroid/os/BatterySaverPolicyConfig; 2
-Landroid/content/ContextWrapper; 2
-Landroid/app/WallpaperInfo; 2
-Landroid/content/pm/PackageManager; 2
-Landroid/app/IWallpaperManager; 2
-Ljava/lang/BootClassLoader; 2
-Ljava/time/Duration; 2
-Landroid/util/Printer; 2
-Landroid/app/WallpaperManager$OnColorsChangedListener; 2
-Landroid/app/WallpaperColors; 2
-Landroid/content/pm/ServiceInfo; 2
-Landroid/app/KeyguardManager$KeyguardDismissCallback; 2
-Ljava/lang/CharSequence; 3
-Landroid/widget/Switch; 4
-Lcom/android/internal/util/ContrastColorUtil; 4
-Landroid/view/SurfaceControl; 4
-Landroid/graphics/ColorMatrix;.dexCache:Ljava/lang/Object; 4
-Lcom/android/internal/widget/CachingIconView; 4
-Landroid/window/IRemoteTransition$Stub$Proxy; 4
-Landroid/app/trust/TrustManager$TrustListener; 4
-Landroid/view/NotificationHeaderView; 4
-Lcom/android/internal/widget/ImageResolver; 4
-Landroid/window/WindowContainerTransaction$Change; 4
-Lcom/android/internal/widget/MessagingLayout; 4
-Ljava/util/concurrent/ConcurrentLinkedQueue; 4
-Lcom/android/internal/protolog/BaseProtoLogImpl;.LOG_GROUPS:Ljava/util/TreeMap;.root:Ljava/util/TreeMap$TreeMapEntry;.right:Ljava/util/TreeMap$TreeMapEntry;.right:Ljava/util/TreeMap$TreeMapEntry;.right:Ljava/util/TreeMap$TreeMapEntry;.right:Ljava/util/TreeMap$TreeMapEntry;.left:Ljava/util/TreeMap$TreeMapEntry; 4
-Landroid/view/RemotableViewMethod; 4
-Landroid/app/IApplicationThread$Stub$Proxy; 4
-Landroid/os/FileUtils; 4
-Landroid/view/View;.SCALE_X:Landroid/util/Property; 4
-Landroid/widget/GridLayout;.UNDEFINED_ALIGNMENT:Landroid/widget/GridLayout$Alignment; 4
-Landroid/media/MediaPlayer$EventHandler; 4
-Landroid/widget/DateTimeView; 4
-Llibcore/util/ZoneInfo; 4
-Lcom/android/internal/statusbar/IStatusBarService; 4
-Ljava/lang/invoke/MethodType;.internTable:Ljava/lang/invoke/MethodType$ConcurrentWeakInternSet;.stale:Ljava/lang/ref/ReferenceQueue; 4
-Lcom/android/internal/protolog/BaseProtoLogImpl;.LOG_GROUPS:Ljava/util/TreeMap;.root:Ljava/util/TreeMap$TreeMapEntry;.right:Ljava/util/TreeMap$TreeMapEntry;.right:Ljava/util/TreeMap$TreeMapEntry; 4
-Lcom/android/internal/logging/UiEventLogger; 4
-Lcom/android/internal/protolog/BaseProtoLogImpl;.LOG_GROUPS:Ljava/util/TreeMap;.root:Ljava/util/TreeMap$TreeMapEntry;.right:Ljava/util/TreeMap$TreeMapEntry;.right:Ljava/util/TreeMap$TreeMapEntry;.left:Ljava/util/TreeMap$TreeMapEntry; 4
-Lcom/android/internal/protolog/BaseProtoLogImpl;.LOG_GROUPS:Ljava/util/TreeMap;.root:Ljava/util/TreeMap$TreeMapEntry;.right:Ljava/util/TreeMap$TreeMapEntry;.right:Ljava/util/TreeMap$TreeMapEntry;.right:Ljava/util/TreeMap$TreeMapEntry; 4
-Landroid/renderscript/RenderScript; 4
-Landroid/view/ViewTreeObserver$OnWindowVisibilityChangeListener; 4
-Lcom/android/internal/widget/RemeasuringLinearLayout; 4
-Landroid/widget/DateTimeView$ReceiverInfo$1; 4
-Landroid/view/View;.TRANSLATION_Y:Landroid/util/Property; 4
-Lcom/android/internal/protolog/BaseProtoLogImpl;.LOG_GROUPS:Ljava/util/TreeMap;.root:Ljava/util/TreeMap$TreeMapEntry; 4
-Lcom/android/internal/widget/NotificationExpandButton; 4
-Lcom/android/internal/view/menu/ActionMenuItemView; 4
-Landroid/view/animation/AnimationSet; 4
-Landroid/hardware/biometrics/BiometricSourceType;.FINGERPRINT:Landroid/hardware/biometrics/BiometricSourceType; 4
-Landroid/window/WindowOrganizer;.IWindowOrganizerControllerSingleton:Landroid/util/Singleton; 4
-Ljava/lang/Runnable; 4
-Lorg/apache/harmony/dalvik/ddmc/DdmServer;.mHandlerMap:Ljava/util/HashMap; 4
-Lcom/android/internal/protolog/BaseProtoLogImpl;.LOG_GROUPS:Ljava/util/TreeMap;.root:Ljava/util/TreeMap$TreeMapEntry;.right:Ljava/util/TreeMap$TreeMapEntry; 4
-Lcom/android/internal/widget/ImageFloatingTextView; 4
-Landroid/window/IWindowContainerToken$Stub$Proxy; 4
-Lcom/android/internal/protolog/BaseProtoLogImpl;.LOG_GROUPS:Ljava/util/TreeMap;.root:Ljava/util/TreeMap$TreeMapEntry;.left:Ljava/util/TreeMap$TreeMapEntry; 4
-Landroid/content/res/ColorStateList; 4
-Landroid/view/View;.SCALE_Y:Landroid/util/Property; 4
-Lcom/android/internal/protolog/BaseProtoLogImpl;.LOG_GROUPS:Ljava/util/TreeMap; 4
-Lcom/android/internal/widget/ConversationLayout; 4
-Lcom/android/internal/protolog/BaseProtoLogImpl;.LOG_GROUPS:Ljava/util/TreeMap;.root:Ljava/util/TreeMap$TreeMapEntry;.right:Ljava/util/TreeMap$TreeMapEntry;.right:Ljava/util/TreeMap$TreeMapEntry;.right:Ljava/util/TreeMap$TreeMapEntry;.right:Ljava/util/TreeMap$TreeMapEntry; 4
-Lcom/android/internal/colorextraction/ColorExtractor$OnColorsChangedListener; 4
-Landroid/hardware/face/FaceManager$FaceDetectionCallback; 4
-Landroid/widget/RemoteViews;.sLookupKey:Landroid/widget/RemoteViews$MethodKey; 4
-Landroid/widget/ViewSwitcher;.dexCache:Ljava/lang/Object; 4
-Lcom/android/internal/widget/NotificationActionListLayout; 4
-Ljava/util/concurrent/ConcurrentLinkedQueue$Node; 4
-Landroid/hardware/biometrics/BiometricSourceType;.FACE:Landroid/hardware/biometrics/BiometricSourceType; 4
-Landroid/hardware/biometrics/BiometricSourceType;.IRIS:Landroid/hardware/biometrics/BiometricSourceType; 4
-Landroid/view/NotificationTopLineView; 4
-Lcom/android/internal/protolog/BaseProtoLogImpl;.LOG_GROUPS:Ljava/util/TreeMap;.root:Ljava/util/TreeMap$TreeMapEntry;.left:Ljava/util/TreeMap$TreeMapEntry;.left:Ljava/util/TreeMap$TreeMapEntry;.left:Ljava/util/TreeMap$TreeMapEntry;.left:Ljava/util/TreeMap$TreeMapEntry;.left:Ljava/util/TreeMap$TreeMapEntry; 4
-Landroid/widget/RemoteViews;.sMethods:Landroid/util/ArrayMap; 4
-Lcom/android/internal/os/BinderInternal$BinderProxyLimitListener; 5
-Landroid/app/AppOpsManager$OnOpNotedInternalListener; 5
-Lcom/android/internal/R$styleable;.WindowAnimation:[I 5
-Lcom/android/internal/logging/UiEventLogger$UiEventEnum; 5
-Lcom/android/internal/policy/AttributeCache; 5
-Landroid/app/Notification$CallStyle; 5
-Landroid/app/AppOpsManager$OnOpNotedListener; 5
-Lcom/android/internal/protolog/BaseProtoLogImpl; 5
-Landroid/app/AppOpsManager$OnOpStartedListener; 5
-Lcom/android/internal/util/ScreenshotHelper$1; 5
-Landroid/app/Notification$DecoratedCustomViewStyle; 5
-Landroid/view/DisplayCutout; 5
-Landroid/view/InputEvent;.mNextSeq:Ljava/util/concurrent/atomic/AtomicInteger; 5
-Lcom/android/internal/statusbar/NotificationVisibility; 5
-Landroid/telephony/DataSpecificRegistrationInfo; 6
-Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle; 7
-Landroid/telephony/VoiceSpecificRegistrationInfo; 8
-Landroid/telephony/AnomalyReporter; 8
-Landroid/telephony/TelephonyRegistryManager;.sCarrierPrivilegeCallbacks:Ljava/util/WeakHashMap; 8
-Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.1:Ljava/util/WeakHashMap$Entry; 8
-Landroid/app/LoadedApk$ServiceDispatcher$InnerConnection; 8
-Landroid/content/ContentProvider$Transport; 8
-Landroid/telephony/NetworkRegistrationInfo; 8
-Landroid/net/MatchAllNetworkSpecifier; 8
-Landroid/telephony/TelephonyRegistryManager;.sCarrierPrivilegeCallbacks:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry; 8
-Landroid/app/PropertyInvalidatedCache;.sInvalidates:Ljava/util/HashMap; 9
-Landroid/app/PropertyInvalidatedCache$NoPreloadHolder; 9
-Landroid/app/PropertyInvalidatedCache;.sDisabledKeys:Ljava/util/HashSet;.map:Ljava/util/HashMap; 10
-Landroid/media/AudioSystem$AudioRecordingCallback; 11
-Lcom/android/internal/util/Parcelling$BuiltIn$ForInternedString; 11
-Landroid/net/metrics/IpManagerEvent; 11
-Lcom/android/internal/os/ProcessCpuTracker$FilterStats; 11
-Lcom/android/internal/infra/AbstractRemoteService$AsyncRequest; 11
-Landroid/content/pm/RegisteredServicesCache$3; 11
-Lcom/android/internal/os/LooperStats; 11
-Lcom/android/server/AppWidgetBackupBridge; 11
-Landroid/hardware/display/DisplayManagerInternal; 11
-Landroid/content/pm/PackageInfo; 11
-Landroid/hardware/soundtrigger/SoundTriggerModule$EventHandlerDelegate; 11
-Landroid/app/servertransaction/ResumeActivityItem; 11
-Lcom/android/internal/widget/AlertDialogLayout; 11
-Landroid/content/pm/FallbackCategoryProvider;.sFallbacks:Landroid/util/ArrayMap; 11
-Landroid/os/RemoteCallback$1; 11
-Landroid/content/pm/SharedLibraryInfo; 11
-Landroid/util/MemoryIntArray; 11
-Landroid/net/metrics/DhcpErrorEvent; 11
-Lcom/android/internal/util/function/DodecConsumer; 11
-Landroid/provider/Settings; 11
-Landroid/app/PropertyInvalidatedCache;.sCorkLock:Ljava/lang/Object; 11
-Lcom/android/internal/os/CachedDeviceState$Readonly; 11
-Landroid/app/job/JobServiceEngine$JobHandler; 11
-Landroid/app/SystemServiceRegistry; 11
-Lcom/android/internal/os/BinderInternal$CallStatsObserver; 11
-Lcom/android/internal/statusbar/IStatusBar$Stub$Proxy; 11
-Landroid/hardware/location/IActivityRecognitionHardwareClient; 11
-Landroid/telecom/Logging/EventManager$EventListener; 11
-Landroid/accounts/AccountManagerInternal; 11
-Lcom/android/internal/os/KernelCpuBpfTracking; 11
-Lcom/android/internal/statusbar/NotificationVisibility$NotificationLocation; 11
-Landroid/hardware/camera2/CameraManager$CameraManagerGlobal; 11
-Landroid/os/ServiceSpecificException; 11
-Landroid/net/Uri$PathPart;.NULL:Landroid/net/Uri$PathPart; 11
-Landroid/app/ActivityManagerInternal; 11
-Landroid/media/AudioSystem; 11
-Landroid/service/dreams/DreamManagerInternal; 11
-Landroid/debug/AdbManagerInternal; 11
-Landroid/graphics/Bitmap$CompressFormat; 11
-Landroid/hardware/location/NanoAppMessage; 11
-Landroid/os/storage/StorageManagerInternal; 11
-Landroid/app/AppOpsManagerInternal; 11
-Ljava/security/cert/CertificateException; 11
-Ldalvik/system/VMRuntime; 11
-Landroid/content/pm/SigningInfo; 11
-Landroid/view/KeyEvent; 11
-Lcom/android/internal/view/WindowManagerPolicyThread; 11
-Landroid/graphics/Region;.sPool:Landroid/util/Pools$SynchronizedPool;.mPool:[Ljava/lang/Object; 11
-Landroid/content/res/ResourceTimer; 11
-Landroid/view/autofill/AutofillManagerInternal; 11
-Lcom/android/internal/util/Parcelling$Cache;.sCache:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object; 11
-Landroid/graphics/Region;.sPool:Landroid/util/Pools$SynchronizedPool; 11
-Landroid/app/LoadedApk$ReceiverDispatcher$Args$$ExternalSyntheticLambda0; 11
-Lcom/android/server/LocalServices;.sLocalServiceObjects:Landroid/util/ArrayMap; 11
-Landroid/app/admin/DevicePolicyManagerInternal$OnCrossProfileWidgetProvidersChangeListener; 11
-Landroid/accounts/AccountManagerInternal$OnAppPermissionChangeListener; 11
-Landroid/content/pm/PermissionInfo; 11
-Landroid/view/WindowManagerPolicyConstants$PointerEventListener; 11
-Landroid/os/UEventObserver; 11
-Landroid/media/AudioManagerInternal$RingerModeDelegate; 11
-Landroid/view/Display$HdrCapabilities; 11
-Landroid/service/notification/Condition; 11
-Landroid/content/pm/UserPackage; 11
-Landroid/app/AppOpsManager$SamplingStrategy; 11
-Landroid/telephony/ServiceState; 11
-Landroid/app/servertransaction/PauseActivityItem; 11
-Lcom/android/internal/util/function/pooled/PooledLambdaImpl;.sMessageCallbacksPool:Lcom/android/internal/util/function/pooled/PooledLambdaImpl$Pool;.mLock:Ljava/lang/Object; 11
-Landroid/view/KeyCharacterMap$FallbackAction; 11
-Lcom/android/internal/util/Parcelling$BuiltIn$ForInternedStringArray; 11
-Landroid/hardware/display/DeviceProductInfo; 11
-Lcom/android/internal/util/Parcelling$Cache;.sCache:Landroid/util/ArrayMap;.mHashes:[I 11
-Landroid/content/pm/RegisteredServicesCache$2; 11
-Landroid/content/pm/PackageManager;.sCacheAutoCorker:Landroid/app/PropertyInvalidatedCache$AutoCorker; 11
-Landroid/app/PropertyInvalidatedCache;.sCorks:Ljava/util/HashMap; 11
-Landroid/service/notification/StatusBarNotification; 11
-Landroid/app/servertransaction/ConfigurationChangeItem; 11
-Landroid/app/ActivityManager$RecentTaskInfo; 11
-Landroid/app/Notification; 11
-Landroid/app/servertransaction/DestroyActivityItem; 11
-Landroid/webkit/WebViewLibraryLoader$RelroFileCreator; 11
-Landroid/net/metrics/NetworkEvent; 11
-Landroid/media/AudioPlaybackConfiguration; 11
-Landroid/accessibilityservice/AccessibilityServiceInfo; 11
-Landroid/hardware/display/DeviceProductInfo$ManufactureDate; 11
-Landroid/os/storage/StorageVolume; 11
-Landroid/os/BatteryManagerInternal; 11
-Landroid/appwidget/AppWidgetManagerInternal; 11
-Landroid/app/servertransaction/NewIntentItem; 11
-Landroid/content/pm/ShortcutServiceInternal; 11
-Landroid/app/assist/ActivityId; 11
-Landroid/window/DisplayAreaAppearedInfo; 11
-Landroid/os/Process;.ZYGOTE_PROCESS:Landroid/os/ZygoteProcess;.mLock:Ljava/lang/Object; 11
-Landroid/app/usage/UsageStats; 11
-Landroid/app/Notification$MediaStyle; 11
-Landroid/media/AudioSystem$DynamicPolicyCallback; 11
-Landroid/content/pm/ProviderInfo; 11
-Landroid/os/PowerManagerInternal; 11
-Landroid/service/voice/VoiceInteractionManagerInternal; 11
-Landroid/content/pm/FeatureInfo; 11
-Landroid/app/servertransaction/TopResumedActivityChangeItem; 11
-Landroid/app/Notification$DecoratedMediaCustomViewStyle; 11
-Landroid/appwidget/AppWidgetProviderInfo; 11
-Landroid/app/AppOpsManager$NoteOpEvent; 11
-Landroid/graphics/GraphicsStatsService; 11
-Landroid/view/DisplayAddress$Physical; 11
-Landroid/content/ComponentName$WithComponentName; 11
-Landroid/app/admin/DevicePolicyManagerInternal; 11
-Landroid/os/ResultReceiver$MyResultReceiver; 11
-Landroid/content/ContentProviderClient; 11
-Landroid/content/pm/RegisteredServicesCache$1; 11
-Landroid/app/PendingIntent$FinishedDispatcher; 11
-Landroid/location/LocationManager; 11
-Landroid/hardware/location/ContextHubInfo; 11
-Landroid/content/pm/ShortcutServiceInternal$ShortcutChangeListener; 11
-Lcom/android/server/usage/AppStandbyInternal; 11
-Landroid/content/pm/RegisteredServicesCacheListener; 11
-Landroid/app/servertransaction/LaunchActivityItem; 11
-Landroid/content/pm/BaseParceledListSlice$1; 11
-Landroid/annotation/StringRes; 11
-Lcom/android/internal/R$styleable;.Window:[I 11
-Landroid/service/notification/ZenModeConfig; 11
-Landroid/telecom/Logging/SessionManager$ISessionListener; 11
-Landroid/app/time/TimeZoneConfiguration; 11
-Landroid/net/metrics/ValidationProbeEvent; 11
-Landroid/content/pm/PackageInstaller$SessionInfo; 11
-Landroid/content/pm/UserPackage;.sCache:Landroid/util/SparseArrayMap;.mData:Landroid/util/SparseArray; 11
-Landroid/content/pm/PermissionGroupInfo; 11
-Landroid/hardware/sidekick/SidekickInternal; 11
-Lcom/android/internal/widget/ButtonBarLayout; 11
-Landroid/content/pm/LauncherActivityInfoInternal; 11
-Lcom/android/internal/util/Parcelling$Cache;.sCache:Landroid/util/ArrayMap; 11
-Lcom/android/internal/widget/LockSettingsInternal; 11
-Landroid/media/AudioManagerInternal; 11
-Landroid/app/AppOpsManager$AttributedOpEntry; 11
-Lcom/android/internal/util/Parcelling$BuiltIn$ForInternedStringList; 11
-Landroid/telecom/Log; 11
-Landroid/app/time/TimeZoneCapabilities; 11
-Landroid/attention/AttentionManagerInternal; 11
-Landroid/view/WindowManagerPolicyConstants; 11
-Landroid/content/pm/CrossProfileAppsInternal; 11
-Landroid/hardware/location/GeofenceHardwareService; 11
-Landroid/content/pm/dex/ArtManagerInternal; 11
-Landroid/net/metrics/IpReachabilityEvent; 11
-Landroid/content/pm/LauncherApps$ShortcutQuery$QueryFlags; 11
-Landroid/media/AudioAttributes; 11
-Landroid/app/PropertyInvalidatedCache$AutoCorker$1; 11
-Landroid/net/metrics/ApfProgramEvent; 11
-Landroid/content/pm/SigningDetails; 11
-Lcom/android/internal/protolog/ProtoLogImpl; 11
-Landroid/hardware/biometrics/ComponentInfoInternal; 11
-Lcom/android/internal/util/ToBooleanFunction; 11
-Landroid/app/ActivityThread$H; 11
-Landroid/hardware/location/GeofenceHardwareImpl; 11
-Landroid/net/wifi/nl80211/WifiNl80211Manager$ScanEventHandler; 11
-Landroid/util/NtpTrustedTime; 11
-Landroid/hardware/soundtrigger/SoundTrigger$StatusListener; 11
-Lcom/android/internal/app/procstats/AssociationState;.sTmpSourceKey:Lcom/android/internal/app/procstats/AssociationState$SourceKey; 11
-Ljava/util/zip/ZipFile$ZipFileInflaterInputStream; 11
-Landroid/app/job/JobInfo; 11
-Lcom/android/internal/content/om/OverlayConfig; 11
-Landroid/webkit/WebViewZygote; 11
-Lcom/android/internal/util/Parcelling$BuiltIn$ForInternedStringSet; 11
-Lcom/android/internal/infra/AbstractRemoteService$VultureCallback; 11
-Landroid/permission/PermissionManagerInternal; 11
-Lcom/android/server/WidgetBackupProvider; 11
-Landroid/window/WindowOnBackInvokedDispatcher$OnBackInvokedCallbackWrapper; 11
-Landroid/app/PropertyInvalidatedCache;.sCorkedInvalidates:Ljava/util/HashMap; 11
-Landroid/media/AudioPlaybackConfiguration$PlayerDeathMonitor; 11
-Landroid/net/wifi/nl80211/WifiNl80211Manager$ScanEventCallback; 11
-Landroid/service/notification/NotificationListenerService$RankingMap; 11
-Landroid/os/UserHandle;.sExtraUserHandleCache:Landroid/util/SparseArray; 11
-Ljava/time/DateTimeException; 11
-Ljava/lang/NumberFormatException; 11
-Ljava/security/Provider;.knownEngines:Ljava/util/Map;.table:[Ljava/util/HashMap$Node;.125:Ljava/util/HashMap$Node;.value:Ljava/lang/Object; 11
-Landroid/app/LoadedApk$ServiceDispatcher$RunConnection; 11
-Landroid/view/RoundedCorners; 11
-Landroid/os/Process;.ZYGOTE_PROCESS:Landroid/os/ZygoteProcess; 11
-Landroid/media/audiopolicy/AudioVolumeGroup; 11
-Landroid/media/AudioSystem$ErrorCallback; 11
-Landroid/app/servertransaction/ActivityResultItem; 11
-Lcom/android/internal/widget/DialogTitle; 11
-Lcom/android/internal/os/StoragedUidIoStatsReader$Callback; 11
-Landroid/view/ViewRootImpl$W; 11
-Landroid/app/ServiceStartArgs; 11
-Landroid/window/TaskAppearedInfo; 11
-Lcom/android/internal/listeners/ListenerExecutor$FailureCallback; 11
-Landroid/app/ApplicationExitInfo; 11
-Landroid/content/pm/PackageManager;.sCacheAutoCorker:Landroid/app/PropertyInvalidatedCache$AutoCorker;.mLock:Ljava/lang/Object; 11
-Lcom/android/internal/util/Parcelling$BuiltIn$ForInternedStringValueMap; 11
-Landroid/content/pm/ResolveInfo; 11
-Lcom/android/internal/display/BrightnessSynchronizer; 11
-Landroid/window/IOnBackInvokedCallback$Stub$Proxy; 12
-Landroid/graphics/drawable/PictureDrawable; 13
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.126:Ljava/lang/Byte; 13
-Landroid/view/ViewDebug$ExportedProperty; 13
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.41:Ljava/lang/Byte; 13
-Landroid/view/inputmethod/DeleteGesture; 13
-Landroid/view/ViewDebug$IntToString; 13
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.56:Ljava/lang/Byte; 13
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.65:Ljava/lang/Byte; 13
-Landroid/webkit/WebViewFactory;.sProviderLock:Ljava/lang/Object; 13
-Ljava/lang/IllegalAccessError; 13
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.51:Ljava/lang/Byte; 13
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.52:Ljava/lang/Byte; 13
-Landroid/view/inputmethod/DeleteRangeGesture; 13
-Landroid/window/WindowContext; 13
-Ljava/util/concurrent/ConcurrentSkipListMap$Node; 13
-Landroid/view/inputmethod/SelectRangeGesture; 13
-Landroid/util/MalformedJsonException; 13
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.131:Ljava/lang/Byte; 13
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.120:Ljava/lang/Byte; 13
-Ljava/lang/Enum;.sharedConstantsCache:Llibcore/util/BasicLruCache;.map:Ljava/util/LinkedHashMap;.tail:Ljava/util/LinkedHashMap$LinkedHashMapEntry;.before:Ljava/util/LinkedHashMap$LinkedHashMapEntry;.before:Ljava/util/LinkedHashMap$LinkedHashMapEntry;.before:Ljava/util/LinkedHashMap$LinkedHashMapEntry; 13
-Ljava/nio/file/StandardOpenOption;.TRUNCATE_EXISTING:Ljava/nio/file/StandardOpenOption; 13
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.121:Ljava/lang/Byte; 13
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.16:Ljava/lang/Byte; 13
-Ljava/util/concurrent/ConcurrentSkipListMap$Index; 13
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.139:Ljava/lang/Byte; 13
-Landroid/view/ViewDebug$FlagToString; 13
-Landroid/view/inputmethod/SelectGesture; 13
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.20:Ljava/lang/Byte; 13
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.94:Ljava/lang/Byte; 13
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.64:Ljava/lang/Byte; 13
-Landroid/webkit/WebViewFactoryProvider$Statics; 13
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.95:Ljava/lang/Byte; 13
-Landroid/service/media/MediaBrowserService$ServiceBinder$1; 13
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.7:Ljava/lang/Byte; 13
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.23:Ljava/lang/Byte; 13
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.46:Ljava/lang/Byte; 13
-Landroid/provider/Settings$SettingNotFoundException; 13
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.74:Ljava/lang/Byte; 13
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.8:Ljava/lang/Byte; 13
-Landroid/widget/TextView;.TEMP_POSITION:[F 13
-Ljava/io/ByteArrayInputStream; 14
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.93:Ljava/lang/Byte; 14
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.134:Ljava/lang/Byte; 14
-Landroid/text/style/ImageSpan; 14
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.154:Ljava/lang/Byte; 15
-Landroid/view/TextureView$SurfaceTextureListener; 16
-Landroid/media/AudioManager$OnAudioFocusChangeListener; 17
-Ljava/util/Locale;.JAPAN:Ljava/util/Locale; 18
-Ljava/util/Locale;.GERMANY:Ljava/util/Locale; 19
-Ljava/util/Locale;.CANADA_FRENCH:Ljava/util/Locale; 20
-Ljava/util/Locale;.ITALY:Ljava/util/Locale; 20
-Ljava/util/Locale;.FRANCE:Ljava/util/Locale; 20
-Ljava/util/Locale;.UK:Ljava/util/Locale; 21
-Ljava/util/Locale;.CANADA:Ljava/util/Locale; 21
-Ljava/util/Locale$Cache;.LOCALECACHE:Ljava/util/Locale$Cache;.map:Ljava/util/concurrent/ConcurrentMap; 22
-Ljava/lang/IllegalStateException; 23
-Lcom/android/internal/util/function/pooled/PooledLambdaImpl;.sMessageCallbacksPool:Lcom/android/internal/util/function/pooled/PooledLambdaImpl$Pool; 24
-Lcom/android/internal/util/function/pooled/PooledLambdaImpl;.sMessageCallbacksPool:Lcom/android/internal/util/function/pooled/PooledLambdaImpl$Pool;.mPool:[Ljava/lang/Object; 24
-Landroid/media/MediaRouter$WifiDisplayStatusChangedReceiver; 25
-Landroid/media/MediaRouter$VolumeChangeReceiver; 25
-Landroid/app/AppOpsManager$OnOpActiveChangedListener; 26
-Landroid/media/PlayerBase; 27
-Landroid/content/pm/Checksum$Type; 28
-Ljava/lang/Class; 29
-Landroid/widget/MediaController$MediaPlayerControl; 30
-Ljava/lang/Long$LongCache;.archivedCache:[Ljava/lang/Long;.135:Ljava/lang/Long; 30
-Ljava/lang/Long$LongCache;.archivedCache:[Ljava/lang/Long;.152:Ljava/lang/Long; 30
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.215:Ljava/lang/Byte; 31
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.206:Ljava/lang/Byte; 31
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.137:Ljava/lang/Byte; 31
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.203:Ljava/lang/Byte; 31
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.213:Ljava/lang/Byte; 31
-Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.549:Ljava/lang/Long; 31
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.201:Ljava/lang/Byte; 31
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.249:Ljava/lang/Byte; 31
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.163:Ljava/lang/Byte; 31
-Ljava/util/HashMap; 31
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.210:Ljava/lang/Byte; 31
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.161:Ljava/lang/Byte; 31
-Landroid/icu/text/MeasureFormat;.hmsTo012:Ljava/util/Map;.table:[Ljava/util/HashMap$Node;.0:Ljava/util/HashMap$Node;.value:Ljava/lang/Object; 31
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.199:Ljava/lang/Byte; 31
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.248:Ljava/lang/Byte; 31
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.252:Ljava/lang/Byte; 31
-Lcom/android/ims/rcs/uce/UceDeviceState;.DEVICE_STATE_DESCRIPTION:Ljava/util/Map;.table:[Ljava/util/HashMap$Node;.3:Ljava/util/HashMap$Node;.key:Ljava/lang/Object; 31
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.159:Ljava/lang/Byte; 31
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.217:Ljava/lang/Byte; 31
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.200:Ljava/lang/Byte; 31
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.240:Ljava/lang/Byte; 31
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.198:Ljava/lang/Byte; 31
-Lcom/android/ims/rcs/uce/UceDeviceState;.DEVICE_STATE_DESCRIPTION:Ljava/util/Map;.table:[Ljava/util/HashMap$Node;.4:Ljava/util/HashMap$Node;.key:Ljava/lang/Object; 31
-Landroid/content/pm/PackageManager$OnChecksumsReadyListener; 31
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.193:Ljava/lang/Byte; 31
-Ljava/lang/Long$LongCache;.archivedCache:[Ljava/lang/Long;.228:Ljava/lang/Long; 31
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.236:Ljava/lang/Byte; 31
-Landroid/telephony/ims/ImsService;.CAPABILITIES_LOG_MAP:Ljava/util/Map;.table:[Ljava/lang/Object;.2:Ljava/lang/Long; 31
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.211:Ljava/lang/Byte; 31
-Landroid/view/SurfaceView; 32
-Landroid/view/ViewStub$OnInflateListener; 33
-Landroid/graphics/drawable/DrawableInflater;.CONSTRUCTOR_MAP:Ljava/util/HashMap; 34
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.245:Ljava/lang/Byte; 35
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.232:Ljava/lang/Byte; 35
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.12:Ljava/lang/Byte; 35
-Ljava/lang/Long$LongCache;.archivedCache:[Ljava/lang/Long;.170:Ljava/lang/Long; 35
-Ljava/lang/Long$LongCache;.archivedCache:[Ljava/lang/Long;.183:Ljava/lang/Long; 35
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.246:Ljava/lang/Byte; 35
-Ljava/lang/Long$LongCache;.archivedCache:[Ljava/lang/Long;.168:Ljava/lang/Long; 35
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.72:Ljava/lang/Byte; 35
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.243:Ljava/lang/Byte; 35
-Ljava/util/WeakHashMap;.NULL_KEY:Ljava/lang/Object; 35
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.235:Ljava/lang/Byte; 35
-Ljava/lang/Long$LongCache;.archivedCache:[Ljava/lang/Long;.147:Ljava/lang/Long; 35
-Ljava/io/InterruptedIOException; 35
-Ljava/lang/Long$LongCache;.archivedCache:[Ljava/lang/Long;.184:Ljava/lang/Long; 35
-Ljava/lang/Long$LongCache;.archivedCache:[Ljava/lang/Long;.165:Ljava/lang/Long; 35
-Landroid/text/style/ForegroundColorSpan; 35
-Ljava/lang/Long$LongCache;.archivedCache:[Ljava/lang/Long;.176:Ljava/lang/Long; 35
-Ljava/lang/Long$LongCache;.archivedCache:[Ljava/lang/Long;.173:Ljava/lang/Long; 35
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.181:Ljava/lang/Byte; 35
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.157:Ljava/lang/Byte; 35
-Landroid/content/res/AssetManager$AssetInputStream; 35
-Landroid/graphics/drawable/TransitionDrawable; 36
-Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.1:Ljava/lang/Boolean; 37
-Landroid/view/ViewOverlay$OverlayViewGroup; 38
-Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.11:Ljava/lang/Boolean; 39
-Ljava/util/Observer; 40
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.129:Ljava/lang/Byte; 41
-[Ljava/lang/Byte; 41
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.144:Ljava/lang/Byte; 41
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.164:Ljava/lang/Byte; 42
-Landroid/view/OrientationEventListener; 43
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.195:Ljava/lang/Byte; 44
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.233:Ljava/lang/Byte; 44
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.229:Ljava/lang/Byte; 44
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.128:Ljava/lang/Byte; 44
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.242:Ljava/lang/Byte; 44
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.196:Ljava/lang/Byte; 44
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.208:Ljava/lang/Byte; 44
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.212:Ljava/lang/Byte; 44
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.228:Ljava/lang/Byte; 44
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.205:Ljava/lang/Byte; 44
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.197:Ljava/lang/Byte; 44
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.204:Ljava/lang/Byte; 44
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.207:Ljava/lang/Byte; 44
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.223:Ljava/lang/Byte; 44
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.244:Ljava/lang/Byte; 44
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.174:Ljava/lang/Byte; 44
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.194:Ljava/lang/Byte; 44
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.225:Ljava/lang/Byte; 45
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.239:Ljava/lang/Byte; 45
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.238:Ljava/lang/Byte; 45
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.227:Ljava/lang/Byte; 45
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.152:Ljava/lang/Byte; 46
-Landroid/app/RemoteAction; 46
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.168:Ljava/lang/Byte; 46
-Landroid/text/style/QuoteSpan; 46
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.54:Ljava/lang/Byte; 46
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.124:Ljava/lang/Byte; 46
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.142:Ljava/lang/Byte; 46
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.190:Ljava/lang/Byte; 46
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.114:Ljava/lang/Byte; 46
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.69:Ljava/lang/Byte; 46
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.30:Ljava/lang/Byte; 46
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.133:Ljava/lang/Byte; 46
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.49:Ljava/lang/Byte; 46
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.58:Ljava/lang/Byte; 46
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.143:Ljava/lang/Byte; 47
-Landroid/icu/text/RelativeDateTimeFormatter$AbsoluteUnit; 47
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.82:Ljava/lang/Byte; 47
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.140:Ljava/lang/Byte; 47
-Landroid/icu/text/RelativeDateTimeFormatter;.fallbackCache:[Landroid/icu/text/RelativeDateTimeFormatter$Style; 47
-Landroid/icu/text/RelativeDateTimeFormatter$Style; 47
-Landroid/icu/text/RelativeDateTimeFormatter;.cache:Landroid/icu/text/RelativeDateTimeFormatter$Cache;.cache:Landroid/icu/impl/CacheBase;.map:Ljava/util/concurrent/ConcurrentHashMap; 47
-Landroid/icu/text/RelativeDateTimeFormatter$RelativeUnit; 47
-Landroid/icu/text/RelativeDateTimeFormatter$Direction; 47
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.130:Ljava/lang/Byte; 47
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.43:Ljava/lang/Byte; 47
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.146:Ljava/lang/Byte; 47
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.138:Ljava/lang/Byte; 47
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.136:Ljava/lang/Byte; 48
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.0:Ljava/lang/Byte; 49
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.160:Ljava/lang/Byte; 49
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.169:Ljava/lang/Byte; 50
-Landroid/widget/Spinner; 50
-Landroid/widget/MultiAutoCompleteTextView; 50
-Ljava/util/ArrayList; 50
-Landroid/widget/CheckBox; 50
-Ljava/io/Serializable; 50
-Landroid/widget/RatingBar; 50
-Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.132:Ljava/lang/Byte; 50
-Landroid/widget/AutoCompleteTextView; 50
-Ljava/util/concurrent/ConcurrentLinkedDeque$Node; 50
-[Ljava/lang/Object; 50
-Landroid/widget/SeekBar; 51
-Ljava/lang/Void; 52
-Landroid/app/ActivityTaskManager;.sInstance:Landroid/util/Singleton; 53
-Landroid/view/ViewRootImpl$$ExternalSyntheticLambda11; 54
-Landroid/view/ViewTreeObserver$OnWindowFocusChangeListener; 55
-Landroid/view/InsetsAnimationThread; 56
-Lcom/android/internal/jank/InteractionJankMonitor$InstanceHolder; 57
-Lcom/android/internal/jank/InteractionJankMonitor; 57
-Landroid/hardware/camera2/CameraCharacteristics;.FLASH_INFO_AVAILABLE:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 58
-Landroid/hardware/display/NightDisplayListener$Callback; 59
-Landroid/media/MediaRouter2Manager; 59
-Landroid/os/HandlerExecutor; 59
-Landroid/os/strictmode/LeakedClosableViolation; 60
-Lcom/android/internal/logging/MetricsLogger; 60
-Lcom/android/internal/os/PowerProfile;.sPowerItemMap:Ljava/util/HashMap; 61
-Lcom/android/internal/os/PowerProfile;.sPowerArrayMap:Ljava/util/HashMap; 61
-Lcom/android/internal/os/PowerProfile;.sModemPowerProfile:Lcom/android/internal/power/ModemPowerProfile;.mPowerConstants:Landroid/util/SparseDoubleArray;.mValues:Landroid/util/SparseLongArray; 61
-Landroid/content/IntentFilter; 62
-Landroid/telecom/TelecomManager; 63
-Ljava/lang/IllegalArgumentException; 64
-Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.1:Ljava/util/WeakHashMap$Entry;.referent:Ljava/lang/Object; 65
-Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.1:Ljava/util/WeakHashMap$Entry;.referent:Ljava/lang/Object;.mCache:Ljava/util/LinkedHashMap; 65
-Landroid/telephony/VisualVoicemailSmsFilterSettings;.DEFAULT_ORIGINATING_NUMBERS:Ljava/util/List; 66
-Ljava/util/AbstractList; 68
-Ljava/util/AbstractCollection; 68
-Ljava/util/Collections$EmptyList; 69
-Ljava/lang/StackTraceElement; 69
-[Ljava/lang/StackTraceElement; 69
-Landroid/os/strictmode/Violation; 70
-Ljava/util/List; 71
-Ljava/lang/String; 72
-Ljava/io/ObjectInputStream; 73
-Ljava/io/ObjectStreamClass$Caches;.localDescs:Ljava/util/concurrent/ConcurrentMap; 73
-Ljava/io/ObjectStreamClass$Caches;.reflectors:Ljava/util/concurrent/ConcurrentMap; 73
-Ljava/io/ObjectOutputStream; 73
-Ljava/lang/Number; 74
-Ljava/math/BigInteger; 75
-[B 76
-Landroid/os/Handler; 77
-Landroid/view/accessibility/AccessibilityManager; 78
-Landroid/view/ViewConfiguration;.sConfigurations:Landroid/util/SparseArray;.mKeys:[I 79
-Landroid/view/ViewConfiguration;.sConfigurations:Landroid/util/SparseArray;.mValues:[Ljava/lang/Object; 79
-Landroid/view/ViewConfiguration;.sConfigurations:Landroid/util/SparseArray; 79
-Landroid/widget/FrameLayout; 80
-Lcom/android/internal/inputmethod/ImeTracing; 80
-Lcom/android/internal/policy/DecorView; 80
-Landroid/view/accessibility/AccessibilityNodeIdManager; 80
-Landroid/view/ViewTreeObserver; 80
-Landroid/view/ViewRootImpl; 80
-Landroid/os/SystemProperties;.sChangeCallbacks:Ljava/util/ArrayList; 80
-Landroid/transition/ChangeTransform; 80
-Landroid/window/SurfaceSyncGroup; 80
-Landroid/transition/ChangeClipBounds; 80
-Landroid/view/SurfaceControlRegistry; 80
-Landroid/transition/ChangeImageTransform; 80
-Landroid/widget/LinearLayout; 80
-Landroid/view/ViewStub; 81
-Landroid/text/TextLine;.sCached:[Landroid/text/TextLine; 82
-Landroid/text/TextUtils; 82
-Landroid/graphics/TemporaryBuffer; 82
-Landroid/content/res/ColorStateList;.sCache:Landroid/util/SparseArray; 83
-Landroid/text/Layout;.sTempRect:Landroid/graphics/Rect; 84
-Landroid/widget/ImageView; 85
-Landroid/graphics/drawable/ColorDrawable; 86
-Landroid/os/StrictMode$InstanceTracker;.sInstanceCounts:Ljava/util/HashMap; 87
-Landroid/app/ActivityClient;.INTERFACE_SINGLETON:Landroid/app/ActivityClient$ActivityClientControllerSingleton; 88
-Landroid/app/ActivityClient;.sInstance:Landroid/util/Singleton; 88
-Landroid/view/AbsSavedState$1; 89
-Landroid/app/FragmentManagerState; 90
-Landroid/window/OnBackAnimationCallback; 91
-Landroid/animation/AnimatorInflater;.sTmpTypedValue:Landroid/util/TypedValue; 92
-Landroid/graphics/drawable/RippleDrawable; 93
-Landroid/view/inputmethod/IInputMethodManagerGlobalInvoker; 94
-Landroid/app/ActivityTaskManager;.IActivityTaskManagerSingleton:Landroid/util/Singleton; 95
-Landroid/view/Choreographer; 96
-Lcom/android/internal/os/SomeArgs; 97
-Landroid/graphics/Bitmap; 98
-Landroid/view/autofill/AutofillId; 99
-Landroid/view/inputmethod/BaseInputConnection;.COMPOSING:Ljava/lang/Object; 100
-Landroid/text/Selection;.SELECTION_MEMORY:Ljava/lang/Object; 101
-Landroid/text/Selection;.SELECTION_END:Ljava/lang/Object; 101
-Landroid/text/Selection;.SELECTION_START:Ljava/lang/Object; 101
-Landroid/text/SpannableStringBuilder;.sCachedIntBuffer:[[I 102
-Landroid/text/Selection$MemoryTextWatcher; 103
-Landroid/text/SpanWatcher; 104
-Lcom/android/internal/util/ArrayUtils;.sCache:[Ljava/lang/Object; 105
-Ljava/lang/Integer;.SMALL_NEG_VALUES:[Ljava/lang/String; 106
-Ljava/lang/Enum;.sharedConstantsCache:Llibcore/util/BasicLruCache;.map:Ljava/util/LinkedHashMap;.tail:Ljava/util/LinkedHashMap$LinkedHashMapEntry;.before:Ljava/util/LinkedHashMap$LinkedHashMapEntry;.before:Ljava/util/LinkedHashMap$LinkedHashMapEntry;.before:Ljava/util/LinkedHashMap$LinkedHashMapEntry;.before:Ljava/util/LinkedHashMap$LinkedHashMapEntry;.before:Ljava/util/LinkedHashMap$LinkedHashMapEntry; 107
-Lsun/nio/ch/SharedFileLockTable;.lockMap:Ljava/util/concurrent/ConcurrentHashMap; 108
-Lsun/nio/ch/FileChannelImpl; 108
-Landroid/database/sqlite/SQLiteDatabase$CursorFactory; 109
-Landroid/database/sqlite/SQLiteDebug$NoPreloadHolder; 110
-Landroid/database/sqlite/SQLiteCompatibilityWalFlags; 110
-Landroid/database/sqlite/SQLiteGlobal; 110
-Landroid/database/CursorWindow; 111
-Landroid/content/ContentResolver; 112
-Ljava/nio/charset/Charset; 113
-Landroid/app/ContextImpl; 114
-Ljava/util/concurrent/Executors$DefaultThreadFactory;.poolNumber:Ljava/util/concurrent/atomic/AtomicInteger; 115
-Landroid/content/pm/PackageManager;.sPackageInfoCache:Landroid/app/PropertyInvalidatedCache;.mCache:Ljava/util/LinkedHashMap; 116
-Landroid/content/pm/PackageManager;.sApplicationInfoCache:Landroid/app/PropertyInvalidatedCache;.mCache:Ljava/util/LinkedHashMap; 117
-Ljava/util/logging/LogManager;.manager:Ljava/util/logging/LogManager;.systemContext:Ljava/util/logging/LogManager$LoggerContext;.namedLoggers:Ljava/util/Hashtable;.table:[Ljava/util/Hashtable$HashtableEntry; 118
-Landroid/ddm/DdmHandleAppName; 118
-Landroid/provider/DeviceConfigInitializer; 118
-Lsun/misc/Cleaner; 118
-Ldalvik/system/CloseGuard; 118
-Landroid/graphics/Typeface; 118
-Landroid/os/BinderProxy;.sProxyMap:Landroid/os/BinderProxy$ProxyMap;.mMainIndexKeys:[[Ljava/lang/Long; 118
-Landroid/permission/PermissionManager; 118
-Landroid/media/MediaFrameworkPlatformInitializer; 118
-Ljava/util/TimeZone; 118
-Landroid/os/Environment; 118
-Landroid/compat/Compatibility; 118
-Landroid/os/ServiceManager; 118
-Landroid/content/pm/PackageManager;.sApplicationInfoCache:Landroid/app/PropertyInvalidatedCache; 118
-Ljava/util/Locale$NoImagePreloadHolder; 118
-Ljava/lang/System; 118
-Lcom/android/internal/os/RuntimeInit; 118
-Ljava/util/logging/LogManager;.manager:Ljava/util/logging/LogManager;.systemContext:Ljava/util/logging/LogManager$LoggerContext;.namedLoggers:Ljava/util/Hashtable; 118
-Ldalvik/system/VMRuntime;.THE_ONE:Ldalvik/system/VMRuntime; 118
-Landroid/view/View; 118
-Landroid/hardware/display/DisplayManagerGlobal; 118
-Ljava/util/logging/LogManager;.manager:Ljava/util/logging/LogManager; 118
-Landroid/telephony/TelephonyFrameworkInitializer; 118
-Landroid/se/omapi/SeFrameworkInitializer; 118
-Landroid/app/LoadedApk;.sApplications:Landroid/util/ArrayMap;.mHashes:[I 118
-Landroid/security/net/config/SystemCertificateSource$NoPreloadHolder; 118
-Landroid/security/net/config/ApplicationConfig; 118
-Landroid/content/res/Resources;.sResourcesHistory:Ljava/util/Set;.c:Ljava/util/Collection;.m:Ljava/util/Map; 118
-Ljava/util/Locale; 118
-Landroid/content/res/Resources;.sResourcesHistory:Ljava/util/Set;.c:Ljava/util/Collection;.m:Ljava/util/Map;.table:[Ljava/util/WeakHashMap$Entry; 118
-Ljava/security/Provider; 118
-Ldalvik/system/ZygoteHooks; 118
-Landroid/os/Message; 118
-Landroid/app/LoadedApk;.sApplications:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object; 118
-Landroid/app/LoadedApk;.sApplications:Landroid/util/ArrayMap; 118
-Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap; 118
-Ljava/util/logging/LogManager;.manager:Ljava/util/logging/LogManager;.userContext:Ljava/util/logging/LogManager$LoggerContext;.namedLoggers:Ljava/util/Hashtable; 118
-Ljava/lang/ThreadGroup;.mainThreadGroup:Ljava/lang/ThreadGroup; 118
-Ldalvik/system/RuntimeHooks; 118
-Landroid/nfc/NfcFrameworkInitializer; 118
-Landroid/os/Looper; 118
-Landroid/os/LocaleList; 118
-Ldalvik/system/SocketTagger; 118
-Landroid/icu/util/TimeZone; 118
-Landroid/util/ArraySet; 118
-Ljava/util/logging/LogManager;.manager:Ljava/util/logging/LogManager;.systemContext:Ljava/util/logging/LogManager$LoggerContext;.root:Ljava/util/logging/LogManager$LogNode; 118
-Landroid/os/BinderProxy;.sProxyMap:Landroid/os/BinderProxy$ProxyMap;.mMainIndexValues:[Ljava/util/ArrayList; 118
-Ljava/util/Random;.seedUniquifier:Ljava/util/concurrent/atomic/AtomicLong; 118
-Landroid/app/ActivityThread; 118
-Landroid/os/Binder; 118
-Ljava/lang/ThreadLocal;.nextHashCode:Ljava/util/concurrent/atomic/AtomicInteger; 119
-Landroid/os/Parcel; 120
-Landroid/system/UnixSocketAddress; 120
-Ljava/lang/ThreadGroup;.systemThreadGroup:Ljava/lang/ThreadGroup; 120
-Ljava/lang/Daemons$FinalizerDaemon;.INSTANCE:Ljava/lang/Daemons$FinalizerDaemon; 120
-Landroid/os/Parcel;.sPairedCreators:Ljava/util/HashMap; 120
-Ljava/lang/Thread; 120
-Landroid/os/Parcel;.mCreators:Ljava/util/HashMap; 120
-Ljava/lang/Daemons$FinalizerDaemon;.INSTANCE:Ljava/lang/Daemons$FinalizerDaemon;.progressCounter:Ljava/util/concurrent/atomic/AtomicInteger; 120
-Landroid/system/StructPollfd; 120
-Ljava/lang/Daemons$HeapTaskDaemon;.INSTANCE:Ljava/lang/Daemons$HeapTaskDaemon; 120
-Landroid/system/StructTimeval; 120
-Ldalvik/system/VMRuntime;.THE_ONE:Ldalvik/system/VMRuntime;.allocationCount:Ljava/util/concurrent/atomic/AtomicInteger; 120
-Ljava/lang/Daemons$ReferenceQueueDaemon;.INSTANCE:Ljava/lang/Daemons$ReferenceQueueDaemon;.progressCounter:Ljava/util/concurrent/atomic/AtomicInteger; 120
-Landroid/os/GraphicsEnvironment;.sInstance:Landroid/os/GraphicsEnvironment; 120
-Ljava/lang/Daemons$FinalizerWatchdogDaemon;.INSTANCE:Ljava/lang/Daemons$FinalizerWatchdogDaemon; 120
-Ljava/lang/ref/FinalizerReference; 120
-Landroid/os/Process; 120
-Ljava/lang/Daemons$ReferenceQueueDaemon;.INSTANCE:Ljava/lang/Daemons$ReferenceQueueDaemon; 120
-Lcom/android/internal/os/BinderInternal; 120
-Landroid/app/ApplicationLoaders;.gApplicationLoaders:Landroid/app/ApplicationLoaders;.mLoaders:Landroid/util/ArrayMap; 121
-Landroid/app/DexLoadReporter;.INSTANCE:Landroid/app/DexLoadReporter;.mDataDirs:Ljava/util/Set;.map:Ljava/util/HashMap; 122
-Ldalvik/system/BaseDexClassLoader; 122
-Landroid/renderscript/RenderScriptCacheDir; 122
-Landroid/graphics/Compatibility; 123
-Llibcore/io/Libcore; 123
-Landroid/provider/FontsContract; 123
-Ljava/security/Security;.version:Ljava/util/concurrent/atomic/AtomicInteger; 123
-Llibcore/net/NetworkSecurityPolicy; 123
-Lsun/security/jca/Providers; 123
-Landroid/graphics/Canvas; 123
-Landroid/os/StrictMode; 124
-Landroid/content/pm/PackageManager;.sPackageInfoCache:Landroid/app/PropertyInvalidatedCache; 125
-Lcom/android/internal/os/StatsdHiddenApiUsageLogger;.sInstance:Lcom/android/internal/os/StatsdHiddenApiUsageLogger; 126
-Ljava/util/logging/LogManager;.manager:Ljava/util/logging/LogManager;.loggerRefQueue:Ljava/lang/ref/ReferenceQueue; 127
-Landroid/view/WindowManagerGlobal; 128
-Lcom/android/internal/util/function/pooled/PooledLambdaImpl;.sPool:Lcom/android/internal/util/function/pooled/PooledLambdaImpl$Pool; 129
-Lcom/android/internal/util/function/pooled/PooledLambdaImpl;.sPool:Lcom/android/internal/util/function/pooled/PooledLambdaImpl$Pool;.mPool:[Ljava/lang/Object; 129
-Landroid/view/inputmethod/InputMethodManager; 130
-Landroid/media/MediaRouter; 131
-Landroid/hardware/SensorPrivacyManager; 132
-Landroid/os/storage/StorageManager; 133
-Landroid/view/contentcapture/ContentCaptureManager; 134
-Landroid/hardware/input/InputManager; 134
-Landroid/app/people/PeopleManager; 134
-Landroid/media/session/MediaSessionManager; 134
-Landroid/security/attestationverification/AttestationVerificationManager; 134
-Landroid/net/vcn/VcnManager; 134
-Landroid/os/RecoverySystem; 134
-Landroid/net/NetworkPolicyManager; 134
-Landroid/net/wifi/sharedconnectivity/app/SharedConnectivityManager; 134
-Landroid/permission/PermissionControllerManager; 134
-Landroid/app/tare/EconomyManager; 134
-Landroid/view/translation/TranslationManager; 134
-Landroid/view/textclassifier/TextClassificationManager; 134
-Landroid/view/autofill/AutofillManager; 134
-Landroid/os/SystemConfigManager; 134
-Landroid/view/LayoutInflater; 134
-Landroid/credentials/CredentialManager; 134
-Landroid/service/persistentdata/PersistentDataBlockManager; 134
-Landroid/view/textservice/TextServicesManager; 134
-Landroid/app/admin/DevicePolicyManager; 134
-Ljava/lang/StackStreamFactory; 134
-Landroid/view/WindowManager; 134
-Landroid/app/contentsuggestions/ContentSuggestionsManager; 134
-Landroid/media/tv/tunerresourcemanager/TunerResourceManager; 134
-Landroid/telephony/SubscriptionManager; 134
-Landroid/os/HardwarePropertiesManager; 134
-Landroid/media/AudioManager; 135
-Landroid/telephony/TelephonyManager; 136
-Landroid/util/ArrayMap; 137
-Landroid/app/QueuedWork; 138
-Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.0:Ljava/util/WeakHashMap$Entry; 139
-Ljava/lang/Enum;.sharedConstantsCache:Llibcore/util/BasicLruCache;.map:Ljava/util/LinkedHashMap; 140
-Ljava/util/concurrent/ScheduledThreadPoolExecutor;.sequencer:Ljava/util/concurrent/atomic/AtomicLong; 141
-Landroid/util/Log; 142
-Ljava/util/Collections$SynchronizedCollection; 143
-Ljava/util/Set; 143
-Ljava/util/Collections$SynchronizedSet; 143
-Ljava/util/Collection; 143
-Ljava/lang/Integer;.SMALL_NONNEG_VALUES:[Ljava/lang/String; 144
-Landroid/content/ComponentName; 145
-Landroid/view/textclassifier/TextLanguage;.EMPTY:Landroid/view/textclassifier/TextLanguage;.mBundle:Landroid/os/Bundle; 146
-Landroid/os/PersistableBundle;.EMPTY:Landroid/os/PersistableBundle; 147
-Landroid/icu/impl/locale/BaseLocale;.CACHE:Landroid/icu/impl/locale/BaseLocale$Cache;._map:Ljava/util/concurrent/ConcurrentHashMap; 148
-Ljava/util/GregorianCalendar; 149
-Ljava/text/DontCareFieldPosition;.INSTANCE:Ljava/text/FieldPosition; 150
-Landroid/app/UiModeManager; 151
-Ljdk/internal/access/SharedSecrets; 152
-Landroid/icu/impl/ZoneMeta;.CANONICAL_ID_CACHE:Landroid/icu/impl/ICUCache; 153
-Landroid/icu/impl/ZoneMeta;.SYSTEM_ZONE_CACHE:Landroid/icu/impl/ZoneMeta$SystemTimeZoneCache;.map:Ljava/util/concurrent/ConcurrentHashMap; 154
-Ljava/time/ZoneOffset;.ID_CACHE:Ljava/util/concurrent/ConcurrentMap; 155
-Ljava/time/ZoneOffset;.ID_CACHE:Ljava/util/concurrent/ConcurrentMap;.table:[Ljava/util/concurrent/ConcurrentHashMap$Node; 155
-Ljava/time/ZoneOffset;.SECONDS_CACHE:Ljava/util/concurrent/ConcurrentMap; 155
-Ljava/time/ZoneOffset;.SECONDS_CACHE:Ljava/util/concurrent/ConcurrentMap;.table:[Ljava/util/concurrent/ConcurrentHashMap$Node;.0:Ljava/util/concurrent/ConcurrentHashMap$Node;.next:Ljava/util/concurrent/ConcurrentHashMap$Node; 155
-Ljava/time/ZoneOffset;.SECONDS_CACHE:Ljava/util/concurrent/ConcurrentMap;.table:[Ljava/util/concurrent/ConcurrentHashMap$Node; 155
-Landroid/widget/TextView; 156
-Landroid/view/ViewGroup$ChildListForAutoFillOrContentCapture;.sPool:Landroid/util/Pools$SimplePool;.mPool:[Ljava/lang/Object; 157
-Landroid/view/ViewGroup$ChildListForAutoFillOrContentCapture;.sPool:Landroid/util/Pools$SimplePool; 157
-Landroid/view/ViewGroup; 158
-Landroid/graphics/Rect; 159
-Landroid/view/View$BaseSavedState; 160
-Landroid/widget/Button; 161
-Landroid/widget/ImageButton; 162
-Landroid/view/View$OnHoverListener; 163
-Landroid/widget/Toolbar; 164
-Landroid/hardware/display/ColorDisplayManager$ColorDisplayManagerInternal; 165
-Landroid/app/WallpaperManager; 166
-Landroid/graphics/ColorSpace$Model;.RGB:Landroid/graphics/ColorSpace$Model; 166
-Landroid/graphics/drawable/AdaptiveIconDrawable; 167
-Landroid/animation/ValueAnimator$DurationScaleChangeListener; 168
-Landroid/widget/Toast; 168
-Landroid/app/smartspace/SmartspaceSession$OnTargetsAvailableListener; 168
-Landroid/view/CrossWindowBlurListeners; 168
-Landroid/app/servertransaction/ActivityRelaunchItem; 169
-[Ljava/util/concurrent/ForkJoinTask; 169
-Landroid/view/WindowManager$LayoutParams; 169
-Ljava/util/concurrent/ForkJoinPool$WorkQueue; 169
-Landroid/app/prediction/AppTargetEvent; 169
-Lorg/xmlpull/v1/XmlPullParserException; 169
-Landroid/app/servertransaction/ObjectPool;.sPoolMap:Ljava/util/Map; 170
-Landroid/app/servertransaction/ClientTransaction; 170
-Landroid/app/servertransaction/StopActivityItem; 170
-Landroid/system/ErrnoException; 171
-Landroid/hardware/location/ContextHubTransaction$OnCompleteListener; 172
-Landroid/app/PendingIntent$OnFinished; 172
-Ljava/lang/NullPointerException; 173
-Landroid/os/strictmode/DiskReadViolation; 174
-Lorg/apache/http/params/HttpParams; 175
-Landroid/nfc/cardemulation/CardEmulation; 176
-Ljava/io/FileDescriptor; 177
-Landroid/content/pm/PackageManager$OnPermissionsChangedListener; 178
-Landroid/security/keystore2/KeyStoreCryptoOperationUtils; 179
-Landroid/app/ActivityTaskManager; 180
-Landroid/util/EventLog; 181
-Ljava/net/URLConnection; 181
-Ljava/net/SocketException; 181
-Ljava/lang/reflect/InvocationTargetException; 181
-Ljava/lang/Enum; 182
-Landroid/widget/AbsListView$SelectionBoundsAdjuster; 183
-Ljava/lang/ClassNotFoundException; 183
-Landroid/content/SyncStatusObserver; 184
-Landroid/content/AsyncTaskLoader$LoadTask; 185
-Landroid/app/LoaderManager$LoaderCallbacks; 185
-Landroid/webkit/CookieSyncManager; 186
-Landroid/webkit/WebViewProvider$ViewDelegate; 187
-Landroid/webkit/WebView; 187
-Landroid/webkit/WebViewProvider$ScrollDelegate; 187
-Landroid/webkit/WebViewProvider; 187
-Landroid/webkit/WebViewFactory;.sTimestamps:Landroid/webkit/WebViewFactory$StartupTimestamps; 188
-Landroid/webkit/WebViewFactoryProvider; 189
-Landroid/webkit/WebViewFactory; 190
-Landroid/os/PowerManager$OnThermalStatusChangedListener; 191
-Landroid/os/Bundle; 192
-Landroid/widget/ProgressBar; 193
-Landroid/graphics/Bitmap$Config;.ALPHA_8:Landroid/graphics/Bitmap$Config; 194
-Landroid/graphics/Bitmap$Config;.RGB_565:Landroid/graphics/Bitmap$Config; 194
-Landroid/graphics/Bitmap$Config;.RGBA_1010102:Landroid/graphics/Bitmap$Config; 194
-Landroid/renderscript/Allocation;.mBitmapOptions:Landroid/graphics/BitmapFactory$Options;.inPreferredConfig:Landroid/graphics/Bitmap$Config; 194
-Landroid/graphics/Bitmap$Config;.RGBA_F16:Landroid/graphics/Bitmap$Config; 194
-Landroid/graphics/Bitmap$Config;.ARGB_4444:Landroid/graphics/Bitmap$Config; 194
-Landroid/graphics/Bitmap$Config;.HARDWARE:Landroid/graphics/Bitmap$Config; 194
-Landroid/graphics/drawable/StateListDrawable; 195
-Landroid/view/PointerIcon;.gSystemIconsByDisplay:Landroid/util/SparseArray; 196
-Landroid/view/PointerIcon; 196
-Ljavax/net/ssl/SSLServerSocketFactory; 197
-Ljavax/net/ssl/SSLSocketFactory; 198
-Ljavax/net/ssl/HttpsURLConnection$NoPreloadHolder; 198
-Ljavax/net/ssl/SSLSessionContext; 199
-Lcom/android/org/bouncycastle/crypto/CryptoServicesRegistrar; 200
-Lsun/security/x509/PKIXExtensions;.KeyUsage_Id:Lsun/security/util/ObjectIdentifier; 201
-Lsun/security/x509/PKIXExtensions;.PolicyConstraints_Id:Lsun/security/util/ObjectIdentifier; 201
-Ljava/security/cert/PKIXRevocationChecker$Option;.ONLY_END_ENTITY:Ljava/security/cert/PKIXRevocationChecker$Option; 201
-Lsun/security/x509/PKIXExtensions;.ExtendedKeyUsage_Id:Lsun/security/util/ObjectIdentifier; 201
-Lsun/security/provider/X509Factory;.certCache:Lsun/security/util/Cache; 201
-Lsun/security/x509/PKIXExtensions;.CertificatePolicies_Id:Lsun/security/util/ObjectIdentifier; 201
-Lsun/security/x509/PKIXExtensions;.NameConstraints_Id:Lsun/security/util/ObjectIdentifier; 201
-Lsun/security/x509/PKIXExtensions;.AuthorityKey_Id:Lsun/security/util/ObjectIdentifier; 201
-Lsun/security/provider/X509Factory;.certCache:Lsun/security/util/Cache;.cacheMap:Ljava/util/Map; 201
-Ljava/security/cert/PKIXRevocationChecker$Option;.NO_FALLBACK:Ljava/security/cert/PKIXRevocationChecker$Option; 201
-Lsun/security/x509/PKIXExtensions;.SubjectAlternativeName_Id:Lsun/security/util/ObjectIdentifier; 201
-Lsun/security/x509/PKIXExtensions;.PolicyMappings_Id:Lsun/security/util/ObjectIdentifier; 202
-Lsun/security/x509/PKIXExtensions;.InhibitAnyPolicy_Id:Lsun/security/util/ObjectIdentifier; 202
-Lsun/security/x509/PKIXExtensions;.BasicConstraints_Id:Lsun/security/util/ObjectIdentifier; 202
-Ljava/security/Security;.spiMap:Ljava/util/Map; 203
-Lsun/security/x509/X500Name;.commonName_oid:Lsun/security/util/ObjectIdentifier; 204
-Lsun/security/x509/X500Name;.countryName_oid:Lsun/security/util/ObjectIdentifier; 204
-Lsun/security/x509/X500Name;.orgName_oid:Lsun/security/util/ObjectIdentifier; 204
-Ljava/nio/charset/Charset;.cache2:Ljava/util/HashMap; 205
-Ljava/net/URL;.handlers:Ljava/util/Hashtable;.table:[Ljava/util/Hashtable$HashtableEntry; 206
-Ljava/net/URL;.handlers:Ljava/util/Hashtable; 206
-Ljava/net/Inet6AddressImpl;.addressCache:Ljava/net/AddressCache;.cache:Llibcore/util/BasicLruCache;.map:Ljava/util/LinkedHashMap; 207
-Ljava/net/Proxy$Type;.DIRECT:Ljava/net/Proxy$Type; 208
-Ljava/net/ProxySelector;.theProxySelector:Ljava/net/ProxySelector; 208
-Lcom/android/okhttp/okio/SegmentPool; 209
-Lcom/android/okhttp/internal/http/AuthenticatorAdapter;.INSTANCE:Lcom/android/okhttp/Authenticator; 209
-Lcom/android/okhttp/HttpsHandler;.HTTP_1_1_ONLY:Ljava/util/List;.element:Ljava/lang/Object; 209
-Lcom/android/okhttp/ConfigAwareConnectionPool;.instance:Lcom/android/okhttp/ConfigAwareConnectionPool;.networkEventDispatcher:Llibcore/net/event/NetworkEventDispatcher;.listeners:Ljava/util/List; 210
-Lcom/android/okhttp/Dns;.SYSTEM:Lcom/android/okhttp/Dns; 210
-Lcom/android/okhttp/ConfigAwareConnectionPool;.instance:Lcom/android/okhttp/ConfigAwareConnectionPool; 210
-Lcom/android/okhttp/okio/AsyncTimeout; 211
-Ljava/lang/IllegalAccessException; 212
-Ljavax/net/ssl/SSLContext; 213
-Ljavax/net/ssl/HttpsURLConnection; 213
-Ljava/security/Security;.props:Ljava/util/Properties;.map:Ljava/util/concurrent/ConcurrentHashMap;.table:[Ljava/util/concurrent/ConcurrentHashMap$Node;.12:Ljava/util/concurrent/ConcurrentHashMap$Node;.next:Ljava/util/concurrent/ConcurrentHashMap$Node; 214
-Ljava/security/Security;.props:Ljava/util/Properties;.map:Ljava/util/concurrent/ConcurrentHashMap;.table:[Ljava/util/concurrent/ConcurrentHashMap$Node;.30:Ljava/util/concurrent/ConcurrentHashMap$Node; 214
-Landroid/database/sqlite/SQLiteTransactionListener; 215
-Landroid/accounts/OnAccountsUpdateListener; 216
-Landroid/accounts/AccountManager$20; 217
-Lsun/nio/ch/FileChannelImpl$Unmapper; 218
-Landroid/content/res/Resources;.sResourcesHistory:Ljava/util/Set;.c:Ljava/util/Collection;.m:Ljava/util/Map;.queue:Ljava/lang/ref/ReferenceQueue; 219
-Landroid/text/method/SingleLineTransformationMethod; 220
-Landroid/widget/RelativeLayout; 221
-Landroid/graphics/drawable/BitmapDrawable; 222
-Landroid/graphics/drawable/GradientDrawable; 223
-Landroid/animation/PropertyValuesHolder;.sGetterPropertyMap:Ljava/util/HashMap; 224
-Landroid/animation/PropertyValuesHolder$FloatPropertyValuesHolder;.sJNISetterPropertyMap:Ljava/util/HashMap; 225
-Landroid/graphics/drawable/Drawable;.DEFAULT_TINT_MODE:Landroid/graphics/PorterDuff$Mode; 226
-Landroid/text/StaticLayout$Builder;.sPool:Landroid/util/Pools$SynchronizedPool; 227
-Landroid/text/StaticLayout$Builder;.sPool:Landroid/util/Pools$SynchronizedPool;.mPool:[Ljava/lang/Object; 227
-Ljava/util/concurrent/ThreadLocalRandom; 228
-Landroid/widget/Space; 229
-Landroid/widget/ScrollView; 230
-Landroid/text/style/LineHeightSpan; 231
-Landroid/text/style/TabStopSpan; 232
-Landroid/text/style/ReplacementSpan; 233
-Landroid/text/style/MetricAffectingSpan; 233
-Landroid/text/style/LeadingMarginSpan; 233
-Landroid/text/style/LineBackgroundSpan; 234
-Landroid/text/style/CharacterStyle; 235
-Landroid/text/style/SuggestionSpan; 236
-Landroid/widget/TextView$ChangeWatcher; 237
-Landroid/text/DynamicLayout$Builder;.sPool:Landroid/util/Pools$SynchronizedPool;.mPool:[Ljava/lang/Object; 238
-Landroid/text/DynamicLayout; 238
-Landroid/text/DynamicLayout$ChangeWatcher; 238
-Landroid/text/style/WrapTogetherSpan; 238
-Landroid/text/DynamicLayout$Builder;.sPool:Landroid/util/Pools$SynchronizedPool; 238
-Landroid/text/method/LinkMovementMethod; 239
-Landroid/text/style/ClickableSpan; 240
-Ljava/util/logging/LogRecord;.globalSequenceNumber:Ljava/util/concurrent/atomic/AtomicLong; 241
-Ljava/lang/Runtime;.currentRuntime:Ljava/lang/Runtime; 242
-Landroid/content/pm/LauncherActivityInfo; 243
-Landroid/database/sqlite/SQLiteMisuseException; 243
-Landroid/speech/tts/TextToSpeech$Connection$SetupConnectionAsyncTask; 243
-Landroid/database/sqlite/SQLiteCantOpenDatabaseException; 243
-Landroid/database/sqlite/SQLiteDatabaseCorruptException; 243
-Landroid/database/sqlite/SQLiteDatabaseLockedException; 243
-Ljava/util/Map$Entry; 243
-Ljava/util/zip/ZipException; 243
-Landroid/database/sqlite/SQLiteAccessPermException; 243
-Landroid/speech/tts/TextToSpeech$OnInitListener; 243
-Landroid/app/Notification$MessagingStyle; 244
-Landroid/text/TextUtils$TruncateAt; 245
-Landroid/app/smartspace/SmartspaceTarget; 246
-Landroid/app/prediction/AppTarget; 246
-Landroid/app/smartspace/uitemplatedata/BaseTemplateData; 246
-Landroid/location/LocationManager;.sLocationListeners:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry; 247
-Landroid/location/LocationManager;.sLocationListeners:Ljava/util/WeakHashMap; 247
-Landroid/service/notification/ConditionProviderService; 248
-Landroid/os/WorkSource; 249
-Landroid/security/keystore2/AndroidKeyStoreProvider; 249
-Ljava/net/Socket; 249
-Lcom/android/internal/listeners/ListenerTransport; 249
-Landroid/os/ParcelUuid; 250
-Landroid/telephony/emergency/EmergencyNumber; 251
-Lcom/android/internal/telephony/uicc/UiccProfile$4; 251
-Lcom/android/internal/telephony/nano/PersistAtomsProto$IncomingSms; 251
-Lcom/android/internal/telephony/SmsStorageMonitor$1; 251
-Lcom/android/internal/telephony/TelephonyDevController; 251
-Lcom/android/internal/telephony/uicc/UiccController; 251
-Lcom/android/internal/telephony/emergency/EmergencyNumberTracker$1; 251
-Lcom/android/internal/telephony/nano/PersistAtomsProto$DataCallSession; 251
-Lcom/android/internal/telephony/TelephonyDevController;.mSims:Ljava/util/ArrayList; 251
-Lcom/android/internal/telephony/nano/PersistAtomsProto$RcsAcsProvisioningStats; 251
-Ljava/lang/UnsupportedOperationException; 251
-Landroid/database/CursorToBulkCursorAdaptor; 251
-Lcom/android/internal/telephony/satellite/PointingAppController; 251
-Landroid/telephony/ModemActivityInfo; 251
-Lcom/android/internal/telephony/imsphone/ImsPhone; 251
-Lcom/android/internal/telephony/ServiceStateTracker; 251
-Lcom/android/internal/telephony/IccSmsInterfaceManager; 251
-Lcom/android/internal/telephony/util/NotificationChannelController$1; 251
-Lcom/android/internal/telephony/RilWakelockInfo; 251
-Lcom/android/internal/telephony/gsm/GsmInboundSmsHandler$GsmCbTestBroadcastReceiver; 251
-Lcom/android/internal/telephony/nano/PersistAtomsProto$SipTransportFeatureTagStats; 251
-Lcom/android/internal/telephony/nano/PersistAtomsProto$UceEventStats; 251
-Lcom/android/internal/telephony/euicc/EuiccConnector$BindingState; 251
-Lcom/android/internal/telephony/ims/ImsResolver$3; 251
-Landroid/net/NetworkPolicyManager$SubscriptionCallbackProxy; 251
-Lcom/android/internal/telephony/TelephonyDevController;.mModems:Ljava/util/ArrayList; 251
-Landroid/telephony/ims/aidl/IImsServiceController$Stub$Proxy; 251
-Lcom/android/internal/telephony/CarrierPrivilegesTracker$1; 251
-Lcom/android/internal/telephony/CommandException; 251
-Lcom/android/ims/FeatureConnector$1; 251
-Lcom/android/internal/telephony/IWapPushManager; 251
-Lcom/android/internal/telephony/nano/PersistAtomsProto$SipDelegateStats; 251
-Lcom/android/internal/telephony/imsphone/ImsPhoneCallTracker; 251
-Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mTtyModeReceivedRegistrants:Lcom/android/internal/telephony/RegistrantList;.registrants:Ljava/util/ArrayList; 251
-Landroid/view/textclassifier/TextLanguage;.EMPTY:Landroid/view/textclassifier/TextLanguage;.mBundle:Landroid/os/Bundle;.mClassLoader:Ljava/lang/ClassLoader; 251
-Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mMmiCompleteRegistrants:Lcom/android/internal/telephony/RegistrantList;.registrants:Ljava/util/ArrayList; 251
-Lcom/android/i18n/timezone/TelephonyLookup; 251
-Landroid/telephony/BarringInfo$BarringServiceInfo; 251
-Lcom/android/internal/telephony/nano/PersistAtomsProto$NetworkRequests; 251
-Lcom/android/internal/telephony/euicc/EuiccConnector$ConnectedState$14; 251
-Lcom/android/internal/telephony/SmsBroadcastUndelivered; 251
-Lcom/android/internal/telephony/LocaleTracker; 251
-Lcom/android/internal/telephony/PhoneSubInfoController; 251
-Lcom/android/internal/telephony/CarrierKeyDownloadManager$1; 251
-Lcom/android/internal/telephony/GsmCdmaCallTracker$1; 251
-Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.339:[Ljava/lang/String; 251
-Lcom/android/internal/telephony/ServiceStateTracker$1; 251
-Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.353:[Ljava/lang/String; 251
-Lcom/android/internal/telephony/euicc/EuiccCardController$SimSlotStatusChangedBroadcastReceiver; 251
-Lcom/android/internal/telephony/nano/PersistAtomsProto$PresenceNotifyEvent; 251
-Lcom/android/internal/telephony/SimActivationTracker$1; 251
-Landroid/telephony/ModemInfo; 251
-Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.1393:[Ljava/lang/String; 251
-Landroid/telephony/CellSignalStrengthWcdma; 251
-Lcom/android/internal/telephony/nano/PersistAtomsProto$SatelliteProvision; 251
-Lcom/android/internal/telephony/PhoneConfigurationManager; 251
-Lcom/android/internal/telephony/SmsApplication$SmsPackageMonitor; 251
-Landroid/telephony/TelephonyRegistryManager$3; 251
-Lcom/android/internal/telephony/nano/PersistAtomsProto$VoiceCallSession; 251
-Landroid/os/Handler$MessengerImpl; 251
-Lcom/android/internal/telephony/LocaleTracker$1; 251
-Lcom/android/internal/telephony/nano/PersistAtomsProto$CellularDataServiceSwitch; 251
-Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mInCallVoicePrivacyOffRegistrants:Lcom/android/internal/telephony/RegistrantList;.registrants:Ljava/util/ArrayList; 251
-Lcom/android/internal/telephony/nano/PersistAtomsProto$SatelliteSosMessageRecommender; 251
-Lcom/android/internal/telephony/nano/PersistAtomsProto$ImsRegistrationServiceDescStats; 251
-Lcom/android/internal/telephony/uicc/UiccPkcs15$Pkcs15Selector; 251
-Lcom/android/internal/telephony/CarrierResolver$2; 251
-Lcom/android/internal/telephony/CarrierActionAgent$1; 251
-Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mPhones:Ljava/util/ArrayList; 251
-Lcom/android/internal/telephony/SmsController; 251
-Lcom/android/internal/telephony/uicc/euicc/EuiccCardException; 251
-Lcom/android/internal/telephony/nano/PersistAtomsProto$ImsRegistrationTermination; 251
-Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.1125:[Ljava/lang/String; 251
-Lcom/android/internal/telephony/NetworkTypeController$1; 251
-Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.803:[Ljava/lang/String; 251
-Lcom/android/internal/telephony/uicc/asn1/TagNotFoundException; 251
-Lcom/android/internal/telephony/CarrierServiceBindHelper$1; 251
-Lcom/android/internal/telephony/nano/PersistAtomsProto$CellularServiceState; 251
-Lcom/android/internal/telephony/cdma/CdmaSubscriptionSourceManager; 251
-Lcom/android/internal/telephony/InboundSmsHandler$NewMessageNotificationActionReceiver; 251
-Lcom/android/internal/telephony/CarrierActionAgent; 251
-Lcom/android/i18n/timezone/TimeZoneFinder; 251
-Lcom/android/internal/telephony/RILRequest; 251
-Lcom/android/internal/telephony/RIL;.sRilTimeHistograms:Landroid/util/SparseArray; 251
-Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.33:[Ljava/lang/String; 251
-Lcom/android/internal/telephony/MccTable; 251
-Lcom/android/internal/telephony/uicc/UiccProfile$2; 251
-Lcom/android/internal/telephony/nano/PersistAtomsProto$CarrierIdMismatch; 251
-Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.1235:[Ljava/lang/String; 251
-Lcom/android/internal/telephony/uicc/UiccCarrierPrivilegeRules; 251
-Landroid/telephony/CellSignalStrengthTdscdma; 251
-Lcom/android/internal/telephony/nano/PersistAtomsProto$ImsDedicatedBearerListenerEvent; 251
-Lcom/android/internal/telephony/SmsDispatchersController; 251
-Landroid/timezone/TelephonyLookup; 251
-Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager; 251
-Lcom/android/internal/telephony/nano/PersistAtomsProto$SatelliteOutgoingDatagram; 251
-Lcom/android/internal/telephony/SMSDispatcher$1; 251
-Lcom/android/internal/telephony/AppSmsManager; 251
-Landroid/timezone/TimeZoneFinder; 251
-Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mBackgroundCalls:Ljava/util/ArrayList; 251
-Lcom/android/ims/rcs/uce/eab/EabProvider; 251
-Lcom/android/internal/telephony/uicc/PinStorage$1; 251
-Lcom/android/internal/telephony/nano/PersistAtomsProto$ImsDedicatedBearerEvent; 251
-Landroid/telephony/CellSignalStrengthLte; 251
-Landroid/telephony/ims/ProvisioningManager$Callback$CallbackBinder; 251
-Lcom/android/ims/ImsManager;.IMS_STATS_CALLBACKS:Landroid/util/SparseArray;.mKeys:[I 251
-Landroid/telephony/CellSignalStrengthNr; 251
-Lcom/android/internal/telephony/SomeArgs; 251
-Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mInCallVoicePrivacyOnRegistrants:Lcom/android/internal/telephony/RegistrantList;.registrants:Ljava/util/ArrayList; 251
-Lcom/android/internal/telephony/StateMachine$SmHandler; 251
-Lcom/android/internal/telephony/PackageChangeReceiver; 251
-Lcom/android/internal/telephony/nano/PersistAtomsProto$OutgoingShortCodeSms; 251
-Lcom/android/internal/telephony/nano/PersistAtomsProto$NetworkRequestsV2; 251
-Lcom/android/internal/telephony/nano/TelephonyProto$RilDataCall; 251
-Lcom/android/internal/telephony/euicc/EuiccConnector$AvailableState; 251
-Lcom/android/ims/internal/IImsServiceFeatureCallback$Stub$Proxy; 251
-Landroid/telephony/data/ApnSetting;.APN_TYPE_INT_MAP:Ljava/util/Map; 251
-Lcom/android/internal/telephony/RadioInterfaceCapabilityController; 251
-Lcom/android/internal/telephony/nano/PersistAtomsProto$ImsRegistrationStats; 251
-Lcom/android/internal/telephony/nano/PersistAtomsProto$VoiceCallRatUsage; 251
-Lcom/android/internal/telephony/metrics/TelephonyMetrics; 251
-Lcom/android/internal/telephony/nano/TelephonyProto$TelephonyCallSession$Event$RilCall; 251
-Lcom/android/internal/telephony/NetworkRegistrationManager$NetworkRegStateCallback; 251
-Lcom/android/internal/telephony/cdma/CdmaInboundSmsHandler; 251
-Lcom/android/internal/telephony/euicc/EuiccConnector$ConnectedState; 251
-Lcom/android/internal/telephony/RadioConfig; 251
-Lcom/android/internal/telephony/euicc/EuiccConnector$DisconnectedState; 251
-Lcom/android/internal/telephony/nano/PersistAtomsProto$SatelliteSession; 251
-Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mDisplayInfoRegistrants:Lcom/android/internal/telephony/RegistrantList;.registrants:Ljava/util/ArrayList; 251
-Lcom/android/phone/ecc/nano/ProtobufEccData$EccInfo; 251
-Lcom/android/internal/telephony/GsmCdmaPhone; 251
-Lcom/android/internal/telephony/TelephonyTester$1; 251
-Lcom/android/internal/telephony/cdma/CdmaInboundSmsHandler$CdmaScpTestBroadcastReceiver; 251
-Lcom/android/internal/telephony/NetworkTypeController$DefaultState; 251
-Landroid/net/TelephonyNetworkSpecifier; 251
-Lcom/android/internal/telephony/NitzStateMachine; 251
-Landroid/app/timezonedetector/TimeZoneDetector; 251
-Lcom/android/internal/telephony/IntentBroadcaster$1; 251
-Lcom/android/internal/telephony/uicc/UiccStateChangedLauncher; 251
-Lcom/android/internal/telephony/ims/ImsResolver$1; 251
-Lcom/android/internal/telephony/nano/PersistAtomsProto$OutgoingSms; 251
-Lcom/android/internal/telephony/nano/PersistAtomsProto$UnmeteredNetworks; 251
-Landroid/view/textclassifier/TextLanguage;.EMPTY:Landroid/view/textclassifier/TextLanguage;.mBundle:Landroid/os/Bundle;.mClassLoader:Ljava/lang/ClassLoader;.packages:Ljava/util/Map;.m:Ljava/util/Map; 251
-Lcom/android/internal/telephony/euicc/EuiccController; 251
-Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mForegroundCalls:Ljava/util/ArrayList; 251
-Lcom/android/internal/telephony/satellite/SatelliteModemInterface; 251
-Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.531:[Ljava/lang/String; 251
-Lcom/android/ims/ImsManager;.IMS_STATS_CALLBACKS:Landroid/util/SparseArray; 251
-Lcom/android/internal/telephony/euicc/EuiccConnector$1; 251
-Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.467:[Ljava/lang/String; 251
-Lcom/android/internal/telephony/nano/PersistAtomsProto$SipMessageResponse; 251
-Lcom/android/internal/telephony/nano/PersistAtomsProto$SipTransportSession; 251
-Lcom/android/internal/telephony/euicc/EuiccConnector$UnavailableState; 251
-Lcom/android/internal/telephony/DeviceStateMonitor$3; 251
-Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mSignalInfoRegistrants:Lcom/android/internal/telephony/RegistrantList;.registrants:Ljava/util/ArrayList; 251
-Lcom/android/ims/ImsManager;.IMS_STATS_CALLBACKS:Landroid/util/SparseArray;.mValues:[Ljava/lang/Object; 251
-Lcom/android/internal/telephony/IccPhoneBookInterfaceManager; 251
-Lcom/android/internal/telephony/DisplayInfoController; 251
-Lcom/android/internal/telephony/ims/ImsResolver$2; 251
-Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.1377:[Ljava/lang/String; 251
-Lcom/android/internal/telephony/nano/TelephonyProto$TelephonyServiceState$NetworkRegistrationInfo; 251
-Lcom/android/internal/telephony/nano/PersistAtomsProto$SatelliteController; 251
-Landroid/telephony/ims/RegistrationManager$RegistrationCallback$RegistrationBinder; 251
-Lcom/android/internal/telephony/imsphone/ImsExternalCallTracker; 251
-Lcom/android/internal/telephony/nano/PersistAtomsProto$EmergencyNumbersInfo; 251
-Lcom/android/internal/telephony/nano/PersistAtomsProto$GbaEvent; 251
-Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.23:[Ljava/lang/String; 251
-Landroid/telephony/CellSignalStrengthCdma; 251
-Landroid/telephony/TelephonyLocalConnection; 251
-Lcom/android/internal/telephony/nano/PersistAtomsProto$SatelliteIncomingDatagram; 251
-Lcom/android/internal/telephony/imsphone/ImsPhoneCallTracker$2; 251
-Lcom/android/internal/telephony/ims/ImsResolver; 251
-Lcom/android/internal/telephony/SmsStorageMonitor; 251
-Lcom/android/internal/telephony/uicc/UiccProfile; 251
-Landroid/telephony/ims/ImsMmTelManager$CapabilityCallback$CapabilityBinder; 251
-Lcom/android/internal/telephony/euicc/EuiccCardController; 251
-Lcom/android/internal/telephony/SmsBroadcastUndelivered$1; 251
-Lcom/android/internal/telephony/GsmCdmaCallTracker; 251
-Lcom/android/internal/telephony/nano/PersistAtomsProto$RcsClientProvisioningStats; 251
-Lcom/android/internal/telephony/cat/CatService; 251
-Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.761:[Ljava/lang/String; 251
-Lcom/android/internal/telephony/SmsApplication; 251
-Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mDisconnectRegistrants:Lcom/android/internal/telephony/RegistrantList;.registrants:Ljava/util/ArrayList; 251
-Lcom/android/internal/telephony/PhoneFactory; 251
-Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mHandlerMap:Ljava/util/HashMap; 251
-Landroid/os/AsyncResult; 251
-Lcom/android/internal/telephony/ProxyController; 251
-Lcom/android/internal/telephony/cdma/CdmaInboundSmsHandler$CdmaCbTestBroadcastReceiver; 251
-Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.453:[Ljava/lang/String; 251
-Lcom/android/internal/telephony/MultiSimSettingController; 251
-Ljava/io/BufferedReader; 251
-Landroid/telephony/CellSignalStrengthGsm; 251
-Lcom/android/internal/telephony/SimActivationTracker; 251
-Lcom/android/internal/telephony/CellBroadcastServiceManager; 251
-Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mRingingCalls:Ljava/util/ArrayList; 251
-Lcom/android/internal/telephony/IntentBroadcaster; 251
-Lcom/android/internal/telephony/nano/PersistAtomsProto$ImsRegistrationFeatureTagStats; 251
-Lcom/android/internal/telephony/euicc/EuiccConnector$EuiccPackageMonitor; 251
-Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mSuppServiceFailedRegistrants:Lcom/android/internal/telephony/RegistrantList;.registrants:Ljava/util/ArrayList; 251
-Landroid/view/textclassifier/TextLanguage;.EMPTY:Landroid/view/textclassifier/TextLanguage;.mBundle:Landroid/os/Bundle;.mClassLoader:Ljava/lang/ClassLoader;.packages:Ljava/util/Map;.m:Ljava/util/Map;.table:[Ljava/util/HashMap$Node; 251
-Lcom/android/internal/telephony/CarrierServiceBindHelper$CarrierServicePackageMonitor; 251
-Lcom/android/internal/telephony/TelephonyComponentFactory; 251
-Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.637:[Ljava/lang/String; 251
-Lcom/android/phone/ecc/nano/ProtobufEccData$CountryInfo; 251
-Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.651:[Ljava/lang/String; 251
-Lcom/android/internal/telephony/SmsUsageMonitor; 251
-Lcom/android/internal/telephony/CommandException$Error;.INVALID_SIM_STATE:Lcom/android/internal/telephony/CommandException$Error; 251
-Landroid/hardware/display/DisplayManagerGlobal$DisplayManagerCallback; 252
-Landroid/app/ActivityThread$ApplicationThread; 252
-Landroid/app/ActivityManager$MyUidObserver; 253
-Landroid/media/browse/MediaBrowser$ServiceCallbacks; 253
-Landroid/media/session/MediaController$CallbackStub; 253
-Landroid/media/session/MediaSessionManager$OnMediaKeyEventSessionChangedListener; 253
-Landroid/app/PendingIntent$CancelListener; 253
-Landroid/media/AudioManager$2; 253
-Landroid/database/ContentObserver$Transport; 253
-Landroid/media/session/MediaSessionManager$SessionsChangedWrapper$1; 253
-Landroid/content/ContentProvider$PipeDataWriter; 254
-Landroid/security/net/config/UserCertificateSource$NoPreloadHolder; 255
-Landroid/view/Window$Callback; 256
-Landroid/transition/TransitionManager;.sDefaultTransition:Landroid/transition/Transition;.mTransitions:Ljava/util/ArrayList;.elementData:[Ljava/lang/Object;.1:Landroid/transition/ChangeBounds;.mCurrentAnimators:Ljava/util/ArrayList; 256
-Landroid/transition/TransitionManager;.sDefaultTransition:Landroid/transition/Transition;.mTransitions:Ljava/util/ArrayList;.elementData:[Ljava/lang/Object;.2:Landroid/transition/Fade;.mCurrentAnimators:Ljava/util/ArrayList; 256
-Landroid/transition/TransitionManager;.sPendingTransitions:Ljava/util/ArrayList; 256
-Landroid/transition/TransitionManager;.sDefaultTransition:Landroid/transition/Transition;.mTransitions:Ljava/util/ArrayList;.elementData:[Ljava/lang/Object;.0:Landroid/transition/Fade;.mCurrentAnimators:Ljava/util/ArrayList; 256
-Landroid/view/AttachedSurfaceControl$OnBufferTransformHintChangedListener; 257
-Landroid/webkit/ValueCallback; 258
-Landroid/webkit/WebResourceRequest; 258
-Landroid/webkit/WebChromeClient$CustomViewCallback; 258
-Landroid/hardware/camera2/CameraCharacteristics;.INFO_SUPPORTED_HARDWARE_LEVEL:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 258
-Landroid/accounts/Account; 258
-Landroid/hardware/camera2/CameraCharacteristics;.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 258
-Landroid/hardware/camera2/CameraCharacteristics;.DEPTH_AVAILABLE_DEPTH_STREAM_CONFIGURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 259
-Landroid/hardware/camera2/CameraCharacteristics;.DEPTH_AVAILABLE_DYNAMIC_DEPTH_STALL_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 259
-Landroid/hardware/camera2/CameraCharacteristics;.HEIC_AVAILABLE_HEIC_STREAM_CONFIGURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 259
-Landroid/hardware/camera2/CameraCharacteristics;.SCALER_AVAILABLE_STALL_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 259
-Landroid/hardware/camera2/CameraCharacteristics;.JPEGR_AVAILABLE_JPEG_R_MIN_FRAME_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 259
-Landroid/hardware/camera2/params/StreamConfigurationDuration; 259
-Landroid/hardware/camera2/CameraCharacteristics;.SCALER_AVAILABLE_STREAM_CONFIGURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 259
-Landroid/hardware/camera2/CameraCharacteristics;.DEPTH_AVAILABLE_DYNAMIC_DEPTH_MIN_FRAME_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 259
-Landroid/hardware/camera2/CameraCharacteristics;.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 259
-Landroid/hardware/camera2/CameraCharacteristics;.JPEGR_AVAILABLE_JPEG_R_STALL_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 259
-Landroid/hardware/camera2/CameraCharacteristics;.DEPTH_AVAILABLE_DEPTH_STALL_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 259
-Landroid/hardware/camera2/CameraCharacteristics;.SCALER_AVAILABLE_MIN_FRAME_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 259
-Landroid/hardware/camera2/CameraCharacteristics;.CONTROL_AVAILABLE_HIGH_SPEED_VIDEO_CONFIGURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 259
-Landroid/hardware/camera2/CameraCharacteristics;.HEIC_AVAILABLE_HEIC_STALL_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 259
-Landroid/hardware/camera2/CameraCharacteristics;.JPEGR_AVAILABLE_JPEG_R_STREAM_CONFIGURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 259
-Landroid/hardware/camera2/CameraCharacteristics;.HEIC_AVAILABLE_HEIC_MIN_FRAME_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 259
-Landroid/hardware/camera2/params/HighSpeedVideoConfiguration; 259
-Landroid/hardware/camera2/CameraCharacteristics;.DEPTH_AVAILABLE_DEPTH_MIN_FRAME_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 259
-Landroid/hardware/camera2/CameraCharacteristics;.SCALER_AVAILABLE_INPUT_OUTPUT_FORMATS_MAP:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 259
-Landroid/hardware/camera2/CameraCharacteristics;.DEPTH_AVAILABLE_DYNAMIC_DEPTH_STREAM_CONFIGURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 259
-I 259
-Landroid/hardware/camera2/CameraCharacteristics;.REQUEST_AVAILABLE_CAPABILITIES:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 259
-Landroid/hardware/camera2/params/StreamConfiguration; 259
-Z 260
-Landroid/hardware/camera2/CameraCharacteristics;.SENSOR_INFO_PIXEL_ARRAY_SIZE:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.LENS_FOCAL_LENGTH:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CameraCharacteristics;.LENS_INFO_AVAILABLE_APERTURES:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.DISTORTION_CORRECTION_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.EXTENSION_STRENGTH:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-J 260
-Landroid/hardware/camera2/CameraCharacteristics;.SENSOR_INFO_PHYSICAL_SIZE:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.TONEMAP_PRESET_CURVE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-B 260
-Landroid/hardware/camera2/CaptureRequest;.CONTROL_AF_TRIGGER:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CameraCharacteristics;.LENS_INFO_AVAILABLE_OPTICAL_STABILIZATION:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.CONTROL_AE_EXPOSURE_COMPENSATION:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-[D 260
-Landroid/hardware/camera2/CaptureRequest;.CONTROL_AWB_REGIONS:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CameraCharacteristics;.REQUEST_AVAILABLE_SESSION_KEYS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.SENSOR_EXPOSURE_TIME:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CameraCharacteristics;.SENSOR_CALIBRATION_TRANSFORM2:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.COLOR_CORRECTION_TRANSFORM:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/content/res/Resources$Theme; 260
-Landroid/hardware/camera2/CaptureRequest;.CONTROL_AE_LOCK:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-F 260
-Landroid/hardware/camera2/CaptureRequest;.LENS_FILTER_DENSITY:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.LENS_OPTICAL_STABILIZATION_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.NOISE_REDUCTION_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.SENSOR_FRAME_DURATION:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.CONTROL_AE_REGIONS:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.CONTROL_EXTENDED_SCENE_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.STATISTICS_OIS_DATA_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.SENSOR_TEST_PATTERN_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.HOT_PIXEL_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.CONTROL_AE_ANTIBANDING_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.STATISTICS_LENS_SHADING_MAP_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.SCALER_CROP_REGION:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.LENS_FOCUS_DISTANCE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.TONEMAP_GAMMA:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CameraCharacteristics;.SENSOR_REFERENCE_ILLUMINANT2:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CameraCharacteristics;.SENSOR_REFERENCE_ILLUMINANT1:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-[F 260
-Landroid/hardware/camera2/CaptureRequest;.CONTROL_ZOOM_RATIO:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.COLOR_CORRECTION_ABERRATION_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.TONEMAP_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CameraCharacteristics;.SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CameraCharacteristics;.REQUEST_AVAILABLE_REQUEST_KEYS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.SCALER_ROTATE_AND_CROP:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.COLOR_CORRECTION_GAINS:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CameraCharacteristics;.SENSOR_COLOR_TRANSFORM1:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.CONTROL_AF_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.SENSOR_SENSITIVITY:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CameraCharacteristics;.LENS_INFO_AVAILABLE_FOCAL_LENGTHS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CameraCharacteristics;.SENSOR_OPTICAL_BLACK_REGIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.JPEG_QUALITY:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.FLASH_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.CONTROL_POST_RAW_SENSITIVITY_BOOST:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.CONTROL_AE_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CameraCharacteristics;.SENSOR_INFO_WHITE_LEVEL:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.CONTROL_SETTINGS_OVERRIDE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-[Landroid/hardware/camera2/params/MeteringRectangle; 260
-Landroid/hardware/camera2/CaptureRequest;.CONTROL_AWB_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CameraCharacteristics;.LOGICAL_MULTI_CAMERA_PHYSICAL_IDS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CameraCharacteristics;.SENSOR_INFO_EXPOSURE_TIME_RANGE:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Ljava/lang/Float; 260
-Landroid/hardware/camera2/CaptureRequest;.CONTROL_ENABLE_ZSL:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CameraCharacteristics;.INFO_DEVICE_STATE_ORIENTATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CameraCharacteristics;.SENSOR_CALIBRATION_TRANSFORM1:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.EDGE_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.CONTROL_CAPTURE_INTENT:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CameraCharacteristics;.SENSOR_ORIENTATION:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.JPEG_ORIENTATION:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CameraCharacteristics;.SENSOR_COLOR_TRANSFORM2:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-[J 260
-Landroid/hardware/camera2/CameraCharacteristics;.CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Ljava/util/concurrent/Phaser; 260
-Landroid/hardware/camera2/CaptureRequest;.BLACK_LEVEL_LOCK:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.COLOR_CORRECTION_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.CONTROL_SCENE_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.JPEG_THUMBNAIL_SIZE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.SHADING_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.STATISTICS_FACE_DETECT_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.STATISTICS_HOT_PIXEL_MAP_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.CONTROL_AUTOFRAMING:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.CONTROL_AE_TARGET_FPS_RANGE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.CONTROL_AWB_LOCK:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.SENSOR_TEST_PATTERN_DATA:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.CONTROL_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.CONTROL_AE_PRECAPTURE_TRIGGER:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.FLASH_STRENGTH_LEVEL:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.CONTROL_VIDEO_STABILIZATION_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.REPROCESS_EFFECTIVE_EXPOSURE_FACTOR:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Ljava/lang/Boolean; 260
-Landroid/hardware/camera2/CaptureRequest;.CONTROL_EFFECT_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.LENS_APERTURE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.JPEG_THUMBNAIL_QUALITY:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Landroid/hardware/camera2/CaptureRequest;.CONTROL_AF_REGIONS:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-Ljava/lang/Long; 260
-Landroid/hardware/camera2/CaptureRequest;.SENSOR_PIXEL_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260
-[Ljava/lang/String; 261
-[Z 262
-Ljava/lang/Class$Caches;.genericInterfaces:Llibcore/util/BasicLruCache;.map:Ljava/util/LinkedHashMap; 263
-Ljava/util/Map; 264
-Ljava/nio/Bits; 265
-Ljava/nio/DirectByteBuffer; 266
-Ljava/io/File; 267
-Ljava/nio/ByteBuffer; 268
-Ljava/io/InputStream; 269
-Landroid/os/ParcelFileDescriptor; 270
-Landroid/os/BinderProxy;.sProxyMap:Landroid/os/BinderProxy$ProxyMap; 271
-Landroid/app/PendingIntent; 272
-Landroid/content/Intent; 273
-Landroid/net/Uri$HierarchicalUri; 274
-Landroid/net/Uri$StringUri; 275
-Landroid/net/Uri$PathPart;.EMPTY:Landroid/net/Uri$PathPart; 276
-Lcom/android/internal/telephony/MccTable;.FALLBACKS:Ljava/util/Map;.table:[Ljava/util/HashMap$Node;.6:Ljava/util/HashMap$Node;.key:Ljava/lang/Object; 277
-Landroid/icu/text/DecimalFormatSymbols;.cachedLocaleData:Landroid/icu/impl/CacheBase;.map:Ljava/util/concurrent/ConcurrentHashMap; 278
-Llibcore/icu/DecimalFormatData;.CACHE:Ljava/util/concurrent/ConcurrentHashMap; 279
-Landroid/icu/impl/CurrencyData;.provider:Landroid/icu/impl/CurrencyData$CurrencyDisplayInfoProvider; 280
-Lcom/android/internal/infra/AndroidFuture; 281
-Lcom/android/internal/util/LatencyTracker$Action; 282
-Landroid/app/AppOpsManager$Mode; 283
-Landroid/view/accessibility/AccessibilityManager$AccessibilityServicesStateChangeListener; 284
-Landroid/annotation/IdRes; 285
-Landroid/content/pm/PackageItemInfo; 286
-Ljava/util/Random; 287
-Landroid/widget/RadioButton; 288
-Lcom/android/internal/policy/PhoneWindow$PanelFeatureState$SavedState; 289
-Landroid/graphics/Insets; 290
-Landroid/view/View;.sNextGeneratedId:Ljava/util/concurrent/atomic/AtomicInteger; 291
-Landroid/graphics/drawable/LayerDrawable; 292
-Landroid/animation/LayoutTransition; 293
-Llibcore/reflect/AnnotationFactory;.cache:Ljava/util/Map; 294
-Llibcore/reflect/AnnotationFactory;.cache:Ljava/util/Map;.table:[Ljava/util/WeakHashMap$Entry; 294
-Ljava/lang/reflect/Proxy;.proxyClassCache:Ljava/lang/reflect/WeakCache;.reverseMap:Ljava/util/concurrent/ConcurrentMap; 295
-Ljava/lang/reflect/Proxy$ProxyClassFactory;.nextUniqueNumber:Ljava/util/concurrent/atomic/AtomicLong; 295
-Ljava/lang/reflect/Proxy;.proxyClassCache:Ljava/lang/reflect/WeakCache;.map:Ljava/util/concurrent/ConcurrentMap; 296
-Ljava/lang/Object; 297
-Ljava/lang/invoke/MethodType;.internTable:Ljava/lang/invoke/MethodType$ConcurrentWeakInternSet;.map:Ljava/util/concurrent/ConcurrentMap; 298
-Ljava/nio/channels/SocketChannel;.dexCache:Ljava/lang/Object; 298
-Ljava/lang/invoke/MethodType;.objectOnlyTypes:[Ljava/lang/invoke/MethodType; 299
-Ljava/util/concurrent/ForkJoinTask; 300
-Ljava/util/concurrent/CompletableFuture; 301
-Landroid/app/Notification$BigTextStyle; 302
-Landroid/content/pm/ApplicationInfo; 303
-Ljava/security/Signature;.signatureInfo:Ljava/util/Map;.table:[Ljava/util/concurrent/ConcurrentHashMap$Node;.13:Ljava/util/concurrent/ConcurrentHashMap$Node;.next:Ljava/util/concurrent/ConcurrentHashMap$Node;.next:Ljava/util/concurrent/ConcurrentHashMap$Node; 304
-Lsun/security/x509/X500Name;.stateName_oid:Lsun/security/util/ObjectIdentifier; 305
-Lsun/security/x509/X500Name;.localityName_oid:Lsun/security/util/ObjectIdentifier; 306
-Lsun/security/x509/X500Name;.orgUnitName_oid:Lsun/security/util/ObjectIdentifier; 306
-Ljava/util/UUID; 307
-Landroid/app/slice/Slice; 308
-Ljava/util/Locale;.FRENCH:Ljava/util/Locale; 308
-Landroid/os/NullVibrator; 308
-Ldalvik/system/CloseGuard;.MESSAGE:Ljava/lang/String; 308
-Lsun/util/locale/BaseLocale$Cache;.CACHE:Lsun/util/locale/BaseLocale$Cache;.map:Ljava/util/concurrent/ConcurrentMap;.table:[Ljava/util/concurrent/ConcurrentHashMap$Node;.22:Ljava/util/concurrent/ConcurrentHashMap$Node;.val:Ljava/lang/Object;.referent:Ljava/lang/Object; 308
-Ljava/util/Locale$Cache;.LOCALECACHE:Ljava/util/Locale$Cache;.map:Ljava/util/concurrent/ConcurrentMap;.table:[Ljava/util/concurrent/ConcurrentHashMap$Node;.24:Ljava/util/concurrent/ConcurrentHashMap$Node;.val:Ljava/lang/Object;.referent:Ljava/lang/Object; 308
-Landroid/app/Activity$$ExternalSyntheticLambda0; 308
-Landroid/icu/impl/locale/BaseLocale;.CACHE:Landroid/icu/impl/locale/BaseLocale$Cache;._map:Ljava/util/concurrent/ConcurrentHashMap;.table:[Ljava/util/concurrent/ConcurrentHashMap$Node;.0:Ljava/util/concurrent/ConcurrentHashMap$Node; 308
-Ljava/util/Locale;.ITALIAN:Ljava/util/Locale; 308
-Landroid/media/MediaRouter2Manager$Callback; 308
-Lsun/util/locale/BaseLocale$Cache;.CACHE:Lsun/util/locale/BaseLocale$Cache;.map:Ljava/util/concurrent/ConcurrentMap;.table:[Ljava/util/concurrent/ConcurrentHashMap$Node;.29:Ljava/util/concurrent/ConcurrentHashMap$Node;.val:Ljava/lang/Object;.referent:Ljava/lang/Object; 308
-Ljava/util/Locale;.GERMAN:Ljava/util/Locale; 309
-Landroid/icu/impl/StandardPlural; 310
-Landroid/icu/impl/number/range/StandardPluralRanges; 311
-Landroid/icu/impl/PluralRulesLoader;.loader:Landroid/icu/impl/PluralRulesLoader; 311
-Landroid/icu/impl/PluralRulesLoader;.loader:Landroid/icu/impl/PluralRulesLoader;.pluralRulesCache:Ljava/util/Map; 311
-Landroid/icu/text/PluralRules$Operand; 311
-Landroid/icu/util/Calendar;.PATTERN_CACHE:Landroid/icu/impl/ICUCache; 312
-Landroid/icu/impl/DateNumberFormat;.CACHE:Landroid/icu/impl/SimpleCache; 313
-Landroid/text/format/DateFormat; 314
-Landroid/view/View$OnDragListener; 315
-Landroid/hardware/input/InputManager$InputDeviceListener; 316
-Landroid/hardware/input/InputManagerGlobal; 317
-Landroid/hardware/SystemSensorManager; 318
-Lcom/android/internal/os/BackgroundThread; 319
-Ljava/lang/Throwable; 320
-Landroid/app/NotificationManager; 321
-Landroid/app/NotificationChannel; 322
-Landroid/content/SharedPreferences$OnSharedPreferenceChangeListener; 323
-Landroid/content/pm/VersionedPackage; 324
-Landroid/app/AppOpsManager; 325
-Ldalvik/system/ZipPathValidator; 326
-Landroid/content/pm/PackageManager;.sPackageInfoCache:Landroid/app/PropertyInvalidatedCache;.mSkips:[J 327
-Landroid/content/pm/PackageManager;.sApplicationInfoCache:Landroid/app/PropertyInvalidatedCache;.mSkips:[J 328
-Lsun/util/locale/BaseLocale$Cache;.CACHE:Lsun/util/locale/BaseLocale$Cache;.map:Ljava/util/concurrent/ConcurrentMap; 329
-Landroid/content/Context; 330
-Ljava/util/concurrent/Executor; 331
-Ljava/util/concurrent/ScheduledExecutorService; 332
-Ljava/util/concurrent/ExecutorService; 332
-Landroid/view/Window$OnFrameMetricsAvailableListener; 333
-Ljava/lang/annotation/Annotation; 334
-Ljava/lang/Enum;.sharedConstantsCache:Llibcore/util/BasicLruCache;.map:Ljava/util/LinkedHashMap;.tail:Ljava/util/LinkedHashMap$LinkedHashMapEntry;.before:Ljava/util/LinkedHashMap$LinkedHashMapEntry;.before:Ljava/util/LinkedHashMap$LinkedHashMapEntry;.before:Ljava/util/LinkedHashMap$LinkedHashMapEntry;.before:Ljava/util/LinkedHashMap$LinkedHashMapEntry; 335
-Ljava/util/concurrent/CancellationException; 336
-Ljava/lang/NoSuchMethodException; 337
-Landroid/os/strictmode/CustomViolation; 338
-Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.3:Ljava/util/WeakHashMap$Entry; 339
-Lcom/android/internal/policy/PhoneWindow; 340
-Landroid/view/autofill/AutofillValue; 340
-Landroid/widget/TextView$SavedState; 341
-Landroid/text/method/MetaKeyKeyListener;.SYM:Ljava/lang/Object; 342
-Landroid/text/method/MetaKeyKeyListener;.ALT:Ljava/lang/Object; 342
-Landroid/text/method/MetaKeyKeyListener;.SELECTING:Ljava/lang/Object; 342
-Landroid/text/method/MetaKeyKeyListener;.CAP:Ljava/lang/Object; 342
-Landroid/widget/PopupWindow$PopupBackgroundView; 343
-Landroid/widget/TextView;.TEMP_RECTF:Landroid/graphics/RectF; 343
-Landroid/text/method/ScrollingMovementMethod; 343
-Landroid/icu/impl/locale/UnicodeLocaleExtension;.EMPTY_SORTED_SET:Ljava/util/SortedSet;.m:Ljava/util/NavigableMap; 343
-Landroid/widget/PopupWindow$PopupDecorView; 343
-Landroid/widget/Editor$TextRenderNode; 343
-Landroid/widget/Editor$PositionListener; 344
-Landroid/text/style/SpellCheckSpan; 345
-Landroid/text/method/ArrowKeyMovementMethod; 346
-Landroid/text/method/TextKeyListener;.sInstance:[Landroid/text/method/TextKeyListener; 346
-Landroid/text/TextUtils$TruncateAt;.MARQUEE:Landroid/text/TextUtils$TruncateAt; 347
-Landroid/view/autofill/Helper; 348
-Lcom/android/internal/util/LatencyTracker; 349
-Lcom/android/internal/util/LatencyTracker$SLatencyTrackerHolder; 349
-Landroid/graphics/drawable/Icon; 350
-Landroid/text/style/AlignmentSpan; 351
-Landroid/text/MeasuredParagraph;.sPool:Landroid/util/Pools$SynchronizedPool;.mPool:[Ljava/lang/Object; 352
-Landroid/text/MeasuredParagraph;.sPool:Landroid/util/Pools$SynchronizedPool; 352
-Landroid/icu/impl/ICUResourceBundleReader;.CACHE:Landroid/icu/impl/ICUResourceBundleReader$ReaderCache;.map:Ljava/util/concurrent/ConcurrentHashMap; 353
-Landroid/location/ILocationManager$Stub;.dexCache:Ljava/lang/Object; 354
-Landroid/annotation/CurrentTimeMillisLong; 355
-Ljava/lang/reflect/Method; 356
-Lcom/android/internal/os/ZygoteInit; 356
-Landroid/database/DatabaseUtils; 356
-Landroid/os/HandlerThread; 356
-Ljava/security/Signature;.signatureInfo:Ljava/util/Map;.table:[Ljava/util/concurrent/ConcurrentHashMap$Node; 357
-Ljava/security/Signature;.signatureInfo:Ljava/util/Map; 358
-Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.queue:Ljava/lang/ref/ReferenceQueue; 359
-Landroid/telephony/TelephonyRegistryManager; 360
-Landroid/graphics/HardwareRenderer; 361
-Landroid/os/BinderProxy; 362
-Landroid/app/compat/CompatChanges;.QUERY_CACHE:Landroid/app/compat/ChangeIdStateCache;.mCache:Ljava/util/LinkedHashMap; 363
-Landroid/app/compat/CompatChanges;.QUERY_CACHE:Landroid/app/compat/ChangeIdStateCache; 363
-Landroid/app/AlarmManager; 364
-Landroid/net/metrics/DhcpClientEvent; 365
-[I 366
-Landroid/media/MediaCodecList; 367
-Landroid/graphics/drawable/InsetDrawable; 368
-Landroid/widget/ProgressBar$SavedState; 369
-Landroid/widget/ScrollView$SavedState; 370
-Landroid/graphics/drawable/AnimatedVectorDrawable; 371
-Landroid/widget/ListView; 372
-Landroid/widget/AbsListView; 373
-Landroid/widget/AbsListView$SavedState; 373
-Landroid/widget/CompoundButton$SavedState; 374
-Landroid/widget/HorizontalScrollView$SavedState; 375
-Landroid/widget/HorizontalScrollView; 376
-Landroid/icu/text/MeasureFormat;.hmsTo012:Ljava/util/Map;.table:[Ljava/util/HashMap$Node;.1:Ljava/util/HashMap$Node;.value:Ljava/lang/Object; 377
-Landroid/icu/text/MeasureFormat;.hmsTo012:Ljava/util/Map;.table:[Ljava/util/HashMap$Node;.6:Ljava/util/HashMap$Node;.value:Ljava/lang/Object; 377
-Landroid/view/View$OnSystemUiVisibilityChangeListener; 378
-Ljava/util/AbstractMap; 379
-Landroid/telephony/euicc/EuiccCardManager$ResultCallback; 380
-Ljava/lang/Character$UnicodeBlock;.CJK_SYMBOLS_AND_PUNCTUATION:Ljava/lang/Character$UnicodeBlock; 381
-Ljava/lang/Character$UnicodeBlock;.KANBUN:Ljava/lang/Character$UnicodeBlock; 381
-Ljava/lang/Character$UnicodeBlock;.HANGUL_COMPATIBILITY_JAMO:Ljava/lang/Character$UnicodeBlock; 381
-Ljava/lang/Character$UnicodeBlock;.KATAKANA:Ljava/lang/Character$UnicodeBlock; 381
-Ljava/lang/Character$UnicodeBlock;.HANGUL_SYLLABLES:Ljava/lang/Character$UnicodeBlock; 381
-Ljava/lang/Character$UnicodeBlock;.ENCLOSED_CJK_LETTERS_AND_MONTHS:Ljava/lang/Character$UnicodeBlock; 381
-Ljava/lang/Character$UnicodeBlock;.HANGUL_JAMO:Ljava/lang/Character$UnicodeBlock; 381
-Ljava/lang/Character$UnicodeBlock;.BOPOMOFO_EXTENDED:Ljava/lang/Character$UnicodeBlock; 381
-Ljava/lang/Character$UnicodeBlock;.CJK_COMPATIBILITY_FORMS:Ljava/lang/Character$UnicodeBlock; 381
-Ljava/lang/Character$UnicodeBlock;.BOPOMOFO:Ljava/lang/Character$UnicodeBlock; 381
-Ljava/lang/Character$UnicodeBlock;.HIRAGANA:Ljava/lang/Character$UnicodeBlock; 381
-Ljava/lang/Character$UnicodeBlock;.HALFWIDTH_AND_FULLWIDTH_FORMS:Ljava/lang/Character$UnicodeBlock; 381
-Ljava/lang/Character$UnicodeBlock;.KANGXI_RADICALS:Ljava/lang/Character$UnicodeBlock; 381
-Ljava/lang/Character$UnicodeBlock;.CJK_RADICALS_SUPPLEMENT:Ljava/lang/Character$UnicodeBlock; 381
-Ljava/lang/Character$UnicodeBlock;.KATAKANA_PHONETIC_EXTENSIONS:Ljava/lang/Character$UnicodeBlock; 381
-Ljava/lang/Character$UnicodeBlock;.CJK_COMPATIBILITY:Ljava/lang/Character$UnicodeBlock; 381
-Ljava/lang/Character$UnicodeBlock;.CJK_COMPATIBILITY_IDEOGRAPHS_SUPPLEMENT:Ljava/lang/Character$UnicodeBlock; 382
-Ljava/lang/Character$UnicodeBlock;.CJK_COMPATIBILITY_IDEOGRAPHS:Ljava/lang/Character$UnicodeBlock; 382
-Ljava/lang/Character$UnicodeBlock;.CJK_UNIFIED_IDEOGRAPHS:Ljava/lang/Character$UnicodeBlock; 382
-Ljava/lang/Character$UnicodeBlock;.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A:Ljava/lang/Character$UnicodeBlock; 382
-Ljava/lang/Character$UnicodeBlock;.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B:Ljava/lang/Character$UnicodeBlock; 382
-Lcom/android/internal/inputmethod/InputMethodPrivilegedOperationsRegistry; 383
-Landroid/view/Window$DecorCallback; 383
-Landroid/view/inputmethod/EditorInfo; 383
-Landroid/view/MenuItem$OnActionExpandListener; 384
-Ljava/util/Locale;.JAPANESE:Ljava/util/Locale; 385
-Ljava/util/Locale;.KOREAN:Ljava/util/Locale; 385
-Lcom/android/internal/config/appcloning/AppCloningDeviceConfigHelper; 386
-Landroid/telecom/PhoneAccountHandle; 387
-Landroid/content/AsyncQueryHandler; 388
-Landroid/speech/RecognitionListener; 389
-Ljava/lang/InstantiationException; 390
-Ljava/util/concurrent/ExecutionException; 391
-Landroid/icu/text/DateIntervalInfo;.DIICACHE:Landroid/icu/impl/ICUCache; 392
-Landroid/text/format/DateIntervalFormat;.CACHED_FORMATTERS:Landroid/util/LruCache;.map:Ljava/util/LinkedHashMap; 392
-Landroid/icu/text/DateIntervalFormat;.LOCAL_PATTERN_CACHE:Landroid/icu/impl/ICUCache; 392
-Landroid/icu/impl/OlsonTimeZone; 392
-Landroid/text/format/DateIntervalFormat;.CACHED_FORMATTERS:Landroid/util/LruCache; 392
-Landroid/graphics/drawable/Drawable; 393
-Ljava/lang/Enum;.sharedConstantsCache:Llibcore/util/BasicLruCache;.map:Ljava/util/LinkedHashMap;.tail:Ljava/util/LinkedHashMap$LinkedHashMapEntry; 394
-Landroid/app/Activity; 395
-Landroid/icu/text/PluralRules$KeywordStatus;.INVALID:Landroid/icu/text/PluralRules$KeywordStatus;.name:Ljava/lang/String; 396
-Landroid/net/Uri; 396
-Lsun/util/calendar/CalendarSystem;.calendars:Ljava/util/concurrent/ConcurrentMap; 396
-Landroid/animation/PropertyValuesHolder$IntPropertyValuesHolder;.sJNISetterPropertyMap:Ljava/util/HashMap; 397
-Landroid/graphics/drawable/ShapeDrawable; 398
-Lcom/android/internal/widget/ActionBarContextView; 399
-Landroid/widget/Toolbar$SavedState; 399
-Lcom/android/internal/widget/ActionBarContainer; 399
-Lcom/android/internal/widget/ActionBarOverlayLayout; 399
-Lcom/android/internal/widget/ActionBarContainer$ActionBarBackgroundDrawable; 399
-Landroid/widget/ActionMenuPresenter$OverflowMenuButton; 400
-Landroid/widget/ActionMenuView; 401
-Landroid/content/res/Configuration; 402
-Ljava/util/IdentityHashMap;.NULL_KEY:Ljava/lang/Object; 403
-Ljava/util/concurrent/ForkJoinPool; 404
-Landroid/os/ResultReceiver; 405
-Ljava/util/concurrent/TimeoutException; 406
-Ljava/io/IOException; 407
-Landroid/accounts/AccountAuthenticatorResponse; 408
-Landroid/nfc/NfcAdapter; 409
-Landroid/nfc/NfcAdapter;.sNfcAdapters:Ljava/util/HashMap; 409
-Landroid/app/backup/BackupManager; 410
-Landroid/app/NotificationChannelGroup; 411
-Landroid/content/pm/ParceledListSlice; 411
-Landroid/os/FileObserver; 412
-Landroid/os/UserHandle; 413
-Landroid/content/pm/PackageManager$NameNotFoundException; 414
-[Ljava/lang/Integer; 415
-Landroid/animation/PropertyValuesHolder;.sSetterPropertyMap:Ljava/util/HashMap; 415
-Landroid/content/LocusId; 416
-Landroid/view/contentcapture/ContentCaptureContext; 416
-Landroid/telephony/ims/RegistrationManager;.IMS_REG_TO_ACCESS_TYPE_MAP:Ljava/util/Map;.table:[Ljava/lang/Object;.18:Ljava/lang/Integer; 417
-Lcom/android/org/bouncycastle/asn1/ASN1ObjectIdentifier;.pool:Ljava/util/concurrent/ConcurrentMap;.table:[Ljava/util/concurrent/ConcurrentHashMap$Node;.4:Ljava/util/concurrent/ConcurrentHashMap$Node; 418
-Lcom/android/org/bouncycastle/asn1/ASN1ObjectIdentifier;.pool:Ljava/util/concurrent/ConcurrentMap;.table:[Ljava/util/concurrent/ConcurrentHashMap$Node; 418
-Lcom/android/org/bouncycastle/asn1/ASN1ObjectIdentifier;.pool:Ljava/util/concurrent/ConcurrentMap;.table:[Ljava/util/concurrent/ConcurrentHashMap$Node;.2:Ljava/util/concurrent/ConcurrentHashMap$Node;.next:Ljava/util/concurrent/ConcurrentHashMap$Node; 418
-Lcom/android/internal/telephony/cdnr/CarrierDisplayNameResolver;.EF_SOURCE_PRIORITY:Ljava/util/List;.a:[Ljava/lang/Object;.9:Ljava/lang/Integer; 418
-Lcom/android/org/bouncycastle/asn1/ASN1ObjectIdentifier;.pool:Ljava/util/concurrent/ConcurrentMap;.table:[Ljava/util/concurrent/ConcurrentHashMap$Node;.56:Ljava/util/concurrent/ConcurrentHashMap$Node; 418
-Lcom/android/internal/telephony/cdnr/CarrierDisplayNameResolver;.EF_SOURCE_PRIORITY:Ljava/util/List;.a:[Ljava/lang/Object;.5:Ljava/lang/Integer; 418
-Lcom/android/org/bouncycastle/asn1/ASN1ObjectIdentifier;.pool:Ljava/util/concurrent/ConcurrentMap; 418
-Landroid/widget/EditText; 419
-Landroid/widget/CheckedTextView; 420
-Landroid/os/strictmode/UnsafeIntentLaunchViolation; 421
-Landroid/app/Service; 422
-Ldalvik/system/BlockGuard; 423
-Landroid/hardware/devicestate/DeviceStateManagerGlobal; 424
-Landroid/hardware/camera2/CameraCharacteristics;.SCALER_MULTI_RESOLUTION_STREAM_SUPPORTED:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 425
-Landroid/hardware/camera2/marshal/MarshalRegistry;.sMarshalerMap:Ljava/util/HashMap; 425
-Landroid/hardware/camera2/CameraCharacteristics;.LENS_FACING:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 425
-Landroid/content/ClipboardManager$OnPrimaryClipChangedListener; 426
-Landroid/icu/text/BreakIterator;.iterCache:[Landroid/icu/impl/CacheValue; 427
-Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.13:Ljava/util/WeakHashMap$Entry; 428
-Landroid/icu/text/Collator; 429
-Landroid/icu/impl/number/parse/NanMatcher;.DEFAULT:Landroid/icu/impl/number/parse/NanMatcher;.uniSet:Landroid/icu/text/UnicodeSet;.strings:Ljava/util/SortedSet;.c:Ljava/util/Collection;.m:Ljava/util/NavigableMap; 430
-Ljava/io/FileNotFoundException; 431
-Landroid/os/BaseBundle; 432
-Landroid/service/watchdog/ExplicitHealthCheckService$PackageConfig; 433
-Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.116:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.12:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.90:Ljava/lang/String; 434
-Landroid/icu/text/MessageFormat;.rootLocale:Ljava/util/Locale;.baseLocale:Lsun/util/locale/BaseLocale;.language:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.385:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.107:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.112:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.480:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.550:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.143:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._obsoleteLanguages:[Ljava/lang/String;.1:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.473:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.138:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.204:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.71:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._replacementCountries:[Ljava/lang/String;.12:Ljava/lang/String; 434
-Landroid/icu/impl/duration/impl/DataRecord$ETimeLimit;.names:[Ljava/lang/String;.1:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._replacementCountries:[Ljava/lang/String;.9:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.99:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages3:[Ljava/lang/String;.152:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.256:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.170:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.220:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.461:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._replacementLanguages:[Ljava/lang/String;.5:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.190:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.157:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._obsoleteCountries:[Ljava/lang/String;.4:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.196:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.117:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.5:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.499:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.199:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.18:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.324:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.101:Ljava/lang/String; 434
-Landroid/icu/impl/locale/UnicodeLocaleExtension;.CA_JAPANESE:Landroid/icu/impl/locale/UnicodeLocaleExtension;._keywords:Ljava/util/SortedMap;.root:Ljava/util/TreeMap$TreeMapEntry;.key:Ljava/lang/Object; 434
-Landroid/icu/impl/LocaleIDs;._replacementCountries:[Ljava/lang/String;.3:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.140:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.105:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.37:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._replacementCountries:[Ljava/lang/String;.5:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.22:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.103:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.412:Ljava/lang/String; 434
-Landroid/icu/impl/duration/impl/DataRecord$EMilliSupport;.names:[Ljava/lang/String;.1:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.124:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.232:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.219:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.179:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.523:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.75:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.486:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.166:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.112:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.119:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.160:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.298:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.257:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.182:Ljava/lang/String; 434
-Landroid/icu/impl/units/UnitPreferences;.measurementSystem:Ljava/util/Map;.m:Ljava/util/Map;.table:[Ljava/util/HashMap$Node;.5:Ljava/util/HashMap$Node;.next:Ljava/util/HashMap$Node;.value:Ljava/lang/Object; 434
-Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.47:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.180:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.111:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.358:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.96:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._obsoleteLanguages:[Ljava/lang/String;.0:Ljava/lang/String; 434
-Landroid/icu/text/DateFormat;.HOUR_GENERIC_TZ:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.67:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.254:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.222:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.55:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.349:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.16:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.352:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.443:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.478:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.19:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.401:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.137:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.65:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.474:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.168:Ljava/lang/String; 434
-Landroid/icu/impl/units/UnitPreferences;.measurementSystem:Ljava/util/Map;.m:Ljava/util/Map;.table:[Ljava/util/HashMap$Node;.15:Ljava/util/HashMap$Node;.value:Ljava/lang/Object; 434
-Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.111:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages3:[Ljava/lang/String;.545:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.30:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.469:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.21:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.69:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.56:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.519:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._replacementLanguages:[Ljava/lang/String;.4:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.107:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.290:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.59:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.220:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.186:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.516:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.181:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.199:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.396:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.117:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.227:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.331:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.447:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.151:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.144:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.132:Ljava/lang/String; 434
-Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.230:Ljava/lang/String; 434
-Landroid/icu/text/DateFormat;.MINUTE_SECOND:Ljava/lang/String; 434
-Ljava/lang/Enum;.sharedConstantsCache:Llibcore/util/BasicLruCache;.map:Ljava/util/LinkedHashMap;.tail:Ljava/util/LinkedHashMap$LinkedHashMapEntry;.before:Ljava/util/LinkedHashMap$LinkedHashMapEntry; 435
-Ljava/lang/Enum;.sharedConstantsCache:Llibcore/util/BasicLruCache;.map:Ljava/util/LinkedHashMap;.head:Ljava/util/LinkedHashMap$LinkedHashMapEntry; 436
-Ljava/lang/Enum;.sharedConstantsCache:Llibcore/util/BasicLruCache;.map:Ljava/util/LinkedHashMap;.tail:Ljava/util/LinkedHashMap$LinkedHashMapEntry;.before:Ljava/util/LinkedHashMap$LinkedHashMapEntry;.before:Ljava/util/LinkedHashMap$LinkedHashMapEntry; 437
-Landroid/graphics/drawable/ColorStateListDrawable; 438
-Ljava/lang/SecurityException; 439
-Ljava/lang/RuntimeException; 440
-Landroid/media/audiopolicy/AudioProductStrategy; 441
-Landroid/os/PersistableBundle; 442
-Landroid/content/pm/ShortcutInfo; 442
-Landroid/icu/text/TimeZoneFormat;._tzfCache:Landroid/icu/text/TimeZoneFormat$TimeZoneFormatCache;.map:Ljava/util/concurrent/ConcurrentHashMap; 443
-Landroid/graphics/LeakyTypefaceStorage;.sStorage:Ljava/util/ArrayList; 443
-Landroid/graphics/LeakyTypefaceStorage;.sTypefaceMap:Landroid/util/ArrayMap; 443
-Landroid/text/TextWatcher; 444
-Landroid/view/accessibility/AccessibilityManager$TouchExplorationStateChangeListener; 445
-Ljavax/net/SocketFactory; 446
-Ljava/util/Collections; 447
-Ljava/lang/Exception; 448
-Landroid/os/UserManager; 449
-Landroid/os/RemoteException; 450
-Landroid/content/AttributionSource; 451
-Lcom/android/okhttp/internalandroidapi/HttpURLConnectionFactory$DnsAdapter; 452
-Lcom/android/okhttp/Protocol;.HTTP_2:Lcom/android/okhttp/Protocol; 452
-Ljava/net/Inet4Address; 452
-Lcom/android/okhttp/Protocol;.SPDY_3:Lcom/android/okhttp/Protocol; 452
-Lcom/android/okhttp/OkHttpClient; 452
-Landroid/os/storage/VolumeInfo; 453
-Landroid/os/storage/DiskInfo; 453
-Landroid/view/textclassifier/TextLanguage;.EMPTY:Landroid/view/textclassifier/TextLanguage;.mBundle:Landroid/os/Bundle;.mMap:Landroid/util/ArrayMap; 454
-Ljava/nio/file/StandardOpenOption;.WRITE:Ljava/nio/file/StandardOpenOption; 455
-Ljava/nio/file/StandardOpenOption;.APPEND:Ljava/nio/file/StandardOpenOption; 456
-Ljava/util/logging/FileHandler; 457
-Ljava/nio/file/StandardOpenOption;.CREATE_NEW:Ljava/nio/file/StandardOpenOption; 457
-Ljava/util/logging/FileHandler;.locks:Ljava/util/Set;.map:Ljava/util/HashMap; 457
-Lsun/nio/ch/SharedFileLockTable;.queue:Ljava/lang/ref/ReferenceQueue; 458
-Ljavax/net/ServerSocketFactory; 458
-Landroid/os/AsyncTask; 459
-Landroid/os/strictmode/UnbufferedIoViolation; 460
-Landroid/app/usage/AppStandbyInfo; 461
-Landroid/text/format/DateUtils; 462
-Landroid/security/IKeyChainService; 463
-Landroid/util/Log$TerribleFailure; 464
-Lcom/android/internal/os/RuntimeInit$KillApplicationHandler; 464
-Ljava/util/Timer;.nextSerialNumber:Ljava/util/concurrent/atomic/AtomicInteger; 465
-Landroid/telephony/ims/stub/ImsConfigImplBase$ImsConfigStub; 466
-Landroid/telephony/ims/stub/ImsRegistrationImplBase$1; 466
-Landroid/telephony/ims/ImsUtListener; 466
-Landroid/telephony/ims/feature/MmTelFeature$1; 466
-Lcom/android/ims/ImsManager;.IMS_MANAGER_INSTANCES:Landroid/util/SparseArray;.mValues:[Ljava/lang/Object; 467
-Lcom/android/ims/ImsManager;.IMS_MANAGER_INSTANCES:Landroid/util/SparseArray; 467
-Lcom/android/ims/ImsManager;.IMS_MANAGER_INSTANCES:Landroid/util/SparseArray;.mKeys:[I 467
-Landroid/telephony/ims/aidl/IImsConfig$Stub$Proxy; 468
-Landroid/telephony/ims/aidl/IImsRegistration$Stub$Proxy; 468
-Landroid/telephony/NetworkService; 469
-Landroid/telephony/TelephonyCallback$ServiceStateListener; 470
-Landroid/telephony/TelephonyCallback$PhysicalChannelConfigListener; 471
-Landroid/telephony/TelephonyCallback$RadioPowerStateListener; 471
-Lsun/security/x509/X500Name;.internedOIDs:Ljava/util/Map; 472
-Lsun/security/x509/X500Name;.internedOIDs:Ljava/util/Map;.table:[Ljava/util/HashMap$Node; 472
-Landroid/media/MediaCodec; 473
-Ljava/nio/file/StandardOpenOption;.CREATE:Ljava/nio/file/StandardOpenOption; 474
-Ljava/nio/file/NoSuchFileException; 475
-Ljava/text/DateFormatSymbols;.cachedInstances:Ljava/util/concurrent/ConcurrentMap; 476
-Ljava/util/Currency;.instances:Ljava/util/concurrent/ConcurrentMap; 476
-Ljava/util/Calendar;.cachedLocaleData:Ljava/util/concurrent/ConcurrentMap; 476
-Ljava/text/SimpleDateFormat;.cachedNumberFormatData:Ljava/util/concurrent/ConcurrentMap; 476
-Landroid/app/UriGrantsManager;.IUriGrantsManagerSingleton:Landroid/util/Singleton; 477
-Landroid/content/ContentProviderProxy; 478
-Landroid/os/DeadObjectException; 479
-Landroid/app/slice/SliceSpec; 479
-Landroid/database/sqlite/SQLiteDatabase; 480
-Ljava/util/Locale;.CHINA:Ljava/util/Locale; 481
-Ljava/util/Locale;.TAIWAN:Ljava/util/Locale; 481
-Ljava/util/Locale;.KOREA:Ljava/util/Locale; 481
-Ljava/util/Scanner; 482
-Ljava/math/BigDecimal; 483
-Ljava/security/interfaces/RSAPrivateCrtKey; 483
-Ljava/security/interfaces/RSAPrivateKey; 483
-Lcom/android/server/backup/AccountSyncSettingsBackupHelper;.KEY_ACCOUNT_TYPE:Ljava/lang/String; 483
-Landroid/util/UtilConfig; 484
-Ljava/net/ResponseCache; 485
-Landroid/content/ReceiverCallNotAllowedException; 486
-Landroid/app/ReceiverRestrictedContext; 487
-Landroid/os/strictmode/CredentialProtectedWhileLockedViolation; 488
-Landroid/app/Application; 489
-Ljava/util/NoSuchElementException; 490
-Landroid/os/Messenger; 491
-Landroid/telephony/TelephonyCallback$DataEnabledListener; 491
-Landroid/system/StructLinger; 492
diff --git a/config/dirty-image-objects.txt b/config/dirty-image-objects.txt
new file mode 100644
index 0000000..2c51511
--- /dev/null
+++ b/config/dirty-image-objects.txt
@@ -0,0 +1,1391 @@
+#
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+#
+#
+# Framework dirty-image-objects file for boot image.
+# The image writer will bin these objects together in the image.
+# There are two dirty-image-objects files:
+# 1) art/build/apex/dirty-image-objects.txt - contains classes and objects
+# referenced by classes defined in the ART module.
+# 2) frameworks/base/config/dirty-image-objects.txt - contains classes and
+# objects referenced by classes defined in framework modules.
+#
+# More info about dirty objects format and how to collect the data can be
+# found in: art/imgdiag/dirty_image_objects.md
+# This particular file was generated by running top 100 Android apps.
+#
+Landroid/util/Base64; 0
+Landroid/os/Debug; 1
+Landroid/content/pm/ResolveInfo; 4
+Landroid/widget/AutoCompleteTextView; 7
+Landroid/widget/ToggleButton; 8
+Landroid/widget/MultiAutoCompleteTextView; 9
+Landroid/speech/RecognitionListener; 10
+Landroid/icu/util/Calendar;.PATTERN_CACHE:Landroid/icu/impl/ICUCache; 12
+Landroid/icu/impl/DateNumberFormat;.CACHE:Landroid/icu/impl/SimpleCache; 13
+Landroid/view/ViewTreeObserver$OnWindowVisibilityChangeListener; 14
+Landroid/window/WindowContainerTransaction$Change; 14
+Landroid/window/WindowOrganizer;.IWindowOrganizerControllerSingleton:Landroid/util/Singleton; 14
+Landroid/widget/Toast; 14
+Landroid/app/smartspace/SmartspaceSession$OnTargetsAvailableListener; 14
+Landroid/view/CrossWindowBlurListeners; 15
+Landroid/text/TextUtils$TruncateAt; 16
+Landroid/app/smartspace/SmartspaceTarget; 17
+Landroid/app/smartspace/uitemplatedata/BaseTemplateData; 17
+Landroid/content/res/Resources$NotFoundException; 18
+Landroid/app/prediction/AppTarget; 18
+Landroid/icu/impl/number/parse/NanMatcher;.DEFAULT:Landroid/icu/impl/number/parse/NanMatcher;.uniSet:Landroid/icu/text/UnicodeSet;.strings:Ljava/util/SortedSet;.c:Ljava/util/Collection;.m:Ljava/util/NavigableMap; 19
+Landroid/app/prediction/AppTargetEvent; 19
+Landroid/content/res/Configuration; 19
+Landroid/os/strictmode/UnbufferedIoViolation; 21
+Lcom/android/internal/os/ProcessCpuTracker$FilterStats; 22
+Lcom/android/internal/view/WindowManagerPolicyThread; 22
+Landroid/content/pm/ShortcutServiceInternal; 22
+Landroid/app/ServiceStartArgs; 22
+Lcom/android/internal/util/ToBooleanFunction; 22
+Landroid/app/ActivityManager$RecentTaskInfo; 22
+Landroid/telecom/Logging/EventManager$EventListener; 22
+Landroid/app/assist/AssistStructure; 22
+Landroid/app/Notification$DecoratedMediaCustomViewStyle; 22
+Landroid/annotation/StringRes; 22
+Landroid/util/MemoryIntArray; 22
+Landroid/graphics/Region;.sPool:Landroid/util/Pools$SynchronizedPool;.mPool:[Ljava/lang/Object; 22
+Landroid/view/RoundedCorners; 22
+Landroid/media/AudioSystem$DynamicPolicyCallback; 22
+Lcom/android/internal/R$styleable;.Window:[I 22
+Landroid/app/servertransaction/PauseActivityItem; 22
+Landroid/content/pm/PermissionGroupInfo; 22
+Landroid/app/job/JobInfo; 22
+Lcom/android/server/criticalevents/nano/CriticalEventLogProto; 22
+Lcom/android/internal/infra/ServiceConnector$Impl$CompletionAwareJob; 22
+Landroid/net/metrics/NetworkEvent; 22
+Landroid/hardware/camera2/CameraManager$CameraManagerGlobal; 22
+Landroid/debug/AdbManagerInternal; 22
+Landroid/app/ActivityManagerInternal; 22
+Landroid/hardware/soundtrigger/SoundTrigger$StatusListener; 22
+Landroid/hardware/display/DisplayManagerInternal; 22
+Landroid/hardware/location/IActivityRecognitionHardwareClient; 22
+Landroid/hardware/sidekick/SidekickInternal; 22
+Landroid/media/AudioSystem; 22
+Landroid/net/Uri$PathPart;.NULL:Landroid/net/Uri$PathPart; 22
+Lcom/android/internal/os/LongArrayMultiStateCounter; 22
+Landroid/hardware/location/NanoAppMessage; 22
+Lcom/android/internal/widget/LockSettingsInternal; 22
+Landroid/accounts/AuthenticatorException; 22
+Landroid/window/DisplayAreaAppearedInfo; 22
+Landroid/content/pm/RegisteredServicesCache$2; 22
+Landroid/permission/PermissionManagerInternal; 22
+Landroid/content/pm/CrossProfileAppsInternal; 22
+Landroid/app/assist/ActivityId; 22
+Landroid/os/Process;.ZYGOTE_PROCESS:Landroid/os/ZygoteProcess; 22
+Landroid/appwidget/AppWidgetManagerInternal; 22
+Landroid/net/metrics/ValidationProbeEvent; 22
+Landroid/webkit/WebViewLibraryLoader$RelroFileCreator; 22
+Landroid/graphics/GraphicsStatsService; 22
+Lcom/android/internal/os/LongArrayMultiStateCounter$LongArrayContainer; 22
+Landroid/content/pm/PackageManager$Property; 22
+Landroid/app/servertransaction/DestroyActivityItem; 22
+Landroid/os/PowerManagerInternal; 22
+Landroid/app/SystemServiceRegistry; 22
+Landroid/hardware/location/GeofenceHardwareImpl; 22
+Landroid/app/AppOpsManager$NoteOpEvent; 22
+Lcom/android/framework/protobuf/nano/MessageNano; 22
+Landroid/content/pm/RegisteredServicesCache$3; 22
+Landroid/content/pm/PackageManager;.sCacheAutoCorker:Landroid/app/PropertyInvalidatedCache$AutoCorker; 22
+Landroid/accounts/AccountManagerInternal; 22
+Landroid/app/PropertyInvalidatedCache;.sCorkedInvalidates:Ljava/util/HashMap; 22
+Landroid/media/audiopolicy/AudioVolumeGroup; 22
+Landroid/app/admin/DevicePolicyManagerInternal$OnCrossProfileWidgetProvidersChangeListener; 22
+Landroid/content/pm/dex/ArtManagerInternal; 22
+Landroid/app/UriGrantsManager;.IUriGrantsManagerSingleton:Landroid/util/Singleton; 22
+Lcom/android/internal/os/RuntimeInit$ApplicationWtfHandler; 22
+Lcom/android/internal/app/procstats/AssociationState;.sTmpSourceKey:Lcom/android/internal/app/procstats/AssociationState$SourceKey; 22
+Landroid/app/servertransaction/StopActivityItem; 22
+Landroid/hardware/soundtrigger/SoundTriggerModule$EventHandlerDelegate; 22
+Landroid/app/PendingIntent$FinishedDispatcher; 22
+Landroid/app/servertransaction/NewIntentItem; 22
+Landroid/content/pm/UserPackage; 22
+Landroid/net/metrics/DhcpErrorEvent; 22
+Lcom/android/internal/listeners/ListenerExecutor$FailureCallback; 22
+Landroid/app/time/TimeZoneConfiguration; 22
+Landroid/net/metrics/IpManagerEvent; 22
+Landroid/view/Display$HdrCapabilities; 22
+Landroid/media/AudioSystem$AudioRecordingCallback; 22
+Landroid/telecom/PhoneAccountHandle; 22
+Lcom/android/server/criticalevents/nano/CriticalEventProto$InstallPackages; 22
+Landroid/os/BatteryManagerInternal; 22
+Landroid/provider/Settings; 22
+Lcom/android/internal/util/function/LongObjPredicate; 22
+Landroid/view/WindowManagerPolicyConstants; 22
+Landroid/accessibilityservice/AccessibilityServiceInfo; 22
+Lcom/android/internal/os/CachedDeviceState$Readonly; 22
+Landroid/service/voice/VoiceInteractionManagerInternal; 22
+Landroid/service/notification/Condition; 22
+Landroid/util/EventLog; 22
+Lcom/android/server/AppWidgetBackupBridge; 22
+Landroid/net/wifi/nl80211/WifiNl80211Manager$ScanEventCallback; 22
+Lcom/android/internal/os/StatsdHiddenApiUsageLogger;.sInstance:Lcom/android/internal/os/StatsdHiddenApiUsageLogger; 22
+Landroid/app/AppOpsManager$AttributedOpEntry; 22
+Landroid/attention/AttentionManagerInternal; 22
+Landroid/os/PatternMatcher;.sParsedPatternScratch:[I 22
+Landroid/view/DisplayAddress$Physical; 22
+Lcom/android/internal/util/function/DodecConsumer; 22
+Landroid/app/servertransaction/StartActivityItem; 22
+Landroid/window/IOnBackInvokedCallback$Stub$Proxy; 22
+Landroid/content/ComponentName$WithComponentName; 22
+Landroid/media/AudioPlaybackConfiguration$PlayerDeathMonitor; 22
+Landroid/telecom/Log; 22
+Landroid/view/ViewDebug$ExportedProperty; 22
+Landroid/graphics/Region;.sPool:Landroid/util/Pools$SynchronizedPool; 22
+Lcom/android/internal/os/LongMultiStateCounter; 22
+Landroid/content/pm/parsing/ApkLite; 22
+Landroid/app/PropertyInvalidatedCache;.sCorks:Ljava/util/HashMap; 22
+Landroid/content/pm/PackageInstaller$SessionInfo; 22
+Landroid/os/RemoteCallback$1; 22
+Landroid/os/UEventObserver; 22
+Landroid/app/AppOpsManagerInternal; 22
+Lcom/android/internal/infra/AbstractRemoteService$VultureCallback; 22
+Landroid/media/AudioSystem$ErrorCallback; 22
+Landroid/window/TaskAppearedInfo; 22
+Landroid/content/pm/ShortcutServiceInternal$ShortcutChangeListener; 22
+Landroid/service/notification/NotificationListenerService$RankingMap; 22
+Landroid/app/PropertyInvalidatedCache$AutoCorker$1; 22
+Landroid/view/autofill/AutofillManagerInternal; 22
+Lcom/android/internal/content/om/OverlayConfig$PackageProvider; 22
+Lcom/android/internal/os/BatteryStatsHistory$HistoryStepDetailsCalculator; 22
+Lcom/android/internal/os/KernelCpuBpfTracking; 22
+Landroid/os/ServiceSpecificException; 22
+Landroid/content/pm/RegisteredServicesCache$1; 22
+Landroid/app/time/TimeZoneCapabilities; 22
+Landroid/content/res/ResourceTimer; 22
+Landroid/app/servertransaction/ConfigurationChangeItem; 22
+Lcom/android/internal/infra/AndroidFuture$1; 22
+Landroid/content/pm/PackageManager;.sCacheAutoCorker:Landroid/app/PropertyInvalidatedCache$AutoCorker;.mLock:Ljava/lang/Object; 22
+Lcom/android/internal/content/om/OverlayConfig; 22
+Landroid/app/ActivityManager; 22
+Landroid/net/metrics/ApfProgramEvent; 22
+Landroid/graphics/Bitmap$CompressFormat; 22
+Landroid/window/ITaskOrganizer$Stub$Proxy; 22
+Landroid/app/servertransaction/ActivityResultItem; 22
+Landroid/service/notification/ConditionProviderService; 22
+Landroid/hardware/display/DeviceProductInfo; 22
+Landroid/hardware/display/DeviceProductInfo$ManufactureDate; 22
+Landroid/app/ApplicationExitInfo; 22
+Landroid/app/admin/DevicePolicyManagerInternal; 22
+Lcom/android/server/usage/AppStandbyInternal; 22
+Landroid/app/PropertyInvalidatedCache;.sCorkLock:Ljava/lang/Object; 22
+Lcom/android/internal/display/BrightnessSynchronizer; 22
+Landroid/service/notification/StatusBarNotification; 22
+Landroid/os/FileUtils$ProgressListener; 22
+Landroid/media/AudioAttributes; 22
+Landroid/service/autofill/FillContext; 22
+Landroid/hardware/biometrics/BiometricSourceType; 22
+Landroid/hardware/location/GeofenceHardwareService; 22
+Landroid/app/servertransaction/ActivityConfigurationChangeItem; 22
+Landroid/content/pm/LauncherActivityInfoInternal; 22
+Landroid/telecom/Logging/SessionManager$ISessionListener; 22
+Landroid/text/format/TimeFormatter; 22
+Landroid/hardware/location/ContextHubInfo; 22
+Landroid/os/ServiceManager$ServiceNotFoundException; 22
+Landroid/hardware/biometrics/ComponentInfoInternal; 22
+Lcom/android/internal/os/LongArrayMultiStateCounter;.sTmpArrayContainer:Ljava/util/concurrent/atomic/AtomicReference; 22
+Lcom/android/internal/config/appcloning/AppCloningDeviceConfigHelper; 22
+Landroid/os/storage/StorageVolume; 22
+Landroid/app/AppOpsManager$SamplingStrategy; 22
+Lcom/android/server/LocalServices;.sLocalServiceObjects:Landroid/util/ArrayMap; 22
+Landroid/media/AudioPlaybackConfiguration; 22
+Landroid/view/ViewDebug$FlagToString; 22
+Landroid/service/dreams/DreamManagerInternal; 22
+Lcom/android/server/criticalevents/nano/CriticalEventProto$NativeCrash; 22
+Landroid/net/wifi/nl80211/WifiNl80211Manager$ScanEventHandler; 22
+Landroid/util/ArrayMap;.sBaseCacheLock:Ljava/lang/Object; 22
+Landroid/content/pm/FallbackCategoryProvider;.sFallbacks:Landroid/util/ArrayMap; 22
+Landroid/os/SharedMemory; 22
+Landroid/media/AudioManagerInternal$RingerModeDelegate; 22
+Landroid/util/NtpTrustedTime; 22
+Landroid/appwidget/AppWidgetProviderInfo; 22
+Landroid/telephony/ServiceState; 22
+Landroid/service/notification/ZenPolicy; 22
+Lcom/android/internal/statusbar/NotificationVisibility$NotificationLocation; 22
+Landroid/content/pm/UserPackage;.sCacheLock:Ljava/lang/Object; 22
+Lcom/android/server/WidgetBackupProvider; 22
+Lcom/android/internal/os/LooperStats; 22
+Lcom/android/internal/infra/AbstractRemoteService$AsyncRequest; 22
+Landroid/content/pm/BaseParceledListSlice$1; 22
+Landroid/media/AudioManagerInternal; 22
+Landroid/content/pm/UserPackage;.sCache:Landroid/util/SparseArrayMap;.mData:Landroid/util/SparseArray; 22
+Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.5:Ljava/util/WeakHashMap$Entry;.referent:Ljava/lang/Object;.mLock:Ljava/lang/Object; 22
+Landroid/content/pm/RegisteredServicesCacheListener; 22
+Landroid/view/WindowManagerPolicyConstants$PointerEventListener; 22
+Landroid/accounts/AccountManagerInternal$OnAppPermissionChangeListener; 22
+Landroid/database/ContentObserver$$ExternalSyntheticLambda1; 22
+Landroid/webkit/WebViewZygote; 22
+Landroid/content/pm/LauncherApps$ShortcutQuery$QueryFlags; 22
+Lcom/android/server/criticalevents/nano/CriticalEventProto$SystemServerStarted; 22
+Landroid/os/storage/StorageManagerInternal; 22
+Landroid/service/notification/ZenModeConfig; 22
+Landroid/icu/text/MessagePattern;.argTypes:[Landroid/icu/text/MessagePattern$ArgType;.0:Landroid/icu/text/MessagePattern$ArgType;.name:Ljava/lang/String; 27
+Lcom/android/internal/app/procstats/DumpUtils;.STATE_NAMES_CSV:[Ljava/lang/String;.12:Ljava/lang/String; 27
+Landroid/media/MediaDrm; 27
+Landroid/icu/util/GenderInfo$Gender;.OTHER:Landroid/icu/util/GenderInfo$Gender;.name:Ljava/lang/String; 27
+Landroid/provider/DocumentsContract;.DOWNLOADS_PROVIDER_AUTHORITY:Ljava/lang/String; 27
+Landroid/annotation/SystemApi; 27
+Landroid/icu/util/MeasureUnit$Complexity;.MIXED:Landroid/icu/util/MeasureUnit$Complexity;.name:Ljava/lang/String; 27
+Landroid/webkit/WebViewFactory;.sTimestamps:Landroid/webkit/WebViewFactory$StartupTimestamps; 55
+Landroid/webkit/WebViewFactoryProvider; 56
+Landroid/webkit/WebViewFactory; 57
+Landroid/webkit/WebViewProvider$ScrollDelegate; 58
+Landroid/webkit/WebViewProvider; 58
+Landroid/webkit/WebViewProvider$ViewDelegate; 58
+Landroid/webkit/CookieSyncManager; 59
+Landroid/view/PointerIcon;.SYSTEM_ICONS:Landroid/util/SparseArray; 60
+Landroid/webkit/WebView; 61
+Landroid/webkit/WebResourceRequest; 62
+Landroid/webkit/DownloadListener; 63
+Landroid/webkit/WebViewFactoryProvider$Statics; 64
+Landroid/window/WindowTokenClientController; 65
+Landroid/os/PowerManager$OnThermalStatusChangedListener; 66
+Landroid/content/ClipboardManager$OnPrimaryClipChangedListener; 67
+Landroid/hardware/input/InputManager$InputDeviceListener; 68
+Landroid/hardware/input/InputManagerGlobal; 69
+Landroid/media/MediaCodecInfo$CodecCapabilities$FeatureList; 70
+Landroid/media/MediaCodecList; 70
+Landroid/media/MediaCodec; 71
+Landroid/os/ParcelFileDescriptor; 72
+Landroid/webkit/ValueCallback; 73
+Landroid/view/View$OnDragListener; 74
+Landroid/view/autofill/Helper; 75
+Landroid/app/ActivityTaskManager;.IActivityTaskManagerSingleton:Landroid/util/Singleton; 78
+Lcom/android/internal/inputmethod/ImeTracing; 79
+Landroid/view/SurfaceControlRegistry; 79
+Landroid/view/inputmethod/IInputMethodManagerGlobalInvoker; 80
+Landroid/window/BackTouchTracker; 80
+Landroid/view/Choreographer; 81
+Lcom/android/internal/policy/DecorView; 82
+Landroid/window/SurfaceSyncGroup; 82
+Landroid/view/ViewTreeObserver; 82
+Landroid/view/accessibility/AccessibilityNodeIdManager; 82
+Landroid/view/ViewRootImpl; 82
+Landroid/widget/FrameLayout; 82
+Landroid/view/ViewStub; 82
+Landroid/os/SystemProperties;.sChangeCallbacks:Ljava/util/ArrayList; 83
+Lcom/android/internal/os/SomeArgs; 84
+Landroid/view/autofill/AutofillId; 85
+Landroid/app/ActivityClient;.sInstance:Landroid/util/Singleton; 86
+Landroid/app/ActivityClient;.INTERFACE_SINGLETON:Landroid/app/ActivityClient$ActivityClientControllerSingleton; 86
+Landroid/os/StrictMode$InstanceTracker;.sInstanceCounts:Ljava/util/HashMap; 87
+Landroid/widget/LinearLayout; 88
+Landroid/view/ViewConfiguration;.sConfigurations:Landroid/util/SparseArray; 89
+Landroid/view/ViewConfiguration;.sConfigurations:Landroid/util/SparseArray;.mKeys:[I 89
+Landroid/view/ViewConfiguration;.sConfigurations:Landroid/util/SparseArray;.mValues:[Ljava/lang/Object; 89
+Landroid/view/accessibility/AccessibilityManager; 90
+Landroid/graphics/Bitmap; 91
+Landroid/content/SharedPreferences; 113
+Landroid/app/AppOpsManager; 115
+Landroid/os/Parcel;.mCreators:Ljava/util/HashMap; 117
+Landroid/os/GraphicsEnvironment;.sInstance:Landroid/os/GraphicsEnvironment; 117
+Landroid/os/Process; 117
+Landroid/os/Parcel;.sPairedCreators:Ljava/util/HashMap; 117
+Landroid/os/Parcel; 117
+Landroid/app/ApplicationLoaders;.gApplicationLoaders:Landroid/app/ApplicationLoaders;.mLoaders:Landroid/util/ArrayMap; 117
+Landroid/graphics/Typeface; 118
+Landroid/provider/DeviceConfigInitializer; 118
+Landroid/icu/util/TimeZone; 118
+Landroid/view/View; 118
+Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap; 118
+Landroid/app/LoadedApk;.sApplications:Landroid/util/ArrayMap; 118
+Landroid/app/ActivityThread; 118
+Landroid/util/ArraySet; 118
+Landroid/hardware/display/DisplayManagerGlobal; 118
+Landroid/os/LocaleList; 118
+Landroid/telephony/TelephonyFrameworkInitializer; 118
+Landroid/app/LoadedApk;.sApplications:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object; 118
+Landroid/os/Binder; 118
+Landroid/os/DdmSyncState; 118
+Landroid/media/MediaFrameworkPlatformInitializer; 118
+Lcom/android/internal/os/RuntimeInit; 118
+Landroid/content/res/Resources;.sResourcesHistory:Ljava/util/Set;.c:Ljava/util/Collection;.m:Ljava/util/Map;.table:[Ljava/util/WeakHashMap$Entry; 118
+Landroid/os/Message; 118
+Landroid/security/net/config/SystemCertificateSource$NoPreloadHolder; 118
+Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.5:Ljava/util/WeakHashMap$Entry;.referent:Ljava/lang/Object; 118
+Landroid/app/LoadedApk;.sApplications:Landroid/util/ArrayMap;.mHashes:[I 118
+Landroid/ddm/DdmHandleAppName; 118
+Landroid/content/res/Resources;.sResourcesHistory:Ljava/util/Set;.c:Ljava/util/Collection;.m:Ljava/util/Map; 118
+Landroid/os/ServiceManager; 118
+Landroid/os/Looper; 118
+Landroid/se/omapi/SeFrameworkInitializer; 118
+Landroid/os/StrictMode; 118
+Landroid/os/Environment; 118
+Landroid/security/net/config/ApplicationConfig; 118
+Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.12:Ljava/util/WeakHashMap$Entry;.referent:Ljava/lang/Object; 119
+Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.5:Ljava/util/WeakHashMap$Entry;.referent:Ljava/lang/Object;.mCache:Ljava/util/LinkedHashMap; 120
+Landroid/app/servertransaction/ClientTransactionListenerController; 121
+Lcom/android/internal/os/BinderInternal; 122
+Landroid/graphics/Compatibility; 123
+Landroid/renderscript/RenderScriptCacheDir; 123
+Landroid/app/DexLoadReporter;.INSTANCE:Landroid/app/DexLoadReporter;.mDataDirs:Ljava/util/Set;.map:Ljava/util/HashMap; 123
+Landroid/graphics/Canvas; 123
+Landroid/provider/FontsContract; 123
+Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.1:Ljava/util/WeakHashMap$Entry;.referent:Ljava/lang/Object; 126
+Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.1:Ljava/util/WeakHashMap$Entry;.referent:Ljava/lang/Object;.mCache:Ljava/util/LinkedHashMap; 127
+Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.12:Ljava/util/WeakHashMap$Entry;.referent:Ljava/lang/Object;.mCache:Ljava/util/LinkedHashMap; 128
+Landroid/graphics/HardwareRenderer; 129
+Landroid/app/QueuedWork; 130
+Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.12:Ljava/util/WeakHashMap$Entry; 131
+Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.10:Ljava/util/WeakHashMap$Entry; 132
+Landroid/content/Context; 133
+Landroid/view/inputmethod/InputMethodManager; 134
+Landroid/view/WindowManagerGlobal; 135
+Landroid/telephony/TelephonyManager; 136
+Landroid/database/CursorWindow; 137
+Landroid/database/sqlite/SQLiteGlobal; 138
+Landroid/database/sqlite/SQLiteDebug$NoPreloadHolder; 138
+Landroid/database/sqlite/SQLiteCompatibilityWalFlags; 138
+Landroid/content/ContentResolver; 139
+Landroid/app/NotificationManager; 140
+Landroid/os/Handler; 141
+Landroid/icu/impl/locale/BaseLocale;.CACHE:Landroid/icu/impl/locale/BaseLocale$Cache;._map:Ljava/util/concurrent/ConcurrentHashMap; 142
+Landroid/graphics/Bitmap;.sAllBitmaps:Ljava/util/WeakHashMap; 143
+Landroid/text/TextUtils; 144
+Landroid/graphics/drawable/ColorDrawable; 145
+Landroid/transition/ChangeImageTransform; 146
+Landroid/transition/ChangeTransform; 146
+Landroid/transition/ChangeClipBounds; 146
+Landroid/text/TextLine;.sCached:[Landroid/text/TextLine; 147
+Landroid/graphics/TemporaryBuffer; 148
+Landroid/text/Layout;.sTempRect:Landroid/graphics/Rect; 149
+Landroid/content/res/ColorStateList;.sCache:Landroid/util/SparseArray; 150
+Landroid/widget/ImageView; 151
+Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.11:Ljava/util/WeakHashMap$Entry;.referent:Ljava/lang/Object; 163
+Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.11:Ljava/util/WeakHashMap$Entry;.referent:Ljava/lang/Object;.mCache:Ljava/util/LinkedHashMap; 164
+Landroid/content/pm/VersionedPackage; 171
+Landroid/content/SharedPreferences$OnSharedPreferenceChangeListener; 172
+Landroid/database/sqlite/SQLiteDatabase$CursorFactory; 173
+Landroid/view/textclassifier/TextLanguage;.EMPTY:Landroid/view/textclassifier/TextLanguage;.mBundle:Landroid/os/Bundle; 174
+Landroid/content/ComponentName; 175
+Landroid/icu/impl/ZoneMeta;.SYSTEM_ZONE_CACHE:Landroid/icu/impl/ZoneMeta$SystemTimeZoneCache;.map:Ljava/util/concurrent/ConcurrentHashMap; 180
+Landroid/icu/impl/ZoneMeta;.CANONICAL_ID_CACHE:Landroid/icu/impl/ICUCache; 181
+Landroid/icu/impl/ICUResourceBundleReader;.CACHE:Landroid/icu/impl/ICUResourceBundleReader$ReaderCache;.map:Ljava/util/concurrent/ConcurrentHashMap; 182
+Landroid/app/UiModeManager; 183
+Landroid/media/AudioManager; 184
+Landroid/util/ArrayMap; 185
+Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.1:Ljava/util/WeakHashMap$Entry;.referent:Ljava/lang/Object;.mSkips:[J 188
+Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.12:Ljava/util/WeakHashMap$Entry;.referent:Ljava/lang/Object;.mSkips:[J 189
+Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.5:Ljava/util/WeakHashMap$Entry;.referent:Ljava/lang/Object;.mSkips:[J 190
+Landroid/icu/text/DecimalFormatSymbols;.cachedLocaleData:Landroid/icu/impl/CacheBase;.map:Ljava/util/concurrent/ConcurrentHashMap; 205
+Landroid/icu/impl/CurrencyData;.provider:Landroid/icu/impl/CurrencyData$CurrencyDisplayInfoProvider; 206
+Landroid/icu/text/DateFormatSymbols;.DFSCACHE:Landroid/icu/impl/CacheBase;.map:Ljava/util/concurrent/ConcurrentHashMap; 211
+Landroid/animation/AnimatorInflater;.sTmpTypedValue:Landroid/util/TypedValue; 217
+Landroid/graphics/drawable/RippleDrawable; 218
+Landroid/text/method/SingleLineTransformationMethod; 219
+Landroid/text/style/SuggestionSpan; 220
+Landroid/text/SpannableStringBuilder;.sCachedIntBuffer:[[I 221
+Landroid/widget/TextView$ChangeWatcher; 222
+Landroid/text/DynamicLayout$Builder;.sPool:Landroid/util/Pools$SynchronizedPool;.mPool:[Ljava/lang/Object; 223
+Landroid/text/DynamicLayout$Builder;.sPool:Landroid/util/Pools$SynchronizedPool; 223
+Landroid/text/DynamicLayout; 223
+Landroid/text/DynamicLayout$ChangeWatcher; 224
+Landroid/text/style/WrapTogetherSpan; 225
+Landroid/text/Selection$MemoryTextWatcher; 226
+Landroid/text/Selection;.SELECTION_MEMORY:Ljava/lang/Object; 227
+Landroid/text/Selection;.SELECTION_END:Ljava/lang/Object; 227
+Landroid/text/Selection;.SELECTION_START:Ljava/lang/Object; 227
+Landroid/text/method/TextKeyListener;.sInstance:[Landroid/text/method/TextKeyListener; 228
+Landroid/text/method/ArrowKeyMovementMethod; 228
+Landroid/view/textclassifier/TextClassificationConstants; 229
+Landroid/text/style/SpellCheckSpan; 230
+Landroid/text/TextUtils$TruncateAt;.MARQUEE:Landroid/text/TextUtils$TruncateAt; 231
+Landroid/text/style/ReplacementSpan; 232
+Landroid/text/style/LineBackgroundSpan; 233
+Landroid/text/style/LeadingMarginSpan; 234
+Landroid/text/style/TabStopSpan; 235
+Landroid/text/style/LineBreakConfigSpan; 236
+Landroid/text/style/MetricAffectingSpan; 237
+Landroid/text/style/CharacterStyle; 238
+Landroid/text/style/AlignmentSpan; 239
+Landroid/text/SpanWatcher; 240
+Lcom/android/internal/util/ArrayUtils;.sCache:[Ljava/lang/Object; 241
+Landroid/text/TextWatcher; 242
+Landroid/text/style/ClickableSpan; 243
+Landroid/text/style/LineHeightSpan; 244
+Landroid/text/method/LinkMovementMethod; 245
+Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.queue:Ljava/lang/ref/ReferenceQueue; 246
+Landroid/view/ViewRootImpl$$ExternalSyntheticLambda11; 247
+Landroid/content/res/Resources;.sResourcesHistory:Ljava/util/Set;.c:Ljava/util/Collection;.m:Ljava/util/Map;.queue:Ljava/lang/ref/ReferenceQueue; 249
+Landroid/app/NotificationChannel; 251
+Landroid/os/HandlerThread; 252
+Lcom/android/internal/os/ZygoteInit; 252
+Landroid/database/DatabaseUtils; 252
+Landroid/annotation/CurrentTimeMillisLong; 253
+Landroid/os/AsyncTask; 254
+Landroid/text/StaticLayout$Builder;.sPool:Landroid/util/Pools$SynchronizedPool;.mPool:[Ljava/lang/Object; 255
+Landroid/text/StaticLayout$Builder;.sPool:Landroid/util/Pools$SynchronizedPool; 255
+Landroid/graphics/drawable/GradientDrawable; 256
+Landroid/graphics/drawable/StateListDrawable; 257
+Landroid/widget/RelativeLayout; 258
+Landroid/graphics/drawable/BitmapDrawable; 259
+Landroid/graphics/drawable/Drawable; 262
+Landroid/view/TextureView$SurfaceTextureListener; 265
+Landroid/graphics/SurfaceTexture; 266
+Landroid/media/audiopolicy/AudioProductStrategy; 267
+Landroid/media/PlayerBase; 268
+Landroid/os/FileUtils; 269
+Landroid/app/AppOpsManager$OnOpActiveChangedListener; 270
+Landroid/telephony/ims/ImsService;.CAPABILITIES_LOG_MAP:Ljava/util/Map;.table:[Ljava/lang/Object;.2:Ljava/lang/Long; 271
+Landroid/icu/impl/ValidIdentifiers$Datasubtype;.unknown:Landroid/icu/impl/ValidIdentifiers$Datasubtype;.name:Ljava/lang/String; 271
+Lcom/android/ims/rcs/uce/UceDeviceState;.DEVICE_STATE_DESCRIPTION:Ljava/util/Map;.table:[Ljava/util/HashMap$Node;.4:Ljava/util/HashMap$Node;.key:Ljava/lang/Object; 271
+Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.569:Ljava/lang/Long; 272
+Lcom/android/ims/rcs/uce/UceDeviceState;.DEVICE_STATE_DESCRIPTION:Ljava/util/Map;.table:[Ljava/util/HashMap$Node;.3:Ljava/util/HashMap$Node;.key:Ljava/lang/Object; 273
+Landroid/icu/text/MeasureFormat;.hmsTo012:Ljava/util/Map;.table:[Ljava/util/HashMap$Node;.0:Ljava/util/HashMap$Node;.value:Ljava/lang/Object; 274
+Landroid/icu/text/MeasureFormat;.hmsTo012:Ljava/util/Map;.table:[Ljava/util/HashMap$Node;.1:Ljava/util/HashMap$Node;.value:Ljava/lang/Object; 275
+Landroid/icu/text/MeasureFormat;.hmsTo012:Ljava/util/Map;.table:[Ljava/util/HashMap$Node;.6:Ljava/util/HashMap$Node;.value:Ljava/lang/Object; 276
+Landroid/view/textclassifier/TextLanguage;.EMPTY:Landroid/view/textclassifier/TextLanguage;.mBundle:Landroid/os/Bundle;.mMap:Landroid/util/ArrayMap; 277
+Landroid/os/RemoteException; 278
+Landroid/content/pm/PackageManager$OnChecksumsReadyListener; 279
+Landroid/content/pm/Checksum$Type; 280
+Landroid/view/InputEvent;.mNextSeq:Ljava/util/concurrent/atomic/AtomicInteger; 281
+Landroid/view/MotionEvent; 282
+Landroid/animation/PropertyValuesHolder$FloatPropertyValuesHolder;.sJNISetterPropertyMap:Ljava/util/HashMap; 283
+Landroid/animation/PropertyValuesHolder;.sGetterPropertyMap:Ljava/util/HashMap; 284
+Landroid/graphics/drawable/LayerDrawable; 285
+Landroid/graphics/Bitmap;.sAllBitmaps:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry; 289
+Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.11:Ljava/lang/Boolean; 292
+Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.1:Ljava/lang/Boolean; 293
+Landroid/util/TypedValue; 305
+Landroid/os/MessageQueue; 306
+Landroid/view/KeyEvent; 307
+Landroid/app/job/JobServiceEngine$JobHandler; 308
+Landroid/os/AsyncTask$InternalHandler; 309
+Landroid/app/IActivityTaskManager; 310
+Landroid/view/View$$ExternalSyntheticLambda4; 310
+Landroid/view/WindowInsets; 311
+Landroid/app/ActivityTaskManager$2; 311
+Landroid/util/Singleton; 311
+Landroid/view/View$AttachInfo; 311
+Landroid/icu/text/DecimalFormatSymbols;.DEF_DIGIT_STRINGS_ARRAY:[Ljava/lang/String;.3:Ljava/lang/String; 311
+Lcom/android/icu/util/regex/PatternNative; 311
+Landroid/icu/text/DecimalFormatSymbols;.DEF_DIGIT_STRINGS_ARRAY:[Ljava/lang/String;.2:Ljava/lang/String; 311
+Landroid/app/Dialog$ListenersHandler; 311
+Landroid/webkit/WebViewDelegate; 311
+Landroid/hardware/display/IDisplayManager; 311
+Landroid/app/servertransaction/PendingTransactionActions$StopInfo; 312
+Landroid/view/animation/Animation$3; 312
+Landroid/content/pm/IPackageManager; 313
+Landroid/view/ViewRootImpl$8; 314
+Landroid/view/ViewRootImpl$ViewRootHandler; 315
+Landroid/app/LoadedApk$ReceiverDispatcher$Args$$ExternalSyntheticLambda0; 316
+Landroid/app/LoadedApk$ServiceDispatcher$RunConnection; 317
+Landroid/view/Choreographer$FrameDisplayEventReceiver; 318
+Landroid/view/inputmethod/InputMethodManager$H; 318
+Landroid/graphics/HardwareRendererObserver$$ExternalSyntheticLambda0; 318
+Landroid/view/Choreographer$FrameHandler; 319
+Landroid/app/ActivityThread$H; 320
+Landroid/os/Parcel$ReadWriteHelper; 327
+Landroid/util/SparseArray; 328
+Landroid/app/Instrumentation; 329
+Landroid/app/IActivityManager$Stub$Proxy; 330
+Landroid/content/pm/Attribution; 331
+[Landroid/content/pm/PermissionInfo; 331
+[Landroid/os/PatternMatcher; 331
+Landroid/content/pm/ActivityInfo$WindowLayout; 331
+Landroid/content/pm/ServiceInfo; 331
+[Landroid/content/pm/PathPermission; 331
+Landroid/content/pm/SharedLibraryInfo; 331
+Landroid/content/pm/SigningInfo; 331
+Landroid/content/pm/PathPermission; 331
+Landroid/hardware/camera2/CameraCharacteristics;.LENS_INFO_AVAILABLE_APERTURES:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 331
+[Landroid/content/pm/ServiceInfo; 331
+[Landroid/content/pm/ProviderInfo; 331
+[Landroid/content/pm/FeatureInfo; 331
+[Landroid/content/pm/Attribution; 331
+Landroid/content/pm/ActivityInfo; 331
+Landroid/os/PatternMatcher; 331
+Landroid/content/pm/ConfigurationInfo; 331
+[Landroid/content/pm/FeatureGroupInfo; 331
+[Landroid/content/pm/InstrumentationInfo; 331
+[Landroid/content/pm/ActivityInfo; 331
+[Landroid/content/pm/ConfigurationInfo; 331
+Landroid/content/pm/InstrumentationInfo; 331
+[Landroid/content/pm/Signature; 331
+Landroid/content/pm/FeatureGroupInfo; 331
+Landroid/view/ViewManager; 332
+Landroid/view/KeyEvent$Callback; 332
+Landroid/view/ViewParent; 332
+Landroid/view/accessibility/AccessibilityEventSource; 332
+Landroid/graphics/drawable/Drawable$Callback; 332
+Landroid/content/pm/SigningDetails; 334
+Landroid/content/pm/ProviderInfo; 334
+Landroid/content/pm/PermissionInfo; 334
+Landroid/content/pm/FeatureInfo; 334
+Landroid/util/Pair; 335
+Landroid/hardware/camera2/CameraCharacteristics;.LENS_INFO_AVAILABLE_FOCAL_LENGTHS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 336
+primitive F 336
+Landroid/hardware/camera2/CameraCharacteristics;.DEPTH_AVAILABLE_DYNAMIC_DEPTH_STREAM_CONFIGURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 337
+Landroid/hardware/camera2/CameraCharacteristics;.SCALER_AVAILABLE_STALL_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 337
+Landroid/hardware/camera2/CameraCharacteristics;.JPEGR_AVAILABLE_JPEG_R_MIN_FRAME_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 337
+Landroid/hardware/camera2/CameraCharacteristics;.SCALER_AVAILABLE_MIN_FRAME_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 337
+Landroid/hardware/camera2/CameraCharacteristics;.DEPTH_AVAILABLE_DEPTH_STALL_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 337
+Landroid/hardware/camera2/CameraCharacteristics;.CONTROL_AVAILABLE_HIGH_SPEED_VIDEO_CONFIGURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 337
+Landroid/hardware/camera2/CameraCharacteristics;.SCALER_AVAILABLE_STREAM_CONFIGURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 337
+Landroid/hardware/camera2/CameraCharacteristics;.JPEGR_AVAILABLE_JPEG_R_STALL_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 337
+Landroid/hardware/camera2/CameraCharacteristics;.HEIC_AVAILABLE_HEIC_STREAM_CONFIGURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 337
+Landroid/hardware/camera2/CameraCharacteristics;.REQUEST_AVAILABLE_CAPABILITIES:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 337
+Landroid/hardware/camera2/CameraCharacteristics;.JPEGR_AVAILABLE_JPEG_R_STREAM_CONFIGURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 337
+Landroid/hardware/camera2/CameraCharacteristics;.DEPTH_AVAILABLE_DYNAMIC_DEPTH_MIN_FRAME_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 337
+Landroid/hardware/camera2/params/StreamConfiguration; 337
+Landroid/hardware/camera2/params/StreamConfigurationDuration; 337
+Landroid/hardware/camera2/CameraCharacteristics;.SCALER_AVAILABLE_INPUT_OUTPUT_FORMATS_MAP:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 337
+Landroid/hardware/camera2/CameraCharacteristics;.HEIC_AVAILABLE_HEIC_MIN_FRAME_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 337
+Landroid/hardware/camera2/CameraCharacteristics;.HEIC_AVAILABLE_HEIC_STALL_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 337
+Landroid/hardware/camera2/CameraCharacteristics;.DEPTH_AVAILABLE_DEPTH_MIN_FRAME_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 337
+Landroid/hardware/camera2/CameraCharacteristics;.DEPTH_AVAILABLE_DYNAMIC_DEPTH_STALL_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 337
+Landroid/hardware/camera2/params/HighSpeedVideoConfiguration; 337
+Landroid/hardware/camera2/CameraCharacteristics;.DEPTH_AVAILABLE_DEPTH_STREAM_CONFIGURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 337
+primitive I 337
+Landroid/hardware/camera2/CameraCharacteristics;.SCALER_MULTI_RESOLUTION_STREAM_SUPPORTED:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 338
+Landroid/hardware/camera2/marshal/MarshalRegistry;.sMarshalerMap:Ljava/util/HashMap; 338
+Landroid/hardware/camera2/CameraCharacteristics;.LENS_FACING:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 339
+Landroid/graphics/drawable/NinePatchDrawable; 341
+Landroid/widget/HorizontalScrollView; 342
+Lcom/android/internal/os/PowerProfile;.sPowerItemMap:Ljava/util/HashMap; 343
+Lcom/android/internal/os/PowerProfile;.sModemPowerProfile:Lcom/android/internal/power/ModemPowerProfile;.mPowerConstants:Landroid/util/SparseDoubleArray;.mValues:Landroid/util/SparseLongArray; 343
+Lcom/android/internal/os/PowerProfile;.sPowerArrayMap:Ljava/util/HashMap; 343
+Landroid/content/pm/ComponentInfo; 346
+Landroid/content/pm/Signature; 346
+Landroid/content/pm/PackageItemInfo; 347
+Landroid/content/pm/PackageInfo; 348
+Landroid/os/Parcelable; 349
+Landroid/aconfig/nano/Aconfig$parsed_flag; 355
+Lcom/android/internal/util/Parcelling$BuiltIn$ForInternedString; 355
+Landroid/aconfig/nano/Aconfig$tracepoint; 355
+Lcom/android/internal/util/Parcelling$BuiltIn$ForInternedStringValueMap; 355
+Lcom/android/internal/util/Parcelling$BuiltIn$ForInternedStringSet; 355
+Lcom/android/internal/util/Parcelling$BuiltIn$ForInternedStringList; 355
+Lcom/android/internal/util/Parcelling$Cache;.sCache:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object; 355
+Lcom/android/internal/util/Parcelling$BuiltIn$ForInternedStringArray; 355
+Landroid/app/ActivityTaskManager; 355
+Lcom/android/internal/util/Parcelling$Cache;.sCache:Landroid/util/ArrayMap;.mHashes:[I 355
+Lcom/android/internal/util/Parcelling$Cache;.sCache:Landroid/util/ArrayMap; 355
+Lcom/android/ims/rcs/uce/presence/pidfparser/omapres/Version;.ELEMENT_NAME:Ljava/lang/String; 356
+primitive [[I 356
+Landroid/provider/SyncStateContract$Columns;.DATA:Ljava/lang/String; 356
+Landroid/view/Window$Callback; 356
+Landroid/os/BatteryConsumer;.sPowerComponentNames:[Ljava/lang/String;.13:Ljava/lang/String; 358
+Landroid/graphics/Color;.sColorNameMap:Ljava/util/HashMap;.table:[Ljava/util/HashMap$Node;.3:Ljava/util/HashMap$Node;.key:Ljava/lang/Object; 358
+Lcom/android/ims/rcs/uce/presence/pidfparser/pidf/Basic;.OPEN:Ljava/lang/String; 358
+Lcom/android/internal/os/BinderCallsStats$SettingsObserver;.SETTINGS_ENABLED_KEY:Ljava/lang/String; 358
+Landroid/os/IncidentManager;.URI_SCHEME:Ljava/lang/String; 358
+Landroid/text/Html$HtmlParser;.schema:Lorg/ccil/cowan/tagsoup/HTMLSchema;.theEntities:Ljava/util/HashMap;.table:[Ljava/util/HashMap$Node;.3233:Ljava/util/HashMap$Node;.key:Ljava/lang/Object; 358
+Landroid/icu/impl/units/UnitsData$Constants;.DEFAULT_USAGE:Ljava/lang/String; 359
+Lcom/android/internal/telephony/IccProvider;.ADDRESS_BOOK_COLUMN_NAMES:[Ljava/lang/String;.0:Ljava/lang/String; 359
+Landroid/app/NotificationChannel;.EDIT_LAUNCHER:Ljava/lang/String; 360
+Lcom/android/internal/os/RailStats;.WIFI_SUBSYSTEM:Ljava/lang/String; 360
+Lcom/android/ims/ImsUt;.KEY_ACTION:Ljava/lang/String; 360
+Landroid/view/textclassifier/TextClassifier;.TYPE_URL:Ljava/lang/String; 360
+Landroid/app/NotificationChannel;.TAG_CHANNEL:Ljava/lang/String; 360
+Landroid/icu/text/MessageFormat;.typeList:[Ljava/lang/String;.5:Ljava/lang/String; 360
+Landroid/icu/impl/ValidIdentifiers$Datatype;.region:Landroid/icu/impl/ValidIdentifiers$Datatype;.name:Ljava/lang/String; 360
+Landroid/provider/Telephony$BaseMmsColumns;.START:Ljava/lang/String; 360
+Lcom/android/ims/rcs/uce/presence/pidfparser/pidf/Timestamp;.ELEMENT_NAME:Ljava/lang/String; 360
+Lcom/android/ims/rcs/uce/presence/pidfparser/pidf/Status;.ELEMENT_NAME:Ljava/lang/String; 360
+Landroid/os/BatteryManager;.EXTRA_SEQUENCE:Ljava/lang/String; 360
+Landroid/icu/text/DecimalFormatSymbols;.DEF_DIGIT_STRINGS_ARRAY:[Ljava/lang/String;.0:Ljava/lang/String; 360
+Landroid/provider/Telephony$ThreadsColumns;.ERROR:Ljava/lang/String; 360
+Lcom/android/ims/ImsManager;.TRUE:Ljava/lang/String; 361
+Ljavax/sip/header/ContentEncodingHeader;.NAME:Ljava/lang/String; 362
+Landroid/widget/CompoundButton; 362
+Landroid/view/View$OnClickListener; 362
+Landroid/content/res/AssetManager; 363
+Landroid/view/ContextThemeWrapper; 364
+Landroid/content/res/Resources; 365
+Landroid/content/res/ResourcesImpl; 366
+Landroid/graphics/drawable/GradientDrawable$Orientation;.BOTTOM_TOP:Landroid/graphics/drawable/GradientDrawable$Orientation; 366
+Landroid/view/animation/Animation$1; 366
+Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.220:Ljava/lang/String; 366
+Landroid/graphics/drawable/GradientDrawable$Orientation;.TL_BR:Landroid/graphics/drawable/GradientDrawable$Orientation; 366
+Landroid/view/ViewRootImpl$5; 366
+Landroid/graphics/drawable/GradientDrawable$Orientation;.RIGHT_LEFT:Landroid/graphics/drawable/GradientDrawable$Orientation; 366
+Landroid/media/MediaPlayer$EventHandler; 366
+Landroid/view/View$$ExternalSyntheticLambda7; 366
+Landroid/graphics/drawable/GradientDrawable$Orientation;.BR_TL:Landroid/graphics/drawable/GradientDrawable$Orientation; 366
+Landroid/graphics/drawable/GradientDrawable$Orientation;.TOP_BOTTOM:Landroid/graphics/drawable/GradientDrawable$Orientation; 366
+Landroid/view/View$$ExternalSyntheticLambda0; 366
+Landroid/graphics/drawable/GradientDrawable$Orientation;.LEFT_RIGHT:Landroid/graphics/drawable/GradientDrawable$Orientation; 366
+Lcom/android/internal/policy/PhoneWindow$1; 366
+Landroid/graphics/drawable/GradientDrawable$Orientation;.BL_TR:Landroid/graphics/drawable/GradientDrawable$Orientation; 366
+Landroid/graphics/drawable/GradientDrawable$Orientation;.TR_BL:Landroid/graphics/drawable/GradientDrawable$Orientation; 366
+Landroid/os/PowerManager$3$$ExternalSyntheticLambda0; 366
+Landroid/hardware/SensorManager; 366
+Landroid/app/SharedPreferencesImpl$EditorImpl$$ExternalSyntheticLambda0; 367
+Landroid/view/View$ScrollabilityCache; 368
+Landroid/graphics/drawable/LevelListDrawable; 368
+Landroid/app/INotificationManager; 368
+Landroid/os/IInterface; 371
+Landroid/app/servertransaction/TopResumedActivityChangeItem; 372
+Landroid/app/servertransaction/ClientTransaction; 373
+Landroid/database/CursorToBulkCursorAdaptor; 375
+Landroid/telephony/TelephonyRegistryManager;.sCarrierPrivilegeCallbacks:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry; 375
+Landroid/net/MatchAllNetworkSpecifier; 375
+Lcom/android/internal/telephony/TelephonyPermissions;.sReportedDeviceIDPackages:Ljava/util/Map; 375
+Landroid/telephony/AnomalyReporter; 375
+Landroid/telephony/NetworkRegistrationInfo; 375
+Landroid/telephony/VoiceSpecificRegistrationInfo; 375
+Landroid/telephony/TelephonyRegistryManager;.sCarrierPrivilegeCallbacks:Ljava/util/WeakHashMap; 375
+Landroid/content/ContentProvider$Transport; 375
+Landroid/app/PropertyInvalidatedCache;.sInvalidates:Ljava/util/HashMap; 376
+Landroid/app/PropertyInvalidatedCache$NoPreloadHolder; 376
+Landroid/app/PropertyInvalidatedCache;.sDisabledKeys:Ljava/util/HashSet;.map:Ljava/util/HashMap; 377
+Landroid/media/session/MediaSessionManager$OnMediaKeyEventSessionChangedListener; 378
+Landroid/app/ActivityManager$MyUidObserver; 378
+Landroid/media/session/MediaSessionManager$SessionsChangedWrapper$1; 378
+Landroid/app/PendingIntent$CancelListener; 379
+Lcom/android/internal/widget/MessagingLayout; 380
+Lcom/android/internal/widget/ImageFloatingTextView; 380
+Landroid/widget/ProgressBar$RefreshData;.sPool:Landroid/util/Pools$SynchronizedPool; 380
+Landroid/widget/ViewSwitcher;.dexCache:Ljava/lang/Object; 380
+Lcom/android/internal/util/PerfettoTrigger;.sLastInvocationPerTrigger:Landroid/util/SparseLongArray;.mKeys:[I 380
+Landroid/widget/DateTimeView$ReceiverInfo$1; 380
+Landroid/widget/SeekBar; 380
+Lcom/android/internal/colorextraction/ColorExtractor$OnColorsChangedListener; 380
+Landroid/view/RemotableViewMethod; 380
+Landroid/view/ViewGroup$ViewLocationHolder;.sPool:Landroid/util/Pools$SynchronizedPool; 380
+Landroid/view/View;.TRANSLATION_Y:Landroid/util/Property; 380
+Lcom/android/internal/widget/ConversationLayout; 380
+Lcom/android/internal/util/ContrastColorUtil; 380
+Lcom/android/internal/widget/ImageResolver; 380
+Landroid/view/View;.SCALE_X:Landroid/util/Property; 380
+Landroid/view/ViewGroup$ViewLocationHolder;.sPool:Landroid/util/Pools$SynchronizedPool;.mPool:[Ljava/lang/Object; 380
+Landroid/permission/PermissionManager;.INDICATOR_EXEMPTED_PACKAGES:[Ljava/lang/String; 380
+Landroid/media/MediaRouter2Manager; 380
+Landroid/hardware/display/NightDisplayListener$Callback; 380
+Lcom/android/internal/widget/NotificationOptimizedLinearLayout; 380
+Landroid/hardware/biometrics/BiometricSourceType;.IRIS:Landroid/hardware/biometrics/BiometricSourceType; 380
+Landroid/view/NotificationTopLineView; 380
+Landroid/os/HandlerExecutor; 380
+Lcom/android/internal/widget/RemeasuringLinearLayout; 380
+Lcom/android/internal/util/PerfettoTrigger;.sLastInvocationPerTrigger:Landroid/util/SparseLongArray; 380
+Landroid/animation/ValueAnimator$DurationScaleChangeListener; 380
+Landroid/view/ViewGroup$ChildListForAccessibility;.sPool:Landroid/util/Pools$SynchronizedPool;.mPool:[Ljava/lang/Object; 380
+Landroid/widget/ProgressBar$RefreshData;.sPool:Landroid/util/Pools$SynchronizedPool;.mPool:[Ljava/lang/Object; 380
+Landroid/graphics/drawable/DrawableInflater;.CONSTRUCTOR_MAP:Ljava/util/HashMap; 380
+Landroid/transition/TransitionManager;.sPendingTransitions:Ljava/util/ArrayList; 380
+Landroid/view/SurfaceControl; 380
+Lcom/android/internal/widget/CachingIconView; 380
+Landroid/view/animation/AnimationSet; 380
+Landroid/hardware/face/FaceManager$FaceDetectionCallback; 380
+Lcom/android/internal/widget/NotificationExpandButton; 380
+Landroid/view/View;.SCALE_Y:Landroid/util/Property; 380
+Landroid/view/NotificationHeaderView; 380
+Landroid/widget/RemoteViews;.sMethods:Landroid/util/ArrayMap; 380
+Landroid/widget/DateTimeView; 380
+Lcom/android/internal/widget/NotificationActionListLayout; 380
+Landroid/view/accessibility/AccessibilityManager$$ExternalSyntheticLambda0; 380
+Landroid/view/ViewOverlay$OverlayViewGroup; 380
+Landroid/text/TextShaper$GlyphsConsumer; 380
+Landroid/permission/PermissionManager; 380
+Landroid/widget/RemoteViews;.sLookupKey:Landroid/widget/RemoteViews$MethodKey; 380
+Lcom/android/internal/logging/UiEventLogger; 380
+Landroid/hardware/biometrics/BiometricSourceType;.FACE:Landroid/hardware/biometrics/BiometricSourceType; 380
+Lcom/android/internal/view/menu/ActionMenuItemView; 380
+Landroid/hardware/biometrics/BiometricSourceType;.FINGERPRINT:Landroid/hardware/biometrics/BiometricSourceType; 380
+Lcom/android/internal/util/PerfettoTrigger;.sLastInvocationPerTrigger:Landroid/util/SparseLongArray;.mValues:[J 380
+Landroid/app/trust/TrustManager$TrustListener; 380
+Landroid/view/ViewGroup$ChildListForAccessibility;.sPool:Landroid/util/Pools$SynchronizedPool; 380
+Landroid/view/View$OnHoverListener; 381
+Landroid/content/res/ColorStateList; 381
+Landroid/view/inputmethod/EditorInfo; 382
+Landroid/view/Window$DecorCallback; 382
+Landroid/view/MenuItem$OnActionExpandListener; 382
+Lcom/android/internal/inputmethod/InputMethodPrivilegedOperationsRegistry; 382
+Landroid/widget/inline/InlinePresentationSpec; 383
+Landroid/os/DeadObjectException; 384
+Landroid/content/ContentProviderClient; 385
+Landroid/os/UserHandle;.sExtraUserHandleCache:Landroid/util/SparseArray; 385
+Landroid/util/Log$TerribleFailure; 386
+Landroid/telephony/DataSpecificRegistrationInfo; 387
+Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle; 388
+Landroid/telephony/NetworkService; 389
+Lcom/android/internal/telephony/nano/PersistAtomsProto$ImsDedicatedBearerEvent; 390
+Lcom/android/internal/telephony/nano/PersistAtomsProto$SatelliteController; 390
+Lcom/android/internal/telephony/GsmCdmaCallTracker; 390
+Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.483:[Ljava/lang/String; 390
+Lcom/android/internal/telephony/nano/PersistAtomsProto$CellularServiceState; 390
+Lcom/android/ims/rcs/uce/eab/EabProvider; 390
+Lcom/android/internal/telephony/NetworkTypeController$DefaultState; 390
+Lcom/android/internal/telephony/nano/PersistAtomsProto$CarrierRoamingSatelliteSession; 390
+Lcom/android/internal/telephony/ProxyController; 390
+Lcom/android/internal/telephony/TelephonyComponentFactory; 390
+Lcom/android/internal/telephony/nano/PersistAtomsProto$SatelliteSosMessageRecommender; 390
+Lcom/android/internal/telephony/nano/PersistAtomsProto$ImsRegistrationTermination; 390
+Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.23:[Ljava/lang/String; 390
+Lcom/android/internal/telephony/DisplayInfoController; 390
+Lcom/android/internal/telephony/DeviceStateMonitor$3; 390
+Landroid/telephony/CellSignalStrengthWcdma; 390
+Landroid/os/Handler$MessengerImpl; 390
+Lcom/android/internal/telephony/TelephonyDevController;.mModems:Ljava/util/ArrayList; 390
+Lcom/android/internal/telephony/CarrierServiceBindHelper$CarrierServicePackageMonitor; 390
+Landroid/telephony/ims/RegistrationManager$RegistrationCallback$RegistrationBinder; 390
+Lcom/android/internal/telephony/uicc/UiccProfile; 390
+Lcom/android/internal/telephony/cdma/CdmaInboundSmsHandler$CdmaScpTestBroadcastReceiver; 390
+Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.363:[Ljava/lang/String; 390
+Lcom/android/internal/telephony/nano/PersistAtomsProto$CarrierIdMismatch; 390
+Lcom/android/internal/telephony/euicc/EuiccConnector$UnavailableState; 390
+Lcom/android/internal/telephony/nano/PersistAtomsProto$SatelliteProvision; 390
+Lcom/android/internal/telephony/emergency/EmergencyNumberTracker$1; 390
+Lcom/android/internal/telephony/metrics/TelephonyMetrics; 390
+Lcom/android/internal/telephony/RILRequest; 390
+Lcom/android/internal/telephony/nano/PersistAtomsProto$GbaEvent; 390
+Lcom/android/internal/telephony/TelephonyDevController; 390
+Lcom/android/internal/telephony/imsphone/ImsPhoneCallTracker$2; 390
+Lcom/android/internal/telephony/satellite/PointingAppController; 390
+Lcom/android/internal/telephony/nano/PersistAtomsProto$SipMessageResponse; 390
+Lcom/android/internal/telephony/InboundSmsHandler$NewMessageNotificationActionReceiver; 390
+Lcom/android/internal/telephony/nano/TelephonyProto$RilDataCall; 390
+Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.831:[Ljava/lang/String; 390
+Lcom/android/internal/telephony/DeviceStateMonitor; 390
+Landroid/telephony/TelephonyRegistryManager$3; 390
+Lcom/android/internal/telephony/nano/PersistAtomsProto$SatelliteSession; 390
+Landroid/telephony/data/ApnSetting;.APN_TYPE_INT_MAP:Ljava/util/Map; 390
+Lcom/android/internal/telephony/imsphone/ImsExternalCallTracker; 390
+Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mInCallVoicePrivacyOffRegistrants:Lcom/android/internal/telephony/RegistrantList;.registrants:Ljava/util/ArrayList; 390
+Landroid/net/NetworkPolicyManager$SubscriptionCallbackProxy; 390
+Lcom/android/internal/telephony/cdma/CdmaInboundSmsHandler; 390
+Lcom/android/internal/telephony/IntentBroadcaster; 390
+Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.469:[Ljava/lang/String; 390
+Landroid/telephony/BarringInfo$BarringServiceInfo; 390
+Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.673:[Ljava/lang/String; 390
+Lcom/android/internal/telephony/PackageChangeReceiver; 390
+Lcom/android/internal/telephony/euicc/EuiccConnector$ConnectedState$14; 390
+Lcom/android/internal/telephony/RadioInterfaceCapabilityController; 390
+Lcom/android/internal/telephony/nano/PersistAtomsProto$VoiceCallSession; 390
+Lcom/android/internal/telephony/nano/PersistAtomsProto$ImsRegistrationStats; 390
+Lcom/android/internal/telephony/SmsStorageMonitor$1; 390
+Lcom/android/internal/telephony/nano/PersistAtomsProto$NetworkRequestsV2; 390
+Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.349:[Ljava/lang/String; 390
+Lcom/android/internal/telephony/nano/PersistAtomsProto$CarrierRoamingSatelliteControllerStats; 390
+Lcom/android/internal/telephony/security/NullCipherNotifier; 390
+Lcom/android/internal/telephony/uicc/UiccPkcs15$Pkcs15Selector; 390
+Lcom/android/internal/telephony/SMSDispatcher$1; 390
+Lcom/android/internal/telephony/nano/PersistAtomsProto$DataNetworkValidation; 390
+Lcom/android/internal/telephony/CarrierActionAgent; 390
+Lcom/android/internal/telephony/TelephonyTester$1; 390
+Lcom/android/internal/telephony/nano/PersistAtomsProto$SipTransportSession; 390
+Lcom/android/internal/telephony/IntentBroadcaster$1; 390
+Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mTtyModeReceivedRegistrants:Lcom/android/internal/telephony/RegistrantList;.registrants:Ljava/util/ArrayList; 390
+Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mMmiCompleteRegistrants:Lcom/android/internal/telephony/RegistrantList;.registrants:Ljava/util/ArrayList; 390
+Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mSuppServiceFailedRegistrants:Lcom/android/internal/telephony/RegistrantList;.registrants:Ljava/util/ArrayList; 390
+Lcom/android/internal/telephony/nano/PersistAtomsProto$PresenceNotifyEvent; 390
+Lcom/android/i18n/timezone/TimeZoneFinder; 390
+Lcom/android/internal/telephony/SimActivationTracker$1; 390
+Lcom/android/internal/telephony/NitzStateMachine; 390
+Lcom/android/internal/telephony/ims/ImsResolver$3; 390
+Lcom/android/internal/telephony/euicc/EuiccConnector$DisconnectedState; 390
+Lcom/android/ims/ImsManager;.IMS_STATS_CALLBACKS:Landroid/util/SparseArray; 390
+Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.1457:[Ljava/lang/String; 390
+Lcom/android/internal/telephony/gsm/GsmInboundSmsHandler$GsmCbTestBroadcastReceiver; 390
+Landroid/timezone/TelephonyLookup; 390
+Lcom/android/internal/telephony/nano/PersistAtomsProto$ImsDedicatedBearerListenerEvent; 390
+Lcom/android/internal/telephony/nano/PersistAtomsProto$IncomingSms; 390
+Lcom/android/internal/telephony/UiccPhoneBookController; 390
+Lcom/android/internal/telephony/imsphone/ImsPhone; 390
+Lcom/android/internal/telephony/euicc/EuiccConnector$AvailableState; 390
+Lcom/android/internal/telephony/euicc/EuiccConnector$BindingState; 390
+Lcom/android/internal/telephony/euicc/EuiccCardController; 390
+Lcom/android/internal/telephony/nano/TelephonyProto$TelephonyCallSession$Event$RilCall; 390
+Lcom/android/internal/telephony/uicc/UiccStateChangedLauncher; 390
+Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mDisplayInfoRegistrants:Lcom/android/internal/telephony/RegistrantList;.registrants:Ljava/util/ArrayList; 390
+Lcom/android/internal/telephony/TelephonyDevController;.mSims:Ljava/util/ArrayList; 390
+Lcom/android/internal/telephony/AppSmsManager; 390
+Lcom/android/internal/telephony/SmsUsageMonitor; 390
+Lcom/android/internal/telephony/cat/CatService; 390
+Lcom/android/internal/telephony/GsmCdmaCallTracker$1; 390
+Landroid/telephony/ims/ProvisioningManager$Callback$CallbackBinder; 390
+Landroid/telephony/ims/ImsMmTelManager$CapabilityCallback$CapabilityBinder; 390
+Landroid/telephony/CellSignalStrengthGsm; 390
+Lcom/android/internal/telephony/nano/PersistAtomsProto$EmergencyNumbersInfo; 390
+Lcom/android/internal/telephony/cdma/CdmaInboundSmsHandler$CdmaCbTestBroadcastReceiver; 390
+Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.1441:[Ljava/lang/String; 390
+Lcom/android/internal/telephony/RIL;.sRilTimeHistograms:Landroid/util/SparseArray; 390
+Landroid/app/timezonedetector/TimeZoneDetector; 390
+Lcom/android/internal/telephony/SmsBroadcastUndelivered; 390
+Landroid/telephony/emergency/EmergencyNumber; 390
+Lcom/android/internal/telephony/nano/PersistAtomsProto$VoiceCallRatUsage; 390
+Lcom/android/internal/telephony/nano/PersistAtomsProto$CellularDataServiceSwitch; 390
+Lcom/android/ims/ImsManager;.IMS_STATS_CALLBACKS:Landroid/util/SparseArray;.mValues:[Ljava/lang/Object; 390
+Lcom/android/ims/ImsManager;.IMS_STATS_CALLBACKS:Landroid/util/SparseArray;.mKeys:[I 390
+Lcom/android/internal/telephony/CommandException$Error;.INVALID_SIM_STATE:Lcom/android/internal/telephony/CommandException$Error; 390
+Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mDisconnectRegistrants:Lcom/android/internal/telephony/RegistrantList;.registrants:Ljava/util/ArrayList; 390
+Lcom/android/internal/telephony/PhoneSubInfoController; 390
+Lcom/android/internal/telephony/PhoneFactory; 390
+Lcom/android/internal/telephony/SmsApplication$SmsPackageMonitor; 390
+Lcom/android/internal/telephony/nano/PersistAtomsProto$SipTransportFeatureTagStats; 390
+Landroid/telephony/ims/aidl/IImsServiceController$Stub$Proxy; 390
+Lcom/android/internal/telephony/nano/PersistAtomsProto$RcsAcsProvisioningStats; 390
+Lcom/android/internal/telephony/SmsStorageMonitor; 390
+Lcom/android/internal/telephony/uicc/UiccController; 390
+Lcom/android/internal/telephony/GsmCdmaPhone; 390
+Landroid/telephony/CellSignalStrengthTdscdma; 390
+Lcom/android/internal/telephony/SomeArgs; 390
+Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mHandlerMap:Ljava/util/HashMap; 390
+Lcom/android/internal/telephony/cdma/CdmaSubscriptionSourceManager; 390
+Lcom/android/internal/telephony/uicc/UiccProfile$2; 390
+Lcom/android/internal/telephony/LocaleTracker; 390
+Lcom/android/internal/telephony/CarrierKeyDownloadManager$3; 390
+Lcom/android/internal/telephony/euicc/EuiccConnector$1; 390
+Lcom/android/internal/telephony/CarrierServiceBindHelper$1; 390
+Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mBackgroundCalls:Ljava/util/ArrayList; 390
+Landroid/telephony/CellSignalStrengthCdma; 390
+Landroid/telephony/TelephonyLocalConnection; 390
+Lcom/android/internal/telephony/RilWakelockInfo; 390
+Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mForegroundCalls:Ljava/util/ArrayList; 390
+Landroid/os/AsyncResult; 390
+Lcom/android/internal/telephony/nano/PersistAtomsProto$SatelliteIncomingDatagram; 390
+Lcom/android/i18n/timezone/TelephonyLookup; 390
+Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.789:[Ljava/lang/String; 390
+Lcom/android/internal/telephony/euicc/EuiccController; 390
+Lcom/android/internal/telephony/nano/PersistAtomsProto$OutgoingSms; 390
+Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.33:[Ljava/lang/String; 390
+Lcom/android/phone/ecc/nano/ProtobufEccData$EccInfo; 390
+Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.549:[Ljava/lang/String; 390
+Lcom/android/internal/telephony/nano/PersistAtomsProto$OutgoingShortCodeSms; 390
+Lcom/android/phone/ecc/nano/ProtobufEccData$CountryInfo; 390
+Lcom/android/internal/telephony/IWapPushManager; 390
+Lcom/android/internal/telephony/MccTable; 390
+Lcom/android/internal/telephony/nano/PersistAtomsProto$SipDelegateStats; 390
+Lcom/android/internal/telephony/nano/PersistAtomsProto$UnmeteredNetworks; 390
+Lcom/android/internal/telephony/IccSmsInterfaceManager; 390
+Lcom/android/internal/telephony/PhoneConfigurationManager; 390
+Lcom/android/internal/telephony/ims/ImsServiceController$ImsFeatureStatusCallback$1; 390
+Lcom/android/internal/telephony/uicc/UiccProfile$4; 390
+Lcom/android/internal/telephony/euicc/EuiccCardController$SimSlotStatusChangedBroadcastReceiver; 390
+Lcom/android/internal/telephony/euicc/EuiccConnector$ConnectedState; 390
+Lcom/android/internal/telephony/emergency/EmergencyNumberTracker; 390
+Lcom/android/internal/telephony/nano/PersistAtomsProto$SatelliteConfigUpdater; 390
+Lcom/android/internal/telephony/nano/PersistAtomsProto$RcsClientProvisioningStats; 390
+Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager; 390
+Lcom/android/internal/telephony/nano/PersistAtomsProto$UceEventStats; 390
+Lcom/android/internal/telephony/ims/ImsResolver$2; 390
+Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mSignalInfoRegistrants:Lcom/android/internal/telephony/RegistrantList;.registrants:Ljava/util/ArrayList; 390
+Lcom/android/internal/telephony/RadioConfig; 390
+Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mRingingCalls:Ljava/util/ArrayList; 390
+Lcom/android/internal/telephony/ims/ImsResolver; 390
+Lcom/android/internal/telephony/euicc/EuiccConnector$EuiccPackageMonitor; 390
+Lcom/android/internal/telephony/SmsBroadcastUndelivered$1; 390
+Lcom/android/internal/telephony/SmsApplication; 390
+Lcom/android/internal/telephony/nano/PersistAtomsProto$SatelliteOutgoingDatagram; 390
+Lcom/android/internal/telephony/uicc/PinStorage$1; 390
+Lcom/android/internal/telephony/IccPhoneBookInterfaceManager; 390
+Lcom/android/internal/telephony/nano/PersistAtomsProto$NetworkRequests; 390
+Lcom/android/internal/telephony/ServiceStateTracker; 390
+Lcom/android/internal/telephony/nano/PersistAtomsProto$ImsRegistrationServiceDescStats; 390
+Lcom/android/internal/telephony/ServiceStateTracker$1; 390
+Landroid/net/TelephonyNetworkSpecifier; 390
+Landroid/telephony/CellSignalStrengthNr; 390
+Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mInCallVoicePrivacyOnRegistrants:Lcom/android/internal/telephony/RegistrantList;.registrants:Ljava/util/ArrayList; 390
+Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mPhones:Ljava/util/ArrayList; 390
+Landroid/telephony/ModemActivityInfo; 390
+Lcom/android/internal/telephony/nano/PersistAtomsProto$ImsRegistrationFeatureTagStats; 390
+Lcom/android/internal/telephony/LocaleTracker$1; 390
+Lcom/android/internal/telephony/SmsDispatchersController; 390
+Landroid/telephony/ModemInfo; 390
+Lcom/android/internal/telephony/CommandException; 390
+Lcom/android/internal/telephony/satellite/SatelliteModemInterface; 390
+Lcom/android/internal/telephony/CarrierPrivilegesTracker$1; 390
+Lcom/android/internal/telephony/nano/PersistAtomsProto$DataCallSession; 390
+Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.1171:[Ljava/lang/String; 390
+Lcom/android/internal/telephony/SimActivationTracker; 390
+Lcom/android/internal/telephony/nano/TelephonyProto$TelephonyServiceState$NetworkRegistrationInfo; 390
+Landroid/telephony/CellSignalStrengthLte; 390
+Lcom/android/internal/telephony/CarrierActionAgent$1; 390
+Landroid/timezone/TimeZoneFinder; 390
+Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.1287:[Ljava/lang/String; 390
+Lcom/android/internal/telephony/nano/PersistAtomsProto$SatelliteEntitlement; 390
+Lcom/android/internal/telephony/CarrierResolver$2; 390
+Lcom/android/internal/telephony/CellBroadcastServiceManager; 390
+Lcom/android/internal/telephony/euicc/EuiccConnector$ConnectedState$5; 390
+Lcom/android/internal/telephony/imsphone/ImsPhoneCallTracker; 390
+Lcom/android/internal/telephony/uicc/UiccCarrierPrivilegeRules; 390
+Lcom/android/internal/telephony/NetworkTypeController$1; 390
+Lcom/android/internal/telephony/util/NotificationChannelController$1; 390
+Lcom/android/internal/telephony/StateMachine$SmHandler; 390
+Lcom/android/ims/FeatureConnector$1; 390
+Lcom/android/internal/telephony/ims/ImsResolver$1; 390
+Lcom/android/internal/telephony/MultiSimSettingController; 390
+Landroid/telephony/ims/aidl/IImsConfig$Stub$Proxy; 391
+Landroid/telephony/ims/aidl/IImsRegistration$Stub$Proxy; 391
+Lcom/android/ims/ImsManager;.IMS_MANAGER_INSTANCES:Landroid/util/SparseArray; 392
+Lcom/android/ims/ImsManager;.IMS_MANAGER_INSTANCES:Landroid/util/SparseArray;.mValues:[Ljava/lang/Object; 392
+Lcom/android/ims/ImsManager;.IMS_MANAGER_INSTANCES:Landroid/util/SparseArray;.mKeys:[I 393
+Landroid/telephony/ims/ImsUtListener; 394
+Landroid/telephony/ims/feature/MmTelFeature$1; 394
+Landroid/telephony/ims/stub/ImsRegistrationImplBase$1; 394
+Landroid/telephony/ims/stub/ImsConfigImplBase$ImsConfigStub; 394
+Landroid/telephony/TelephonyRegistryManager; 396
+Landroid/widget/Button; 397
+Landroid/widget/TextView; 398
+Lcom/android/internal/telephony/MccTable;.FALLBACKS:Ljava/util/Map;.table:[Ljava/util/HashMap$Node;.6:Ljava/util/HashMap$Node;.key:Ljava/lang/Object; 399
+Landroid/media/AudioManager$OnAudioFocusChangeListener; 400
+primitive [I 401
+Landroid/os/Parcelable$Creator; 402
+Landroid/os/Bundle; 403
+Landroid/graphics/Bitmap$Config;.HARDWARE:Landroid/graphics/Bitmap$Config; 404
+Landroid/graphics/Bitmap$Config;.RGBA_F16:Landroid/graphics/Bitmap$Config; 404
+Landroid/renderscript/Allocation;.mBitmapOptions:Landroid/graphics/BitmapFactory$Options;.inPreferredConfig:Landroid/graphics/Bitmap$Config; 404
+Landroid/graphics/Bitmap$Config;.ALPHA_8:Landroid/graphics/Bitmap$Config; 404
+Landroid/graphics/Bitmap$Config;.RGBA_1010102:Landroid/graphics/Bitmap$Config; 404
+Landroid/graphics/Bitmap$Config;.ARGB_4444:Landroid/graphics/Bitmap$Config; 404
+Landroid/graphics/Bitmap$Config;.RGB_565:Landroid/graphics/Bitmap$Config; 404
+Landroid/icu/util/Calendar;.WEEK_DATA_CACHE:Landroid/icu/util/Calendar$WeekDataCache;.map:Ljava/util/concurrent/ConcurrentHashMap; 407
+Landroid/icu/util/ULocale; 408
+Landroid/graphics/Paint;.sMinikinLocaleListIdCache:Ljava/util/HashMap; 409
+Landroid/icu/text/BreakIterator;.iterCache:[Landroid/icu/impl/CacheValue; 410
+Landroid/widget/EditText; 411
+Landroid/view/autofill/AutofillValue; 412
+Landroid/view/ViewGroup$ChildListForAutoFillOrContentCapture;.sPool:Landroid/util/Pools$SimplePool;.mPool:[Ljava/lang/Object; 413
+Landroid/view/ViewGroup$ChildListForAutoFillOrContentCapture;.sPool:Landroid/util/Pools$SimplePool; 413
+Landroid/view/ViewGroup; 414
+Landroid/graphics/Rect; 415
+Landroid/graphics/Insets; 416
+Landroid/content/LocusId; 417
+Landroid/view/contentcapture/ContentCaptureContext; 417
+Landroid/telephony/TelephonyCallback$DisplayInfoListener; 419
+Landroid/app/assist/AssistStructure$HtmlInfoNode; 420
+Landroid/app/ActivityManager$AppTask; 420
+Landroid/database/CursorIndexOutOfBoundsException; 422
+Landroid/media/MediaPlayer; 422
+Lcom/android/internal/policy/DecorView$2; 423
+Landroid/security/keystore2/AndroidKeyStoreRSAPrivateKey; 424
+Landroid/widget/Spinner; 424
+Landroid/app/slice/Slice;.SUBTYPE_SOURCE:Ljava/lang/String; 425
+Ljavax/sip/message/Request;.INFO:Ljava/lang/String; 425
+Lgov/nist/javax/sip/header/AuthenticationHeader;.SIGNATURE:Ljava/lang/String; 426
+Landroid/widget/RadioGroup$OnCheckedChangeListener; 428
+Ljavax/sip/header/AcceptEncodingHeader;.NAME:Ljava/lang/String; 429
+Landroid/app/ReceiverRestrictedContext; 429
+Landroid/content/Context;.ACCOUNT_SERVICE:Ljava/lang/String; 429
+Landroid/service/trust/TrustAgentService;.EXTRA_TOKEN:Ljava/lang/String; 429
+Landroid/view/VelocityTracker;.sPool:Landroid/util/Pools$SynchronizedPool;.mPool:[Ljava/lang/Object; 429
+Lcom/android/internal/widget/DialogTitle; 429
+Landroid/view/VelocityTracker;.sPool:Landroid/util/Pools$SynchronizedPool; 429
+Landroid/os/StrictMode$OnThreadViolationListener; 429
+Lcom/android/internal/widget/ButtonBarLayout; 429
+Lcom/android/internal/widget/AlertDialogLayout; 429
+Lcom/android/internal/logging/AndroidHandler; 430
+Landroid/webkit/JavascriptInterface; 446
+Landroid/icu/impl/StandardPlural; 447
+Landroid/icu/impl/number/range/StandardPluralRanges; 448
+Landroid/icu/text/PluralRules$Operand; 448
+Landroid/icu/impl/PluralRulesLoader;.loader:Landroid/icu/impl/PluralRulesLoader; 448
+Landroid/icu/impl/PluralRulesLoader;.loader:Landroid/icu/impl/PluralRulesLoader;.pluralRulesCache:Ljava/util/Map; 448
+Landroid/webkit/CookieManager; 449
+Landroid/os/strictmode/Violation; 459
+Lcom/android/internal/telephony/uicc/asn1/Asn1Node;.EMPTY_NODE_LIST:Ljava/util/List; 460
+Landroid/os/strictmode/CredentialProtectedWhileLockedViolation; 462
+Landroid/content/AsyncQueryHandler; 463
+Landroid/widget/CheckedTextView; 464
+Landroid/app/Activity$$ExternalSyntheticLambda0; 464
+Landroid/app/job/JobParameters; 467
+Landroid/os/SystemProperties; 468
+Landroid/hardware/display/DisplayManager$DisplayListener; 470
+Landroid/view/View$OnApplyWindowInsetsListener; 470
+Landroid/view/Choreographer$FrameCallback; 471
+Landroid/os/Handler$Callback; 472
+Landroid/telephony/TelephonyCallback$DataConnectionStateListener; 473
+Landroid/view/WindowManagerImpl; 474
+Landroid/util/DisplayMetrics; 474
+Landroid/content/ServiceConnection; 474
+Landroid/app/ActivityManager$MemoryInfo; 474
+Landroid/view/Display; 474
+Landroid/os/VibrationEffect; 475
+Landroid/view/SurfaceView; 476
+Landroid/content/ContextWrapper; 477
+Landroid/app/Application; 478
+Landroid/view/View$OnSystemUiVisibilityChangeListener; 479
+Landroid/view/View$OnLayoutChangeListener; 480
+Landroid/os/Build$VERSION; 481
+Landroid/view/InputDevice; 482
+Landroid/preference/PreferenceManager; 482
+Landroid/app/SharedPreferencesImpl$EditorImpl; 482
+Landroid/widget/Switch; 486
+Landroid/view/View$OnGenericMotionListener; 487
+Landroid/icu/text/DecimalFormatSymbols;.DEF_DIGIT_STRINGS_ARRAY:[Ljava/lang/String;.1:Ljava/lang/String; 488
+Landroid/widget/Editor$TextRenderNode; 489
+Landroid/database/sqlite/SQLiteCantOpenDatabaseException; 490
+Landroid/accounts/Account; 490
+Landroid/accounts/Account;.sAccessedAccounts:Ljava/util/Set; 491
+Landroid/app/PendingIntent$OnFinished; 492
+Landroid/hardware/location/ContextHubTransaction$OnCompleteListener; 492
+Landroid/os/WorkSource; 492
+Landroid/os/ParcelUuid; 493
+Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.12:Ljava/util/WeakHashMap$Entry;.referent:Ljava/lang/Object;.mLock:Ljava/lang/Object; 493
+Landroid/database/sqlite/SQLiteConstraintException; 493
+Landroid/location/Location; 493
+Landroid/os/strictmode/CustomViolation; 494
+Landroid/graphics/drawable/PictureDrawable; 495
+primitive [C 504
+primitive [S 505
+Landroid/view/AttachedSurfaceControl$OnBufferTransformHintChangedListener; 507
+Landroid/webkit/WebChromeClient$CustomViewCallback; 508
+primitive [B 514
+Landroid/view/Window$OnFrameMetricsAvailableListener; 516
+Landroid/net/Uri$PathPart;.EMPTY:Landroid/net/Uri$PathPart; 521
+Landroid/app/IActivityManager; 522
+Landroid/text/style/ImageSpan; 523
+Landroid/security/keystore2/AndroidKeyStoreECPrivateKey; 524
+Landroid/security/keystore/KeyInfo; 525
+Landroid/view/WindowLeaked; 526
+Landroid/app/ContentProviderHolder; 526
+Landroid/text/style/URLSpan; 526
+Landroid/icu/util/ULocale$AvailableType;.DEFAULT:Landroid/icu/util/ULocale$AvailableType;.name:Ljava/lang/String; 527
+Lcom/android/internal/telephony/cdnr/CarrierDisplayNameResolver;.EF_SOURCE_PRIORITY:Ljava/util/List;.a:[Ljava/lang/Object;.9:Ljava/lang/Integer; 528
+Lcom/android/internal/telephony/WspTypeDecoder;.WELL_KNOWN_MIME_TYPES:Ljava/util/HashMap;.table:[Ljava/util/HashMap$Node;.11:Ljava/util/HashMap$Node;.key:Ljava/lang/Object; 528
+Lcom/android/internal/telephony/cdnr/CarrierDisplayNameResolver;.EF_SOURCE_PRIORITY:Ljava/util/List;.a:[Ljava/lang/Object;.2:Ljava/lang/Integer; 529
+Landroid/text/Html$TagHandler; 530
+Landroid/text/HtmlToSpannedConverter$Font; 531
+Landroid/transition/Explode; 532
+Landroid/content/pm/PackageManager$OnPermissionsChangedListener; 535
+Landroid/os/UserManager; 537
+Landroid/hardware/display/ColorDisplayManager$ColorDisplayManagerInternal; 538
+Landroid/graphics/ColorSpace$Model;.RGB:Landroid/graphics/ColorSpace$Model; 539
+Landroid/app/WallpaperManager; 539
+Landroid/app/Notification; 540
+Landroid/app/RemoteAction; 541
+Landroid/database/sqlite/SQLiteTransactionListener; 542
+Landroid/accounts/OnAccountsUpdateListener; 544
+Landroid/accounts/AccountManager$20; 545
+Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.1:Ljava/util/WeakHashMap$Entry;.referent:Ljava/lang/Object;.mLock:Ljava/lang/Object; 548
+Landroid/hardware/SensorEventListener; 553
+Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.6:Ljava/lang/String; 553
+Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.237:Ljava/lang/String; 553
+Lcom/android/internal/content/om/OverlayConfigParser;.CONFIG_DIRECTORY:Ljava/lang/String; 553
+Lcom/android/internal/app/procstats/DumpUtils;.STATE_TAGS:[Ljava/lang/String;.14:Ljava/lang/String; 553
+Landroid/opengl/GLSurfaceView; 553
+Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.127:Ljava/lang/String; 553
+Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.141:Ljava/lang/String; 553
+Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.80:Ljava/lang/String; 553
+Landroid/util/AndroidRuntimeException; 553
+Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.35:Ljava/lang/String; 553
+Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.15:Ljava/lang/String; 553
+Landroid/text/Html$ImageGetter; 553
+Landroid/view/ThreadedRenderer;.OVERDRAW_PROPERTY_SHOW:Ljava/lang/String; 553
+Landroid/icu/impl/LocaleIDs;._replacementCountries:[Ljava/lang/String;.9:Ljava/lang/String; 553
+Landroid/window/ImeOnBackInvokedDispatcher;.RESULT_KEY_PRIORITY:Ljava/lang/String; 553
+Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.118:Ljava/lang/String; 553
+Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.221:Ljava/lang/String; 553
+Landroid/transition/TransitionInflater;.sConstructors:Landroid/util/ArrayMap; 554
+Lcom/android/internal/transition/EpicenterTranslateClipReveal; 554
+Landroid/widget/HorizontalScrollView$SavedState; 555
+Landroid/widget/Spinner$SavedState; 555
+Landroid/widget/AbsSpinner$SavedState; 555
+Lcom/android/internal/telephony/WspTypeDecoder;.WELL_KNOWN_MIME_TYPES:Ljava/util/HashMap;.table:[Ljava/util/HashMap$Node;.33:Ljava/util/HashMap$Node;.key:Ljava/lang/Object; 557
+Landroid/graphics/drawable/ShapeDrawable; 558
+Landroid/text/method/DialerKeyListener; 560
+Lgov/nist/javax/sip/address/NetObject;.PHONE:Ljava/lang/String; 567
+Landroid/app/usage/UsageEvents$Event;.DEVICE_EVENT_PACKAGE_NAME:Ljava/lang/String; 567
+Landroid/view/translation/UiTranslationManager;.EXTRA_PACKAGE_NAME:Ljava/lang/String; 567
+Landroid/icu/text/MessageFormat;.dateModifierList:[Ljava/lang/String;.3:Ljava/lang/String; 567
+Landroid/icu/impl/ValidIdentifiers$Datatype;.language:Landroid/icu/impl/ValidIdentifiers$Datatype;.name:Ljava/lang/String; 567
+Lgov/nist/javax/sip/header/extensions/ReferencesHeader;.SERVICE:Ljava/lang/String; 567
+Landroid/icu/impl/locale/LocaleValidityChecker$SpecialCase;.normal:Landroid/icu/impl/locale/LocaleValidityChecker$SpecialCase;.name:Ljava/lang/String; 567
+Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.140:Ljava/lang/String; 568
+Landroid/icu/impl/units/UnitPreferences;.measurementSystem:Ljava/util/Map;.m:Ljava/util/Map;.table:[Ljava/util/HashMap$Node;.15:Ljava/util/HashMap$Node;.value:Ljava/lang/Object; 569
+Lcom/android/i18n/timezone/TimeZoneFinder;.COUNTRY_ELEMENT:Ljava/lang/String; 570
+Landroid/icu/text/MessageFormat;.rootLocale:Ljava/util/Locale;.baseLocale:Lsun/util/locale/BaseLocale;.language:Ljava/lang/String; 571
+Landroid/view/accessibility/AccessibilityManager$TouchExplorationStateChangeListener; 585
+Landroid/graphics/drawable/AnimatedVectorDrawable; 590
+Landroid/view/TextureView; 592
+Landroid/app/Notification$MessagingStyle; 596
+Landroid/os/Message;.sPoolSync:Ljava/lang/Object; 596
+Landroid/os/strictmode/LeakedClosableViolation; 597
+Landroid/view/DisplayCutout; 597
+Landroid/app/AppOpsManager$OnOpNotedListener; 597
+Lcom/android/internal/policy/AttributeCache; 597
+Landroid/app/Notification$CallStyle; 597
+Lcom/android/internal/logging/UiEventLogger$UiEventEnum; 597
+Lcom/android/internal/statusbar/NotificationVisibility; 597
+Landroid/app/AppOpsManager$OnOpNotedInternalListener; 597
+Landroid/window/IRemoteTransition$Stub$Proxy; 597
+Lcom/android/internal/logging/MetricsLogger; 597
+Landroid/hardware/display/DisplayManagerGlobal$DisplayListenerDelegate$$ExternalSyntheticLambda0; 597
+Landroid/app/Notification$DecoratedCustomViewStyle; 597
+Landroid/app/AppOpsManager$OnOpStartedListener; 597
+Lcom/android/internal/R$styleable;.WindowAnimation:[I 597
+Lcom/android/internal/util/LatencyTracker$Action; 597
+Landroid/app/Notification$BigPictureStyle; 598
+Landroid/app/Notification$MediaStyle; 598
+Landroid/app/Notification$InboxStyle; 598
+Landroid/app/Notification$BigTextStyle; 599
+Landroid/app/AppOpsManager$Mode; 600
+Landroid/view/accessibility/AccessibilityManager$AccessibilityServicesStateChangeListener; 601
+Landroid/annotation/IdRes; 602
+Landroid/app/usage/AppStandbyInfo; 603
+Landroid/content/ContentProvider$PipeDataWriter; 604
+Landroid/os/strictmode/UnsafeIntentLaunchViolation; 604
+Landroid/app/servertransaction/ObjectPool;.sPoolMap:Ljava/util/Map; 605
+Landroid/app/servertransaction/ResumeActivityItem; 605
+Landroid/app/servertransaction/ActivityRelaunchItem; 605
+Landroid/os/strictmode/DiskReadViolation; 606
+Landroid/net/metrics/DhcpClientEvent; 607
+Landroid/icu/impl/CharacterPropertiesImpl;.inclusions:[Landroid/icu/text/UnicodeSet; 608
+Lcom/android/internal/policy/PhoneWindow; 616
+Landroid/os/ResultReceiver$MyRunnable; 619
+Landroid/window/ImeOnBackInvokedDispatcher$$ExternalSyntheticLambda0; 620
+Landroid/text/StaticLayout; 620
+Landroid/graphics/SurfaceTexture$1; 620
+Landroid/animation/AnimationHandler$AnimationFrameCallbackProvider; 620
+Landroid/provider/FontsContract;.sTypefaceCache:Landroid/util/LruCache; 622
+Landroid/provider/FontsContract;.sTypefaceCache:Landroid/util/LruCache;.map:Ljava/util/LinkedHashMap; 623
+Landroid/graphics/drawable/InsetDrawable; 624
+Landroid/graphics/PorterDuff$Mode;.SRC_IN:Landroid/graphics/PorterDuff$Mode; 625
+Landroid/widget/CheckBox; 626
+Landroid/widget/RadioButton; 627
+Landroid/content/pm/PackageManager; 628
+Landroid/view/textclassifier/TextLanguage;.EMPTY:Landroid/view/textclassifier/TextLanguage;.mBundle:Landroid/os/Bundle;.mClassLoader:Ljava/lang/ClassLoader;.packages:Ljava/util/Map;.m:Ljava/util/Map;.table:[Ljava/util/HashMap$Node; 631
+Landroid/view/textclassifier/TextLanguage;.EMPTY:Landroid/view/textclassifier/TextLanguage;.mBundle:Landroid/os/Bundle;.mClassLoader:Ljava/lang/ClassLoader;.packages:Ljava/util/Map;.m:Ljava/util/Map; 631
+Landroid/view/textclassifier/TextLanguage;.EMPTY:Landroid/view/textclassifier/TextLanguage;.mBundle:Landroid/os/Bundle;.mClassLoader:Ljava/lang/ClassLoader; 631
+Landroid/content/AttributionSource; 632
+Landroid/widget/GridLayout;.UNDEFINED_ALIGNMENT:Landroid/widget/GridLayout$Alignment; 633
+Landroid/text/Spanned; 636
+Landroid/renderscript/RenderScript; 636
+Lcom/android/internal/policy/PhoneLayoutInflater; 636
+Landroid/content/pm/IPackageManager$Stub$Proxy; 637
+Landroid/widget/ViewSwitcher; 638
+Landroid/app/IActivityTaskManager$Stub$Proxy; 639
+Lcom/android/internal/telephony/ITelephony; 639
+Landroid/content/IntentFilter; 640
+Landroid/telephony/TelephonyCallback$RadioPowerStateListener; 641
+Landroid/telephony/TelephonyCallback$ServiceStateListener; 642
+Landroid/app/compat/CompatChanges;.QUERY_CACHE:Landroid/app/compat/ChangeIdStateCache;.mCache:Ljava/util/LinkedHashMap; 643
+Landroid/app/compat/CompatChanges;.QUERY_CACHE:Landroid/app/compat/ChangeIdStateCache; 643
+Landroid/app/AlarmManager; 644
+Landroid/text/format/DateFormat; 647
+Landroid/icu/text/Collator; 648
+Landroid/widget/TextView$SavedState; 649
+Landroid/widget/ViewFlipper; 661
+Landroid/os/PersistableBundle; 664
+Landroid/content/pm/ShortcutInfo; 665
+Landroid/graphics/drawable/Icon; 666
+Landroid/content/Intent; 667
+Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.197:Landroid/os/PersistableBundle; 668
+Landroid/app/ActivityTaskManager;.sInstance:Landroid/util/Singleton; 672
+Landroid/widget/Space; 677
+Landroid/content/pm/ApplicationInfo; 678
+primitive [J 681
+primitive [D 682
+primitive [Z 683
+primitive [F 684
+Landroid/window/SplashScreen; 685
+Landroid/app/servertransaction/LaunchActivityItem; 689
+Landroid/graphics/Matrix; 691
+Landroid/graphics/RectF; 691
+Landroid/os/Parcel$LazyValue; 691
+Landroid/app/FragmentManagerState; 693
+Landroid/view/AbsSavedState$1; 694
+Lcom/android/internal/policy/PhoneWindow$PanelFeatureState$SavedState; 695
+Landroid/net/Uri; 697
+Landroid/view/View;.sNextGeneratedId:Ljava/util/concurrent/atomic/AtomicInteger; 698
+Landroid/media/MediaRouter$WifiDisplayStatusChangedReceiver; 701
+Landroid/media/MediaRouter$VolumeChangeReceiver; 701
+Landroid/media/MediaRouter; 702
+Landroid/hardware/SensorPrivacyManager; 703
+Landroid/os/storage/StorageManager; 704
+Landroid/credentials/CredentialManager; 705
+Landroid/media/tv/tunerresourcemanager/TunerResourceManager; 705
+Landroid/provider/E2eeContactKeysManager; 705
+Landroid/service/persistentdata/PersistentDataBlockManager; 705
+Landroid/os/HardwarePropertiesManager; 705
+Landroid/app/wearable/WearableSensingManager; 705
+Landroid/net/vcn/VcnManager; 705
+Landroid/app/admin/DevicePolicyManager; 705
+Landroid/app/contentsuggestions/ContentSuggestionsManager; 705
+Landroid/view/textservice/TextServicesManager; 705
+Landroid/view/textclassifier/TextClassificationManager; 705
+Landroid/media/session/MediaSessionManager; 705
+Landroid/view/translation/TranslationManager; 705
+Landroid/view/WindowManager; 705
+Landroid/os/SystemConfigManager; 705
+Landroid/hardware/input/InputManager; 705
+Landroid/permission/PermissionControllerManager; 705
+Landroid/app/people/PeopleManager; 705
+Landroid/app/contextualsearch/ContextualSearchManager; 705
+Landroid/os/RecoverySystem; 705
+Landroid/net/wifi/sharedconnectivity/app/SharedConnectivityManager; 705
+Landroid/security/attestationverification/AttestationVerificationManager; 705
+Landroid/view/autofill/AutofillManager; 705
+Landroid/telephony/SubscriptionManager; 705
+Landroid/view/LayoutInflater; 705
+Landroid/net/NetworkPolicyManager; 705
+Landroid/view/contentcapture/ContentCaptureManager; 705
+Landroid/content/pm/PackageManager$NameNotFoundException; 706
+Landroid/hardware/devicestate/DeviceStateManagerGlobal; 707
+Landroid/telecom/TelecomManager; 708
+Landroid/content/pm/ParceledListSlice; 709
+Landroid/app/NotificationChannelGroup; 709
+Landroid/os/vibrator/StepSegment; 710
+Landroid/security/keystore2/KeyStoreCryptoOperationUtils; 711
+Landroid/security/keystore2/AndroidKeyStoreProvider; 712
+Landroid/widget/ProgressBar; 715
+Landroid/animation/LayoutTransition; 716
+Landroid/widget/ImageButton; 717
+Landroid/graphics/drawable/AdaptiveIconDrawable; 718
+Landroid/widget/ActionMenuView; 719
+Landroid/widget/Toolbar; 719
+Landroid/widget/ActionMenuPresenter$OverflowMenuButton; 719
+Lcom/android/internal/widget/ActionBarContainer; 720
+Lcom/android/internal/widget/ActionBarContainer$ActionBarBackgroundDrawable; 720
+Lcom/android/internal/widget/ActionBarContextView; 720
+Lcom/android/internal/widget/ActionBarOverlayLayout; 720
+Landroid/app/Fragment;.sClassMap:Landroid/util/ArrayMap; 721
+Landroid/graphics/drawable/TransitionDrawable; 722
+Landroid/media/MediaDrm$OnEventListener; 723
+Landroid/widget/MediaController$MediaPlayerControl; 724
+Landroid/hardware/camera2/CameraCharacteristics;.SENSOR_INFO_PIXEL_ARRAY_SIZE:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 725
+Landroid/hardware/camera2/CameraCharacteristics;.INFO_SESSION_CONFIGURATION_QUERY_VERSION:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 725
+Landroid/hardware/camera2/CameraCharacteristics;.SENSOR_INFO_ACTIVE_ARRAY_SIZE:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 725
+Landroid/util/Size; 725
+Landroid/hardware/camera2/CameraCharacteristics;.CONTROL_AE_AVAILABLE_MODES:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 725
+Landroid/hardware/camera2/CaptureRequest;.CONTROL_AF_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 725
+Landroid/hardware/camera2/CameraCharacteristics;.LENS_INFO_SHADING_MAP_SIZE:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 725
+Landroid/hardware/camera2/CameraCaptureSession$StateCallback; 725
+Landroid/hardware/camera2/CaptureRequest;.CONTROL_AE_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 725
+Landroid/telephony/ims/ImsService;.CAPABILITIES_LOG_MAP:Ljava/util/Map;.table:[Ljava/lang/Object;.8:Ljava/lang/Long; 725
+Landroid/hardware/camera2/CameraCharacteristics;.CONTROL_AF_AVAILABLE_MODES:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 725
+Landroid/hardware/camera2/CaptureRequest;.CONTROL_AWB_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 725
+Landroid/view/displayhash/DisplayHashManager; 725
+Landroid/hardware/camera2/CaptureResult;.SENSOR_TIMESTAMP:Landroid/hardware/camera2/CaptureResult$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 725
+Landroid/hardware/camera2/CaptureRequest;.CONTROL_AE_EXPOSURE_COMPENSATION:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 725
+Landroid/hardware/camera2/CameraCharacteristics;.CONTROL_AWB_AVAILABLE_MODES:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 725
+Landroid/hardware/camera2/CaptureRequest;.CONTROL_ZOOM_RATIO:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 725
+Landroid/hardware/camera2/marshal/MarshalRegistry;.sMarshalLock:Ljava/lang/Object; 725
+Landroid/hardware/camera2/CaptureRequest;.CONTROL_AE_TARGET_FPS_RANGE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 725
+Landroid/hardware/camera2/CaptureRequest;.CONTROL_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 725
+Landroid/hardware/usb/UsbManager;.FUNCTION_NAME_TO_CODE:Ljava/util/Map;.table:[Ljava/util/HashMap$Node;.13:Ljava/util/HashMap$Node;.next:Ljava/util/HashMap$Node;.next:Ljava/util/HashMap$Node;.value:Ljava/lang/Object; 725
+Landroid/hardware/camera2/CaptureRequest;.CONTROL_ENABLE_ZSL:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 725
+Landroid/hardware/camera2/CameraDevice$StateCallback; 725
+Landroid/hardware/camera2/CameraCharacteristics;.CONTROL_ZOOM_RATIO_RANGE:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 725
+Landroid/hardware/camera2/CameraCharacteristics;.REQUEST_PARTIAL_RESULT_COUNT:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 725
+Landroid/hardware/camera2/CameraCaptureSession$CaptureCallback; 725
+Landroid/hardware/camera2/CameraCharacteristics;.FLASH_INFO_AVAILABLE:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 726
+Landroid/text/format/DateUtils; 727
+Landroid/security/IKeyChainService; 728
+Landroid/app/usage/UsageStats; 729
+Landroid/app/usage/CacheQuotaHint; 730
+Landroid/service/watchdog/ExplicitHealthCheckService$PackageConfig; 730
+Landroid/os/BaseBundle; 731
+Landroid/view/ViewTreeObserver$OnWindowFocusChangeListener; 732
+Landroid/graphics/ColorMatrix;.dexCache:Ljava/lang/Object; 733
+Landroid/view/InsetsAnimationThread; 736
+Lcom/android/internal/jank/InteractionJankMonitor$InstanceHolder; 737
+Lcom/android/internal/jank/InteractionJankMonitor; 737
+Landroid/graphics/Bitmap;.sAllBitmaps:Ljava/util/WeakHashMap;.queue:Ljava/lang/ref/ReferenceQueue; 738
+Lcom/android/internal/os/BackgroundThread; 739
+Landroid/app/PendingIntent; 740
+Landroid/security/net/config/UserCertificateSource$NoPreloadHolder; 741
+Lorg/apache/http/params/HttpParams; 742
+Landroid/app/Service; 745
+Landroid/content/ComponentCallbacks2; 746
+Landroid/content/ComponentCallbacks; 747
+Landroid/widget/AbsListView; 751
+Landroid/widget/ListView; 752
+Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.1:Ljava/util/WeakHashMap$Entry; 753
+Landroid/widget/TextView;.TEMP_RECTF:Landroid/graphics/RectF; 756
+Landroid/app/Activity; 757
+Landroid/text/MeasuredParagraph;.sPool:Landroid/util/Pools$SynchronizedPool;.mPool:[Ljava/lang/Object; 758
+Landroid/text/MeasuredParagraph;.sPool:Landroid/util/Pools$SynchronizedPool; 758
+Lcom/android/internal/infra/AndroidFuture; 759
+Landroid/widget/TextView;.TEMP_POSITION:[F 760
+Landroid/text/method/MetaKeyKeyListener;.SYM:Ljava/lang/Object; 761
+Landroid/view/inputmethod/SelectRangeGesture; 761
+Landroid/text/method/MetaKeyKeyListener;.CAP:Ljava/lang/Object; 761
+Landroid/text/method/MetaKeyKeyListener;.ALT:Ljava/lang/Object; 761
+Landroid/view/inputmethod/DeleteGesture; 761
+Landroid/view/inputmethod/SelectGesture; 761
+Landroid/text/method/MetaKeyKeyListener;.SELECTING:Ljava/lang/Object; 761
+Landroid/view/inputmethod/DeleteRangeGesture; 761
+Landroid/view/inputmethod/BaseInputConnection;.COMPOSING:Ljava/lang/Object; 762
+Landroid/text/method/DigitsKeyListener;.sLocaleInstanceCache:Ljava/util/HashMap; 763
+Landroid/app/StackTrace; 764
+Landroid/opengl/EGLConfig; 768
+Landroid/widget/PopupWindow$PopupDecorView; 769
+Landroid/widget/PopupWindow$PopupBackgroundView; 769
+Landroid/view/ViewStub$OnInflateListener; 770
+Landroid/opengl/GLSurfaceView;.sGLThreadManager:Landroid/opengl/GLSurfaceView$GLThreadManager; 772
+Landroid/opengl/GLSurfaceView$Renderer; 773
+Landroid/content/ContentValues; 774
+Landroid/graphics/Point; 774
+Landroid/os/Build; 776
+Landroid/widget/ScrollView; 779
+Landroid/os/FileObserver; 780
+Lcom/android/internal/os/RuntimeInit$KillApplicationHandler; 781
+Landroid/app/ResourcesManager; 782
+Landroid/content/res/ResourcesKey; 782
+Landroid/app/backup/BackupManager; 783
+Lcom/android/internal/util/LatencyTracker; 784
+Lcom/android/internal/util/LatencyTracker$SLatencyTrackerHolder; 784
+Landroid/app/Application$ActivityLifecycleCallbacks; 785
+Landroid/os/Messenger; 786
+Landroid/widget/ProgressBar$SavedState; 789
+Lcom/android/internal/telephony/WspTypeDecoder;.WELL_KNOWN_PARAMETERS:Ljava/util/HashMap;.table:[Ljava/util/HashMap$Node;.25:Ljava/util/HashMap$Node;.key:Ljava/lang/Object; 792
+Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.11:Ljava/util/WeakHashMap$Entry;.referent:Ljava/lang/Object;.mSkips:[J 793
+Landroid/os/UserHandle; 794
+Landroid/hardware/usb/UsbManager;.FUNCTION_NAME_TO_CODE:Ljava/util/Map;.table:[Ljava/util/HashMap$Node;.13:Ljava/util/HashMap$Node;.value:Ljava/lang/Object; 796
+Landroid/graphics/drawable/RotateDrawable; 796
+Landroid/database/sqlite/SQLiteException; 797
+Landroid/webkit/WebViewFactory;.sProviderLock:Ljava/lang/Object; 798
+Landroid/content/res/AssetManager$AssetInputStream; 799
+Landroid/util/SparseIntArray; 800
+Landroid/database/ContentObserver; 800
+Landroid/icu/text/NFRule;.ZERO:Ljava/lang/Long; 802
+Landroid/view/View$BaseSavedState; 803
+Landroid/app/ActivityThread$ReceiverData; 804
+Landroid/icu/util/ULocale$AliasReplacer; 805
+Landroid/text/method/TextKeyListener;.ACTIVE:Ljava/lang/Object; 806
+Landroid/text/method/PasswordTransformationMethod; 807
+Landroid/speech/tts/TextToSpeech$Connection$SetupConnectionAsyncTask; 808
+Landroid/speech/tts/TextToSpeech$OnInitListener; 809
+Landroid/icu/util/CodePointMap$RangeOption;.NORMAL:Landroid/icu/util/CodePointMap$RangeOption;.name:Ljava/lang/String; 810
+Lcom/android/internal/telephony/cdnr/CarrierDisplayNameResolver;.EF_SOURCE_PRIORITY:Ljava/util/List;.a:[Ljava/lang/Object;.5:Ljava/lang/Integer; 810
+Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.5:Ljava/util/WeakHashMap$Entry; 811
+Landroid/window/IWindowContainerToken$Stub$Proxy; 812
+Landroid/telephony/SignalStrength; 813
+Landroid/app/LoadedApk$WarningContextClassLoader; 814
+Landroid/widget/ViewAnimator; 814
+Landroid/util/proto/ProtoStream;.FIELD_TYPE_NAMES:[Ljava/lang/String;.10:Ljava/lang/String; 814
+Lcom/android/internal/telephony/euicc/EuiccController;.EXTRA_OPERATION:Ljava/lang/String; 814
+Lorg/apache/http/conn/ssl/SSLSocketFactory$NoPreloadHolder; 814
+Ljavax/sip/header/PriorityHeader;.NORMAL:Ljava/lang/String; 814
+Landroid/hardware/camera2/CameraCharacteristics;.INFO_DEVICE_STATE_ORIENTATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 816
+Landroid/hardware/camera2/CameraCharacteristics;.INFO_SUPPORTED_HARDWARE_LEVEL:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 816
+Landroid/hardware/camera2/CameraCharacteristics;.SENSOR_ORIENTATION:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 816
+Landroid/hardware/camera2/CameraCharacteristics;.SENSOR_INFO_PHYSICAL_SIZE:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 817
+Landroid/util/Log; 818
+Landroid/text/style/StyleSpan; 819
+Landroid/security/keystore/KeyGenParameterSpec; 819
+Landroid/telephony/TelephonyCallback$DataEnabledListener; 820
+Landroid/icu/impl/ZoneMeta;.REGION_CACHE:Landroid/icu/impl/ICUCache; 822
+Lcom/android/internal/telephony/WspTypeDecoder;.WELL_KNOWN_MIME_TYPES:Ljava/util/HashMap;.table:[Ljava/util/HashMap$Node;.81:Ljava/util/HashMap$Node;.key:Ljava/lang/Object; 823
+Landroid/text/style/QuoteSpan; 824
+Landroid/text/HtmlToSpannedConverter$Strikethrough; 824
+Landroid/text/HtmlToSpannedConverter$Background; 824
+Landroid/text/HtmlToSpannedConverter$Alignment; 824
+Landroid/text/HtmlToSpannedConverter$Foreground; 824
+Landroid/text/method/QwertyKeyListener; 826
+Landroid/graphics/Path$Op; 826
+Landroid/view/OrientationEventListener; 832
+Landroid/animation/PropertyValuesHolder$IntPropertyValuesHolder;.sJNISetterPropertyMap:Ljava/util/HashMap; 834
+Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.11:Ljava/util/WeakHashMap$Entry; 835
+Landroid/view/accessibility/AccessibilityManager;.sInstanceSync:Ljava/lang/Object; 836
+Lcom/android/internal/listeners/ListenerTransport; 837
diff --git a/core/api/current.txt b/core/api/current.txt
index 354e26b..ea039a7 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -59034,7 +59034,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 +60099,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 9d63be7..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;
                 }
             }
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/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/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/content/Context.java b/core/java/android/content/Context.java
index 8365840..9aebfc8 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -6676,6 +6676,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/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index e370e85..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"
@@ -386,4 +403,7 @@
   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/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/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/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 e79b8f3..de39847 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -524,19 +524,12 @@
 
     /**
      * @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
      */
-    public static final int IME_VISIBLE_IMPERCEPTIBLE = 0x8;
+    public static final int IME_VISIBLE_IMPERCEPTIBLE = 0x4;
 
     // Min and max values for back disposition.
     private static final int BACK_DISPOSITION_MIN = BACK_DISPOSITION_DEFAULT;
@@ -3125,7 +3118,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) {
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/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/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 9d7d9ae..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
@@ -110,3 +103,13 @@
         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/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 224379b..fc6c2e8 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -25,7 +25,6 @@
 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.Condition.STATE_TRUE;
 import static android.service.notification.SystemZenRules.PACKAGE_ANDROID;
 import static android.service.notification.ZenAdapters.peopleTypeToPrioritySenders;
 import static android.service.notification.ZenAdapters.prioritySendersToPeopleType;
@@ -76,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;
@@ -1074,7 +1074,7 @@
                         rt.manualRule.type = AutomaticZenRule.TYPE_OTHER;
                         rt.manualRule.condition = new Condition(
                                 rt.manualRule.conditionId != null ? rt.manualRule.conditionId
-                                        : Uri.EMPTY, "", STATE_TRUE);
+                                        : Uri.EMPTY, "", Condition.STATE_TRUE);
                     }
                 }
                 return rt;
@@ -2540,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
@@ -2579,6 +2603,15 @@
         // 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() { }
 
@@ -2620,6 +2653,7 @@
                 if (Flags.modesUi()) {
                     disabledOrigin = source.readInt();
                     legacySuppressedEffects = source.readInt();
+                    conditionOverride = source.readInt();
                 }
             }
         }
@@ -2698,6 +2732,7 @@
                 if (Flags.modesUi()) {
                     dest.writeInt(disabledOrigin);
                     dest.writeInt(legacySuppressedEffects);
+                    dest.writeInt(conditionOverride);
                 }
             }
         }
@@ -2708,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)
@@ -2753,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) {
@@ -2763,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());
@@ -2816,7 +2871,8 @@
                 if (Flags.modesUi()) {
                     finalEquals = finalEquals
                             && other.disabledOrigin == disabledOrigin
-                            && other.legacySuppressedEffects == legacySuppressedEffects;
+                            && other.legacySuppressedEffects == legacySuppressedEffects
+                            && other.conditionOverride == conditionOverride;
                 }
             }
 
@@ -2832,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,
@@ -2858,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/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 e2c62ab..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
      */
@@ -28672,14 +28668,6 @@
         }
 
         int privFlags = computeHapticFeedbackPrivateFlags();
-        if (Flags.useVibratorHapticFeedback()) {
-            if (!mAttachInfo.canPerformHapticFeedback()) {
-                return false;
-            }
-            getSystemVibrator().performHapticFeedback(feedbackConstant,
-                    "View#performHapticFeedback", flags, privFlags);
-            return true;
-        }
         return mAttachInfo.mRootCallbacks.performHapticFeedback(feedbackConstant, flags, privFlags);
     }
 
@@ -28711,16 +28699,10 @@
                 feedbackConstant, inputDeviceId, inputSource, flags, privFlags);
     }
 
-    private Vibrator getSystemVibrator() {
-        if (mVibrator != null) {
-            return mVibrator;
-        }
-        return mVibrator = mContext.getSystemService(Vibrator.class);
-    }
-
     private boolean isPerformHapticFeedbackSuppressed(int feedbackConstant, int flags) {
         if (feedbackConstant == HapticFeedbackConstants.NO_HAPTICS
-                || mAttachInfo == null) {
+                || mAttachInfo == null
+                || mAttachInfo.mSession == null) {
             return true;
         }
         //noinspection SimplifiableIfStatement
@@ -32342,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 6e85569..1c0700f 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -363,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
@@ -2828,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;
     }
 
     /**
@@ -9675,17 +9673,9 @@
             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
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/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..a6ae948 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"
diff --git a/core/java/com/android/internal/accessibility/common/MagnificationConstants.java b/core/java/com/android/internal/accessibility/common/MagnificationConstants.java
index 67e587e..2db3e65 100644
--- a/core/java/com/android/internal/accessibility/common/MagnificationConstants.java
+++ b/core/java/com/android/internal/accessibility/common/MagnificationConstants.java
@@ -33,7 +33,7 @@
     /** Minimum supported value for magnification scale. */
     public static final float SCALE_MIN_VALUE = 1.0f;
 
-    /** Maximum supported value for magnification scale. Default of 20.0. */
+    /** Maximum supported value for magnification scale. Default of 8.0. */
     public static final float SCALE_MAX_VALUE =
-            Float.parseFloat(SystemProperties.get("ro.config.max_magnification_scale", "20.0"));
+            Float.parseFloat(SystemProperties.get("ro.config.max_magnification_scale", "8.0"));
 }
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..921363c 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
@@ -108,7 +108,6 @@
      * @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
      */
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..e0c90d8 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -332,6 +332,8 @@
     }
 
     private void onTracingFlush() {
+        Log.d(LOG_TAG, "Executing onTracingFlush");
+
         final ExecutorService loggingService;
         try {
             mBackgroundServiceLock.lock();
@@ -352,15 +354,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 +396,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(
@@ -770,6 +778,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 +810,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 +848,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/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/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 03b5143a..db6fb23 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -1023,10 +1023,22 @@
     parseCompilerOption("dalvik.vm.image-dex2oat-filter", dex2oatImageCompilerFilterBuf,
                         "--compiler-filter=", "-Ximage-compiler-option");
 
-    // If there is a dirty-image-objects file, push it.
-    if (hasFile("/system/etc/dirty-image-objects")) {
-        addOption("-Ximage-compiler-option");
-        addOption("--dirty-image-objects=/system/etc/dirty-image-objects");
+    // If there are dirty-image-objects files, push them.
+    const char* dirty_image_objects_options[] = {
+            // Currently, there are two dirty-image-objects files: one for
+            // ART module, one for framework.
+            "--dirty-image-objects=/system/etc/dirty-image-objects.txt",
+            "--dirty-image-objects=/apex/com.android.art/etc/dirty-image-objects.txt",
+            // Allow old filename (without .txt) for backward compatibility.
+            "--dirty-image-objects=/system/etc/dirty-image-objects",
+    };
+    for (const char* option : dirty_image_objects_options) {
+        // Get the file path by finding the first '/' and check if
+        // this file exists.
+        if (hasFile(strchr(option, '/'))) {
+            addOption("-Ximage-compiler-option");
+            addOption(option);
+        }
     }
 
     parseCompilerOption("dalvik.vm.image-dex2oat-threads", dex2oatThreadsImageBuf, "-j",
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/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/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/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index b990f24..e240a08 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -1434,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..25e7107 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -2725,15 +2725,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/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/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/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 0a8166f..df5f1e4 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -139,6 +139,10 @@
     <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>
     <!-- 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/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index f14f419..7275c64 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;
@@ -837,8 +844,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 +864,10 @@
             // start post animation
             dispatchOnBackInvoked(mActiveCallback);
         } else {
+            if (migrateBackToTransition
+                    && mBackTransitionHandler.mPrepareOpenTransition != null) {
+                mBackTransitionHandler.createClosePrepareTransition();
+            }
             tryDispatchOnBackCancelled(mActiveCallback);
         }
     }
@@ -960,6 +972,7 @@
         mShellBackAnimationRegistry.resetDefaultCrossActivity();
         cancelLatencyTracking();
         mReceivedNullNavigationInfo = false;
+        mBackTransitionHandler.mLastTrigger = triggerBack;
         if (mBackNavigationInfo != null) {
             mPreviousNavigationType = mBackNavigationInfo.getType();
             mBackNavigationInfo.onBackNavigationFinished(triggerBack);
@@ -1128,12 +1141,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 +1177,8 @@
                 mFinishOpenTransitionCallback.onTransitionFinished(null);
                 mFinishOpenTransitionCallback = null;
             }
-            mOpeningRunning = false;
+            mOpenTransitionInfo = null;
+            mPrepareOpenTransition = null;
         }
 
         private void applyAndFinish(@NonNull SurfaceControl.Transaction st,
@@ -1178,21 +1198,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_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
                     && !isGestureBackTransition(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 +1242,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 (!isGestureBackTransition(info) || shouldCancelAnimation(info)
+                    || !mCloseTransitionRequested) {
+                if (mPrepareOpenTransition != null) {
                     applyFinishOpenTransition();
                 }
                 if (mQueuedTransition != null) {
@@ -1222,7 +1382,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 +1393,56 @@
             }
         }
 
+        // Cancel close animation if something happen unexpected, let another handler to handle
+        private boolean shouldCancelAnimation(@NonNull TransitionInfo info) {
+            final boolean noCloseAllowed =
+                    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.
@@ -1247,9 +1457,11 @@
             }
 
             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,13 +1471,14 @@
                         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;
         }
 
@@ -1288,6 +1501,10 @@
                 @NonNull SurfaceControl.Transaction st,
                 @NonNull SurfaceControl.Transaction ft,
                 @NonNull Transitions.TransitionFinishCallback finishCallback) {
+            if (info.getType() == WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION
+                    || !mCloseTransitionRequested) {
+                return false;
+            }
             SurfaceControl openingLeash = null;
             SurfaceControl closingLeash = null;
             for (int i = mApps.length - 1; i >= 0; --i) {
@@ -1325,7 +1542,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 +1591,36 @@
             }
         }
     }
+
+    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 b6da761..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;
@@ -421,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;
             }
         }
 
@@ -448,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/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..b7834db 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
@@ -32,6 +32,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewOutlineProvider;
+import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.FrameLayout;
 
 import com.android.wm.shell.R;
@@ -188,12 +189,30 @@
 
             // Handle view needs to draw on top of task view.
             bringChildToFront(mHandleView);
+
+            mHandleView.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_expand_menu)));
+                }
+            });
         }
         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
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/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/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/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/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/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 87dc16a..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
@@ -2243,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.
@@ -2258,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
@@ -2331,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.
@@ -2348,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/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/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/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/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/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/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index d184f64..1217b47 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -42,6 +42,9 @@
 constexpr bool initialize_gl_always() {
     return false;
 }
+constexpr bool resample_gainmap_regions() {
+    return false;
+}
 }  // namespace hwui_flags
 #endif
 
@@ -100,6 +103,7 @@
 
 bool Properties::clipSurfaceViews = false;
 bool Properties::hdr10bitPlus = false;
+bool Properties::resampleGainmapRegions = false;
 
 int Properties::timeoutMultiplier = 1;
 
@@ -175,6 +179,8 @@
     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);
 
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index e264642..73e80ce 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -342,6 +342,7 @@
 
     static bool clipSurfaceViews;
     static bool hdr10bitPlus;
+    static bool resampleGainmapRegions;
 
     static int timeoutMultiplier;
 
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
index cd3ae53..13c0b00 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -97,3 +97,13 @@
   description: "Initialize GL even when HWUI is set to use Vulkan. This improves app startup time for apps using GL."
   bug: "335172671"
 }
+
+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/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/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/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/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/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 22e6133..f7492cf 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
@@ -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
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 88497a3..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;
 
@@ -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/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/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/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/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/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/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/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index e3c39d0..9046d4e 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"
@@ -1197,6 +1186,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."
@@ -1260,4 +1260,14 @@
    namespace: "systemui"
    description: "Adding haptic component infrastructure to sliders in Compose."
    bug: "341968766"
+}
+
+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."
+  bug: "355389014"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
 }
\ No newline at end of file
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/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..b65b471 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
@@ -160,6 +160,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
@@ -269,7 +270,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) {
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/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/ShortcutsBesideUdfpsBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
deleted file mode 100644
index a5e120c..0000000
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
+++ /dev/null
@@ -1,259 +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 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(
-        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,
-            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(
-                                    smartSpacePaddingTop = viewModel::getSmartSpacePaddingTop,
-                                    modifier =
-                                        Modifier.graphicsLayer {
-                                            translationX = unfoldTranslations.start
-                                        },
-                                )
-                            }
-                            if (isShadeLayoutWide) {
-                                with(notificationSection) {
-                                    Notifications(
-                                        areNotificationsVisible = areNotificationsVisible,
-                                        isShadeLayoutWide = isShadeLayoutWide,
-                                        burnInParams = null,
-                                        modifier =
-                                            Modifier.fillMaxWidth(0.5f)
-                                                .fillMaxHeight()
-                                                .align(alignment = Alignment.TopEnd)
-                                    )
-                                }
-                            }
-                        }
-                        if (!isShadeLayoutWide) {
-                            with(notificationSection) {
-                                Notifications(
-                                    areNotificationsVisible = areNotificationsVisible,
-                                    isShadeLayoutWide = isShadeLayoutWide,
-                                    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/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..66be7bc 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
@@ -20,15 +20,19 @@
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.animation.scene.UserAction
 import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.battery.BatteryMeterViewController
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.ui.composable.LockscreenContent
-import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeSceneViewModel
+import com.android.systemui.lifecycle.rememberViewModel
+import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeSceneActionsViewModel
+import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeSceneContentViewModel
 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 +54,10 @@
 class NotificationsShadeScene
 @Inject
 constructor(
-    sceneViewModel: NotificationsShadeSceneViewModel,
-    private val overlayShadeViewModel: OverlayShadeViewModel,
-    private val shadeHeaderViewModel: ShadeHeaderViewModel,
+    private val contentViewModelFactory: NotificationsShadeSceneContentViewModel.Factory,
+    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,21 +69,32 @@
 
     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(
         modifier: Modifier,
     ) {
+        val viewModel = rememberViewModel { contentViewModelFactory.create() }
+        val isEmptySpaceClickable by viewModel.isEmptySpaceClickable.collectAsStateWithLifecycle()
+
         OverlayShade(
             modifier = modifier,
-            viewModel = overlayShadeViewModel,
+            viewModelFactory = overlayShadeViewModelFactory,
             lockscreenContent = lockscreenContent,
         ) {
             Column {
                 ExpandedShadeHeader(
-                    viewModel = shadeHeaderViewModel,
+                    viewModelFactory = shadeHeaderViewModelFactory,
                     createTintedIconManager = tintedIconManagerFactory::create,
                     createBatteryMeterViewController = batteryMeterViewControllerFactory::create,
                     statusBarIconController = statusBarIconController,
@@ -94,6 +110,8 @@
                     shouldFillMaxSize = false,
                     shouldReserveSpaceForNavBar = false,
                     shadeMode = ShadeMode.Dual,
+                    onEmptySpaceClick =
+                        viewModel::onEmptySpaceClicked.takeIf { isEmptySpaceClickable },
                     modifier = Modifier.fillMaxWidth(),
                 )
 
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/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/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 77b48d3..0e3fcf4 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
@@ -145,7 +147,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 +160,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 +177,7 @@
     ) =
         ShadeScene(
             notificationStackScrollView.get(),
-            viewModel = viewModel,
+            viewModel = rememberViewModel { contentViewModelFactory.create() },
             notificationsPlaceholderViewModel = notificationsPlaceholderViewModel,
             createTintedIconManager = tintedIconManagerFactory::create,
             createBatteryMeterViewController = batteryMeterViewControllerFactory::create,
@@ -196,7 +203,7 @@
 @Composable
 private fun SceneScope.ShadeScene(
     notificationStackScrollView: NotificationScrollView,
-    viewModel: ShadeSceneViewModel,
+    viewModel: ShadeSceneContentViewModel,
     notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel,
     createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
     createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
@@ -242,7 +249,7 @@
 @Composable
 private fun SceneScope.SingleShade(
     notificationStackScrollView: NotificationScrollView,
-    viewModel: ShadeSceneViewModel,
+    viewModel: ShadeSceneContentViewModel,
     notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel,
     createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
     createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
@@ -261,7 +268,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 =
@@ -299,9 +306,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 +316,7 @@
                                     },
                         ) {
                             CollapsedShadeHeader(
-                                viewModel = viewModel.shadeHeaderViewModel,
+                                viewModelFactory = viewModel.shadeHeaderViewModelFactory,
                                 createTintedIconManager = createTintedIconManager,
                                 createBatteryMeterViewController = createBatteryMeterViewController,
                                 statusBarIconController = statusBarIconController,
@@ -361,6 +368,8 @@
                             maxScrimTop = { maxNotifScrimTop.value },
                             shadeMode = ShadeMode.Single,
                             shouldPunchHoleBehindScrim = shouldPunchHoleBehindScrim,
+                            onEmptySpaceClick =
+                                viewModel::onEmptySpaceClicked.takeIf { isEmptySpaceClickable },
                         )
                     },
                 )
@@ -407,7 +416,7 @@
 @Composable
 private fun SceneScope.SplitShade(
     notificationStackScrollView: NotificationScrollView,
-    viewModel: ShadeSceneViewModel,
+    viewModel: ShadeSceneContentViewModel,
     notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel,
     createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
     createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
@@ -468,8 +477,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,6 +492,7 @@
         onDispose { notificationsPlaceholderViewModel.setAlphaForBrightnessMirror(1f) }
     }
 
+    val isEmptySpaceClickable by viewModel.isEmptySpaceClickable.collectAsStateWithLifecycle()
     val isMediaVisible by viewModel.isMediaVisible.collectAsStateWithLifecycle()
 
     val brightnessMirrorShowingModifier = Modifier.graphicsLayer { alpha = contentAlpha }
@@ -503,7 +515,7 @@
             modifier = Modifier.fillMaxSize(),
         ) {
             CollapsedShadeHeader(
-                viewModel = viewModel.shadeHeaderViewModel,
+                viewModelFactory = viewModel.shadeHeaderViewModelFactory,
                 createTintedIconManager = createTintedIconManager,
                 createBatteryMeterViewController = createBatteryMeterViewController,
                 statusBarIconController = statusBarIconController,
@@ -522,7 +534,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
@@ -591,6 +603,8 @@
                     shouldPunchHoleBehindScrim = false,
                     shouldReserveSpaceForNavBar = false,
                     shadeMode = ShadeMode.Split,
+                    onEmptySpaceClick =
+                        viewModel::onEmptySpaceClicked.takeIf { isEmptySpaceClickable },
                     modifier =
                         Modifier.weight(1f)
                             .fillMaxHeight()
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/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/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/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/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/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/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/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 f82a7b8..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
@@ -85,7 +85,9 @@
                 GestureEduModel(
                     signalCount = 2,
                     educationShownCount = 1,
-                    lastShortcutTriggeredTime = kosmos.fakeEduClock.instant()
+                    lastShortcutTriggeredTime = kosmos.fakeEduClock.instant(),
+                    lastEducationTime = kosmos.fakeEduClock.instant(),
+                    usageSessionStartTime = kosmos.fakeEduClock.instant(),
                 )
             underTest.updateGestureEduModel(BACK) { newModel }
             val model by collectLastValue(underTest.readGestureEduModelFlow(BACK))
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 1b4632a..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
@@ -22,9 +22,15 @@
 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
@@ -37,6 +43,7 @@
     private val testScope = kosmos.testScope
     private val contextualEduInteractor = kosmos.contextualEducationInteractor
     private val underTest: KeyboardTouchpadEduInteractor = kosmos.keyboardTouchpadEduInteractor
+    private val eduClock = kosmos.fakeEduClock
 
     @Before
     fun setup() {
@@ -46,12 +53,32 @@
     @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 {
             contextualEduInteractor.incrementSignalCount(BACK)
@@ -64,11 +91,30 @@
         testScope.runTest {
             val model by collectLastValue(underTest.educationTriggered)
             contextualEduInteractor.updateShortcutTriggerTime(BACK)
-            tryTriggeringEducation(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) {
             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/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/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 72a5cd1..66e45ab 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -63,8 +63,10 @@
 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.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
@@ -147,7 +149,8 @@
         )
     }
 
-    private lateinit var shadeSceneViewModel: ShadeSceneViewModel
+    private lateinit var shadeSceneContentViewModel: ShadeSceneContentViewModel
+    private lateinit var shadeSceneActionsViewModel: ShadeSceneActionsViewModel
 
     private val powerInteractor by lazy { kosmos.powerInteractor }
 
@@ -186,12 +189,15 @@
         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)
@@ -220,8 +226,8 @@
     @Test
     fun swipeUpOnLockscreen_enterCorrectPin_unlocksDevice() =
         testScope.runTest {
-            val destinationScenes by collectLastValue(lockscreenSceneActionsViewModel.actions)
-            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,
@@ -240,8 +246,8 @@
         testScope.runTest {
             setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
 
-            val destinationScenes by collectLastValue(lockscreenSceneActionsViewModel.actions)
-            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,
@@ -251,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)
@@ -260,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(
@@ -271,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)
 
@@ -288,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(
@@ -346,8 +352,8 @@
     fun swipeUpOnLockscreenWhileUnlocked_dismissesLockscreen() =
         testScope.runTest {
             unlockDevice()
-            val destinationScenes by collectLastValue(lockscreenSceneActionsViewModel.actions)
-            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)
         }
 
@@ -368,8 +374,8 @@
     fun dismissingIme_whileOnPasswordBouncer_navigatesToLockscreen() =
         testScope.runTest {
             setAuthMethod(AuthenticationMethodModel.Password)
-            val destinationScenes by collectLastValue(lockscreenSceneActionsViewModel.actions)
-            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,
@@ -386,8 +392,8 @@
     fun bouncerActionButtonClick_opensEmergencyServicesDialer() =
         testScope.runTest {
             setAuthMethod(AuthenticationMethodModel.Password)
-            val destinationScenes by collectLastValue(lockscreenSceneActionsViewModel.actions)
-            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)
 
@@ -406,8 +412,8 @@
         testScope.runTest {
             setAuthMethod(AuthenticationMethodModel.Password)
             startPhoneCall()
-            val destinationScenes by collectLastValue(lockscreenSceneActionsViewModel.actions)
-            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/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/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 2fb9e1e0..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)
@@ -175,7 +179,7 @@
     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/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/util/settings/SettingsProxyExtTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/SettingsProxyExtTest.kt
new file mode 100644
index 0000000..e281894
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/SettingsProxyExtTest.kt
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.util.settings
+
+import android.database.ContentObserver
+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.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+
+/** Tests for [SettingsProxyExt]. */
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+class SettingsProxyExtTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    @Mock lateinit var settingsProxy: SettingsProxy
+    @Mock lateinit var userSettingsProxy: UserSettingsProxy
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_SETTINGS_EXT_REGISTER_CONTENT_OBSERVER_ON_BG_THREAD)
+    fun observeFlow_bgFlagEnabled_settingsProxy_registerContentObserverInvoked() =
+        testScope.runTest {
+            val unused by collectLastValue(settingsProxy.observerFlow(SETTING_1, SETTING_2))
+            runCurrent()
+            verify(settingsProxy, times(2))
+                .registerContentObserver(any<String>(), any<ContentObserver>())
+        }
+
+    @Test
+    @DisableFlags(Flags.FLAG_SETTINGS_EXT_REGISTER_CONTENT_OBSERVER_ON_BG_THREAD)
+    fun observeFlow_bgFlagDisabled_multipleSettings_SettingsProxy_registerContentObserverInvoked() =
+        testScope.runTest {
+            val unused by collectLastValue(settingsProxy.observerFlow(SETTING_1, SETTING_2))
+            runCurrent()
+            verify(settingsProxy, times(2))
+                .registerContentObserverSync(any<String>(), any<ContentObserver>())
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_SETTINGS_EXT_REGISTER_CONTENT_OBSERVER_ON_BG_THREAD)
+    fun observeFlow_bgFlagEnabled_channelClosed_settingsProxy_unregisterContentObserverInvoked() =
+        testScope.runTest {
+            val job = Job()
+            val unused by
+                collectLastValue(settingsProxy.observerFlow(SETTING_1, SETTING_2), context = job)
+            runCurrent()
+            job.cancel()
+            runCurrent()
+            verify(settingsProxy).unregisterContentObserverAsync(any<ContentObserver>())
+        }
+
+    @Test
+    @DisableFlags(Flags.FLAG_SETTINGS_EXT_REGISTER_CONTENT_OBSERVER_ON_BG_THREAD)
+    fun observeFlow_bgFlagDisabled_channelClosed_settingsProxy_unregisterContentObserverInvoked() =
+        testScope.runTest {
+            val job = Job()
+            val unused by
+                collectLastValue(settingsProxy.observerFlow(SETTING_1, SETTING_2), context = job)
+            runCurrent()
+            job.cancel()
+            runCurrent()
+            verify(settingsProxy).unregisterContentObserverSync(any<ContentObserver>())
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_SETTINGS_EXT_REGISTER_CONTENT_OBSERVER_ON_BG_THREAD)
+    fun observeFlow_bgFlagEnabled_userSettingsProxy_registerContentObserverForUserInvoked() =
+        testScope.runTest {
+            val unused by
+                collectLastValue(userSettingsProxy.observerFlow(userId = 0, SETTING_1, SETTING_2))
+            runCurrent()
+            verify(userSettingsProxy, times(2))
+                .registerContentObserverForUser(any<String>(), any<ContentObserver>(), any<Int>())
+        }
+
+    @Test
+    @DisableFlags(Flags.FLAG_SETTINGS_EXT_REGISTER_CONTENT_OBSERVER_ON_BG_THREAD)
+    fun observeFlow_bgFlagDisabled_userSettingsProxy_registerContentObserverForUserInvoked() =
+        testScope.runTest {
+            val unused by
+                collectLastValue(userSettingsProxy.observerFlow(userId = 0, SETTING_1, SETTING_2))
+            runCurrent()
+            verify(userSettingsProxy, times(2))
+                .registerContentObserverForUserSync(
+                    any<String>(),
+                    any<ContentObserver>(),
+                    any<Int>()
+                )
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_SETTINGS_EXT_REGISTER_CONTENT_OBSERVER_ON_BG_THREAD)
+    fun observeFlow_bgFlagEnabled_channelClosed_userSettingsProxy_unregisterContentObserverInvoked() =
+        testScope.runTest {
+            val job = Job()
+            val unused by
+                collectLastValue(
+                    userSettingsProxy.observerFlow(userId = 0, SETTING_1, SETTING_2),
+                    context = job
+                )
+            runCurrent()
+            job.cancel()
+            runCurrent()
+            verify(userSettingsProxy).unregisterContentObserverAsync(any<ContentObserver>())
+        }
+
+    @Test
+    @DisableFlags(Flags.FLAG_SETTINGS_EXT_REGISTER_CONTENT_OBSERVER_ON_BG_THREAD)
+    fun observeFlow_bgFlagDisabled_channelClosed_userSettingsProxy_unregisterContentObserverInvoked() =
+        testScope.runTest {
+            val job = Job()
+            val unused by
+                collectLastValue(
+                    userSettingsProxy.observerFlow(userId = 0, SETTING_1, SETTING_2),
+                    context = job
+                )
+            runCurrent()
+            job.cancel()
+            runCurrent()
+            verify(userSettingsProxy).unregisterContentObserverSync(any<ContentObserver>())
+        }
+
+    private companion object {
+        val SETTING_1 = "settings_1"
+        val SETTING_2 = "settings_2"
+    }
+}
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/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/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/strings.xml b/packages/SystemUI/res/values/strings.xml
index 4bc4692..a5fd5b9 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -971,8 +971,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>
@@ -1367,13 +1367,18 @@
 
     <!-- Media projection that launched from 1P/3P apps -->
     <!-- 1P/3P app media projection permission dialog title. [CHAR LIMIT=NONE] -->
-    <string name="media_projection_entry_app_permission_dialog_title">Start recording or casting with <xliff:g id="app_seeking_permission" example="Meet">%s</xliff:g>?</string>
+    <string name="media_projection_entry_app_permission_dialog_title">Share your screen with <xliff:g id="app_seeking_permission" example="Meet">%s</xliff:g>?</string>
+
+    <!-- 1P/3P app media projection permission option for capturing just a single app [CHAR LIMIT=50] -->
+    <string name="media_projection_entry_app_permission_dialog_option_text_single_app">Share one app</string>
+    <!-- 1P/3P app media projection permission option for capturing the whole screen [CHAR LIMIT=50] -->
+    <string name="media_projection_entry_app_permission_dialog_option_text_entire_screen">Share entire screen</string>
     <!-- 1P/3P app media projection permission warning for capturing the whole screen. [CHAR LIMIT=350] -->
-    <string name="media_projection_entry_app_permission_dialog_warning_entire_screen">When you’re sharing, recording, or casting, <xliff:g id="app_seeking_permission" example="Meet">%s</xliff:g> 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="media_projection_entry_app_permission_dialog_warning_entire_screen">When you’re sharing your entire screen, anything on your screen is visible to <xliff:g id="app_seeking_permission" example="Meet">%s</xliff:g>. So be careful with things like passwords, payment details, messages, photos, and audio and video.</string>
     <!-- 1P/3P app media projection permission warning for capturing an app. [CHAR LIMIT=350] -->
-    <string name="media_projection_entry_app_permission_dialog_warning_single_app">When you’re sharing, recording, or casting an app, <xliff:g id="app_seeking_permission" example="Meet">%s</xliff:g> 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>
+    <string name="media_projection_entry_app_permission_dialog_warning_single_app">When you’re sharing an app, anything shown or played in that app is visible to <xliff:g id="app_seeking_permission" example="Meet">%s</xliff:g>. So be careful with things like passwords, payment details, messages, photos, and audio and video.</string>
     <!-- 1P/3P apps media projection permission button to continue with app selection or recording [CHAR LIMIT=60] -->
-    <string name="media_projection_entry_app_permission_dialog_continue">Start</string>
+    <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>
 
@@ -3674,6 +3679,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] -->
@@ -3682,17 +3688,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/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/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/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/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/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 096556f..7e2c9f8 100644
--- a/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
@@ -26,6 +26,7 @@
 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
@@ -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/UserContextualEducationRepository.kt b/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt
index 22ba4ad..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
@@ -73,6 +73,8 @@
         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"
     }
@@ -113,6 +115,14 @@
                 preferences[getLastShortcutTriggeredTimeKey(gestureType)]?.let {
                     Instant.ofEpochSecond(it)
                 },
+            usageSessionStartTime =
+                preferences[getUsageSessionStartTimeKey(gestureType)]?.let {
+                    Instant.ofEpochSecond(it)
+                },
+            lastEducationTime =
+                preferences[getLastEducationTimeKey(gestureType)]?.let {
+                    Instant.ofEpochSecond(it)
+                },
         )
     }
 
@@ -125,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)
+            )
         }
     }
 
@@ -142,7 +162,13 @@
     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>
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 5ec1006..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
@@ -68,7 +68,13 @@
     }
 
     suspend fun incrementSignalCount(gestureType: GestureType) {
-        repository.updateGestureEduModel(gestureType) { it.copy(signalCount = it.signalCount + 1) }
+        repository.updateGestureEduModel(gestureType) {
+            it.copy(
+                signalCount = it.signalCount + 1,
+                usageSessionStartTime =
+                    if (it.signalCount == 0) clock.instant() else it.usageSessionStartTime
+            )
+        }
     }
 
     suspend fun updateShortcutTriggerTime(gestureType: GestureType) {
@@ -76,4 +82,22 @@
             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 1d1ac5a..cd0b3f9 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -34,8 +34,6 @@
 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
@@ -60,7 +58,7 @@
         // Internal notification frontend dependencies
         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
@@ -76,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/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/tutorial/data/model/TutorialSchedulerInfo.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/model/TutorialSchedulerInfo.kt
index cfe64e2..9f46846 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/model/TutorialSchedulerInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/model/TutorialSchedulerInfo.kt
@@ -16,11 +16,6 @@
 
 package com.android.systemui.inputdevice.tutorial.data.model
 
-data class TutorialSchedulerInfo(
-    val keyboard: DeviceSchedulerInfo = DeviceSchedulerInfo(),
-    val touchpad: DeviceSchedulerInfo = DeviceSchedulerInfo()
-)
-
 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
index 31ff018..b9b3895 100644
--- 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
@@ -25,21 +25,32 @@
 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 com.android.systemui.inputdevice.tutorial.data.model.TutorialSchedulerInfo
 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) {
+constructor(
+    @Application private val applicationContext: Context,
+    @Background private val backgroundScope: CoroutineScope
+) {
 
     private val Context.dataStore: DataStore<Preferences> by
-        preferencesDataStore(name = DATASTORE_NAME)
+        preferencesDataStore(name = DATASTORE_NAME, scope = backgroundScope)
 
-    suspend fun loadData(): TutorialSchedulerInfo {
+    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()
     }
 
@@ -51,10 +62,10 @@
         applicationContext.dataStore.edit { pref -> pref[getLaunchedKey(device)] = true }
     }
 
-    private fun getSchedulerInfo(pref: Preferences): TutorialSchedulerInfo {
-        return TutorialSchedulerInfo(
-            keyboard = getDeviceSchedulerInfo(pref, DeviceType.KEYBOARD),
-            touchpad = getDeviceSchedulerInfo(pref, DeviceType.TOUCHPAD)
+    private fun getSchedulerInfo(pref: Preferences): Map<DeviceType, DeviceSchedulerInfo> {
+        return mapOf(
+            DeviceType.KEYBOARD to getDeviceSchedulerInfo(pref, DeviceType.KEYBOARD),
+            DeviceType.TOUCHPAD to getDeviceSchedulerInfo(pref, DeviceType.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
index 05e1044..b3b8f21 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt
@@ -16,23 +16,25 @@
 
 package com.android.systemui.inputdevice.tutorial.domain.interactor
 
-import android.content.Context
-import android.content.Intent
+import android.os.SystemProperties
+import android.util.Log
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.inputdevice.tutorial.data.model.DeviceSchedulerInfo
+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.Duration
 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.Flow
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.launch
 
 /**
@@ -43,62 +45,72 @@
 class TutorialSchedulerInteractor
 @Inject
 constructor(
-    @Application private val context: Context,
-    @Application private val applicationScope: CoroutineScope,
-    private val keyboardRepository: KeyboardRepository,
-    private val touchpadRepository: TouchpadRepository,
-    private val tutorialSchedulerRepository: TutorialSchedulerRepository
+    @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() {
-        applicationScope.launch {
-            val info = tutorialSchedulerRepository.loadData()
-            if (!info.keyboard.isLaunched) {
-                applicationScope.launch {
-                    schedule(
-                        keyboardRepository.isAnyKeyboardConnected,
-                        info.keyboard,
-                        DeviceType.KEYBOARD
-                    )
-                }
-            }
-            if (!info.touchpad.isLaunched) {
-                applicationScope.launch {
-                    schedule(
-                        touchpadRepository.isAnyTouchpadConnected,
-                        info.touchpad,
-                        DeviceType.TOUCHPAD
-                    )
-                }
+        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 suspend fun schedule(
-        isAnyDeviceConnected: Flow<Boolean>,
-        info: DeviceSchedulerInfo,
-        deviceType: DeviceType
-    ) {
-        if (!info.wasEverConnected) {
-            waitForDeviceConnection(isAnyDeviceConnected)
-            info.connectTime = Instant.now().toEpochMilli()
-            tutorialSchedulerRepository.updateConnectTime(deviceType, info.connectTime!!)
+    private val touchpadScheduleFlow = flow {
+        if (!repo.isLaunched(TOUCHPAD)) {
+            schedule(TOUCHPAD)
+            emit(TOUCHPAD)
         }
-        delay(remainingTimeMillis(info.connectTime!!))
-        waitForDeviceConnection(isAnyDeviceConnected)
-        info.isLaunched = true
-        tutorialSchedulerRepository.updateLaunch(deviceType)
-        launchTutorial()
     }
 
-    private suspend fun waitForDeviceConnection(isAnyDeviceConnected: Flow<Boolean>): Boolean {
-        return isAnyDeviceConnected.filter { it }.first()
+    private val keyboardScheduleFlow = flow {
+        if (!repo.isLaunched(KEYBOARD)) {
+            schedule(KEYBOARD)
+            emit(KEYBOARD)
+        }
     }
 
-    private fun launchTutorial() {
-        val intent = Intent(TUTORIAL_ACTION)
-        intent.addCategory(Intent.CATEGORY_DEFAULT)
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-        context.startActivity(intent)
+    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 {
@@ -107,7 +119,20 @@
     }
 
     companion object {
-        const val TUTORIAL_ACTION = "com.android.systemui.action.TOUCHPAD_TUTORIAL"
-        private val LAUNCH_DELAY = Duration.ofHours(72).toMillis()
+        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/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 0b3d0f7..1f3df95 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2334,6 +2334,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 +2405,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);
             }
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/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/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/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/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/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/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/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/ShareToAppPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ShareToAppPermissionDialogDelegate.kt
index 8bf2202..4b132d0 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ShareToAppPermissionDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ShareToAppPermissionDialogDelegate.kt
@@ -48,6 +48,7 @@
         appName,
         hostUid,
         mediaProjectionMetricsLogger,
+        dialogIconDrawable = R.drawable.ic_present_to_all,
     ) {
     override fun onCreate(dialog: AlertDialog, savedInstanceState: Bundle?) {
         super.onCreate(dialog, savedInstanceState)
@@ -83,22 +84,28 @@
                 listOf(
                     ScreenShareOption(
                         mode = SINGLE_APP,
-                        spinnerText = R.string.screen_share_permission_dialog_option_single_app,
+                        spinnerText =
+                            R.string
+                                .media_projection_entry_app_permission_dialog_option_text_single_app,
                         warningText =
                             R.string
                                 .media_projection_entry_app_permission_dialog_warning_single_app,
                         startButtonText =
-                            R.string.media_projection_entry_app_permission_dialog_continue,
+                            R.string
+                                .media_projection_entry_generic_permission_dialog_continue_single_app,
                         spinnerDisabledText = singleAppDisabledText,
                     ),
                     ScreenShareOption(
                         mode = ENTIRE_SCREEN,
-                        spinnerText = R.string.screen_share_permission_dialog_option_entire_screen,
+                        spinnerText =
+                            R.string
+                                .media_projection_entry_app_permission_dialog_option_text_entire_screen,
                         warningText =
                             R.string
                                 .media_projection_entry_app_permission_dialog_warning_entire_screen,
                         startButtonText =
-                            R.string.media_projection_entry_app_permission_dialog_continue,
+                            R.string
+                                .media_projection_entry_app_permission_dialog_continue_entire_screen,
                     )
                 )
             return if (singleAppDisabledText != null) {
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/views/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
index 7b248eb..e895d83 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
@@ -102,6 +102,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 +197,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 +558,7 @@
             @Nullable Bundle savedState,
             @DisplayId Context context,
             @DisplayId WindowManager windowManager,
+            @DisplayId ViewCaptureAwareWindowManager viewCaptureAwareWindowManager,
             Lazy<AssistManager> assistManagerLazy,
             AccessibilityManager accessibilityManager,
             DeviceProvisionedController deviceProvisionedController,
@@ -601,6 +604,7 @@
         mContext = context;
         mSavedState = savedState;
         mWindowManager = windowManager;
+        mViewCaptureAwareWindowManager = viewCaptureAwareWindowManager;
         mAccessibilityManager = accessibilityManager;
         mDeviceProvisionedController = deviceProvisionedController;
         mStatusBarStateController = statusBarStateController;
@@ -721,7 +725,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 +768,7 @@
             mCommandQueue.removeCallback(this);
             Trace.beginSection("NavigationBar#removeViewImmediate");
             try {
-                mWindowManager.removeViewImmediate(mView.getRootView());
+                mViewCaptureAwareWindowManager.removeViewImmediate(mView.getRootView());
             } finally {
                 Trace.endSection();
             }
@@ -866,7 +870,7 @@
         if (mOrientationHandle != null) {
             resetSecondaryHandle();
             getBarTransitions().removeDarkIntensityListener(mOrientationHandleIntensityListener);
-            mWindowManager.removeView(mOrientationHandle);
+            mViewCaptureAwareWindowManager.removeView(mOrientationHandle);
             mOrientationHandle.getViewTreeObserver().removeOnGlobalLayoutListener(
                     mOrientationHandleGlobalLayoutListener);
         }
@@ -937,7 +941,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");
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 64%
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..2d2b869 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,27 @@
 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.
+ *
+ * Different from the [NotificationsShadeSceneContentViewModel] which models the _content_ of the
+ * scene.
+ */
+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 +50,10 @@
                 Back to SceneFamilies.Home,
             )
         )
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(): NotificationsShadeSceneActionsViewModel
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneContentViewModel.kt
new file mode 100644
index 0000000..c1c7320
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneContentViewModel.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.notifications.ui.viewmodel
+
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.shade.ui.viewmodel.BaseShadeSceneViewModel
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+/**
+ * Models UI state used to render the content of the notifications shade scene.
+ *
+ * Different from [NotificationsShadeSceneActionsViewModel], which only models user actions that can
+ * be performed to navigate to other scenes.
+ */
+class NotificationsShadeSceneContentViewModel
+@AssistedInject
+constructor(
+    deviceEntryInteractor: DeviceEntryInteractor,
+    sceneInteractor: SceneInteractor,
+) :
+    BaseShadeSceneViewModel(
+        deviceEntryInteractor,
+        sceneInteractor,
+    ) {
+
+    @AssistedFactory
+    interface Factory {
+        fun create(): NotificationsShadeSceneContentViewModel
+    }
+}
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/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/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..3ec088c 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()
@@ -152,6 +154,7 @@
             handleKeyguardEnabledness()
             notifyKeyguardDismissCallbacks()
             refreshLockscreenEnabled()
+            handleHideAlternateBouncerOnTransitionToGone()
         } else {
             sceneLogger.logFrameworkEnabled(
                 isEnabled = false,
@@ -228,13 +231,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,7 +357,9 @@
                                 )
                         }
                     val isOnLockscreen = renderedScenes.contains(Scenes.Lockscreen)
-                    val isOnBouncer = renderedScenes.contains(Scenes.Bouncer)
+                    val isOnBouncer =
+                        renderedScenes.contains(Scenes.Bouncer) ||
+                            alternateBouncerInteractor.isVisibleState()
                     if (!deviceUnlockStatus.isUnlocked) {
                         return@mapNotNull if (isOnLockscreen || isOnBouncer) {
                             // Already on lockscreen or bouncer, no need to change scenes.
@@ -379,12 +387,13 @@
                                     !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 bouncer showing," +
+                                        " from sceneKey=$prevScene"
                             }
                         isOnLockscreen ->
                             // The lockscreen should be dismissed automatically in 2 scenarios:
@@ -776,4 +785,14 @@
                 .collectLatest { deviceEntryInteractor.refreshLockscreenEnabled() }
         }
     }
+
+    private fun handleHideAlternateBouncerOnTransitionToGone() {
+        applicationScope.launch {
+            sceneInteractor.transitionState
+                .map { it.isIdle(Scenes.Gone) }
+                .distinctUntilChanged()
+                .filter { it }
+                .collectLatest { alternateBouncerInteractor.hide() }
+        }
+    }
 }
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/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index 1170354..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);
-                    createErrorStartingNotification();
+                    createErrorStartingNotification(currentUser);
                     stopForeground(STOP_FOREGROUND_DETACH);
                     stopSelf();
                     return Service.START_NOT_STICKY;
@@ -276,8 +279,8 @@
      * errors.
      */
     @VisibleForTesting
-    protected void createErrorStartingNotification() {
-        createErrorNotification(strings().getStartError());
+    protected void createErrorStartingNotification(UserHandle currentUser) {
+        createErrorNotification(currentUser, strings().getStartError(), GROUP_KEY_ERROR_STARTING);
     }
 
     /**
@@ -285,17 +288,22 @@
      * errors.
      */
     @VisibleForTesting
-    protected void createErrorSavingNotification() {
-        createErrorNotification(strings().getSaveError());
+    protected void createErrorSavingNotification(UserHandle currentUser) {
+        createErrorNotification(currentUser, strings().getSaveError(), GROUP_KEY_ERROR_SAVING);
     }
 
-    private void createErrorNotification(String notificationContentTitle) {
+    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());
 
         Notification.Builder builder = new Notification.Builder(this, getChannelId())
                 .setSmallIcon(R.drawable.ic_screenrecord)
                 .setContentTitle(notificationContentTitle)
+                .setGroup(groupKey)
                 .addExtras(extras);
         startForeground(mNotificationId, builder.build());
     }
@@ -350,7 +358,7 @@
                 .setContentText(
                         strings().getBackgroundProcessingLabel())
                 .setSmallIcon(R.drawable.ic_screenrecord)
-                .setGroup(GROUP_KEY)
+                .setGroup(GROUP_KEY_SAVED)
                 .addExtras(extras);
         return builder.build();
     }
@@ -387,7 +395,7 @@
                         PendingIntent.FLAG_IMMUTABLE))
                 .addAction(shareAction)
                 .setAutoCancel(true)
-                .setGroup(GROUP_KEY)
+                .setGroup(GROUP_KEY_SAVED)
                 .addExtras(extras);
 
         // Add thumbnail if available
@@ -402,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() {
@@ -427,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 {
@@ -444,7 +460,7 @@
             Log.e(getTag(), "stopRecording called, but there was an error when ending"
                     + "recording");
             exception.printStackTrace();
-            createErrorSavingNotification();
+            createErrorSavingNotification(currentUser);
         } catch (Throwable throwable) {
             if (getRecorder() != null) {
                 // Something unexpected happen, SystemUI will crash but let's delete
@@ -468,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) {
@@ -527,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/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..15bbef0 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -293,7 +293,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.
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
index 0a092a0..16aef65 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
@@ -2252,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/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/BaseShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/BaseShadeSceneViewModel.kt
new file mode 100644
index 0000000..068d6a7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/BaseShadeSceneViewModel.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.lifecycle.SysUiViewModel
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collectLatest
+
+/** Base class for classes that model UI state of the content of shade scenes. */
+abstract class BaseShadeSceneViewModel(
+    private val deviceEntryInteractor: DeviceEntryInteractor,
+    private val sceneInteractor: SceneInteractor,
+) : SysUiViewModel() {
+
+    private val _isEmptySpaceClickable =
+        MutableStateFlow(!deviceEntryInteractor.isDeviceEntered.value)
+    /** Whether clicking on the empty area of the shade does something */
+    val isEmptySpaceClickable: StateFlow<Boolean> = _isEmptySpaceClickable.asStateFlow()
+
+    override suspend fun onActivated() {
+        deviceEntryInteractor.isDeviceEntered.collectLatest { isDeviceEntered ->
+            _isEmptySpaceClickable.value = !isDeviceEntered
+        }
+    }
+
+    /** 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.")
+    }
+}
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..3cdff96
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt
@@ -0,0 +1,89 @@
+/*
+ * 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.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.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.StateFlow
+
+/**
+ * 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,
+    deviceEntryInteractor: DeviceEntryInteractor,
+    sceneInteractor: SceneInteractor,
+) :
+    BaseShadeSceneViewModel(
+        deviceEntryInteractor,
+        sceneInteractor,
+    ) {
+
+    val shadeMode: StateFlow<ShadeMode> = shadeInteractor.shadeMode
+
+    val isMediaVisible: StateFlow<Boolean> = mediaCarouselInteractor.hasActiveMediaOrRecommendation
+
+    private val footerActionsControllerInitialized = AtomicBoolean(false)
+
+    /**
+     * 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)
+    }
+
+    @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..50be6dc 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;
@@ -1219,7 +1218,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 +1226,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/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/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/stack/domain/interactor/SharedNotificationContainerInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt
index 2537aff..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
@@ -23,7 +23,9 @@
 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
@@ -43,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>,
@@ -56,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 =
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 db91eed..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,20 +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>
-        get() = 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 b6d58d6..ada3c52 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -2353,7 +2353,11 @@
                     && !mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()
                     && mStatusBarKeyguardViewManager.isSecure()) {
                 Log.d(TAG, "showBouncerOrLockScreenIfKeyguard, showingBouncer");
-                mStatusBarKeyguardViewManager.showBouncer(true /* scrimmed */);
+                if (SceneContainerFlag.isEnabled()) {
+                    mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
+                } else {
+                    mStatusBarKeyguardViewManager.showBouncer(true /* scrimmed */);
+                }
             }
         }
     }
@@ -2985,7 +2989,7 @@
                 @Override
                 public void onFalse() {
                     // Hides quick settings, bouncer, and quick-quick settings.
-                    mStatusBarKeyguardViewManager.reset(true);
+                    mStatusBarKeyguardViewManager.reset(true, /* isFalsingReset= */true);
                 }
             };
 
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/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/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/ui/composable/ActionKeyTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/ActionKeyTutorialScreen.kt
new file mode 100644
index 0000000..f7f2631
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/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.touchpad.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.res.R
+import com.android.systemui.touchpad.tutorial.ui.composable.TutorialActionState.FINISHED
+import com.android.systemui.touchpad.tutorial.ui.composable.TutorialActionState.NOT_STARTED
+
+@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/touchpad/tutorial/ui/composable/ActionTutorialContent.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/ActionTutorialContent.kt
new file mode 100644
index 0000000..2b7f674
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/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.touchpad.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.touchpad.tutorial.ui.composable.TutorialActionState.FINISHED
+import com.android.systemui.touchpad.tutorial.ui.composable.TutorialActionState.IN_PROGRESS
+import com.android.systemui.touchpad.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/BackGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
index 1b00ae2..5980e1d 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
@@ -38,8 +38,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..dfe9c0d 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,18 @@
 
 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.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 +42,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 +73,11 @@
             )
         }
     TouchpadGesturesHandlingBox(gestureHandler, gestureState) {
-        GestureTutorialContent(gestureState, onDoneButtonClicked, screenConfig)
+        ActionTutorialContent(
+            gestureState.toTutorialActionState(),
+            onDoneButtonClicked,
+            screenConfig
+        )
     }
 }
 
@@ -135,165 +108,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..ed3110c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.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.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/view/TouchpadTutorialActivity.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt
index 088a8fd..48e6397 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.touchpad.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/SettingsProxyExt.kt b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt
index d757e33..36468144 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt
@@ -19,6 +19,7 @@
 
 import android.annotation.UserIdInt
 import android.database.ContentObserver
+import com.android.systemui.Flags
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
@@ -39,9 +40,21 @@
                     }
                 }
 
-            names.forEach { name -> registerContentObserverForUserSync(name, observer, userId) }
+            names.forEach { name ->
+                if (Flags.settingsExtRegisterContentObserverOnBgThread()) {
+                    registerContentObserverForUser(name, observer, userId)
+                } else {
+                    registerContentObserverForUserSync(name, observer, userId)
+                }
+            }
 
-            awaitClose { unregisterContentObserverSync(observer) }
+            awaitClose {
+                if (Flags.settingsExtRegisterContentObserverOnBgThread()) {
+                    unregisterContentObserverAsync(observer)
+                } else {
+                    unregisterContentObserverSync(observer)
+                }
+            }
         }
     }
 
@@ -57,9 +70,21 @@
                     }
                 }
 
-            names.forEach { name -> registerContentObserverSync(name, observer) }
+            names.forEach { name ->
+                if (Flags.settingsExtRegisterContentObserverOnBgThread()) {
+                    registerContentObserver(name, observer)
+                } else {
+                    registerContentObserverSync(name, observer)
+                }
+            }
 
-            awaitClose { unregisterContentObserverSync(observer) }
+            awaitClose {
+                if (Flags.settingsExtRegisterContentObserverOnBgThread()) {
+                    unregisterContentObserverAsync(observer)
+                } else {
+                    unregisterContentObserverSync(observer)
+                }
+            }
         }
     }
 }
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/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/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/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/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/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/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/mediaprojection/permission/ShareToAppPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/ShareToAppPermissionDialogDelegateTest.kt
index c57aa36..04ef1be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/ShareToAppPermissionDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/ShareToAppPermissionDialogDelegateTest.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.phone.AlertDialogWithDelegate
 import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.google.common.truth.Truth.assertThat
 import kotlin.test.assertEquals
 import org.junit.After
 import org.junit.Test
@@ -44,8 +45,10 @@
 
     private val appName = "Test App"
 
-    private val resIdSingleApp = R.string.screen_share_permission_dialog_option_single_app
-    private val resIdFullScreen = R.string.screen_share_permission_dialog_option_entire_screen
+    private val resIdSingleApp =
+        R.string.media_projection_entry_app_permission_dialog_option_text_single_app
+    private val resIdFullScreen =
+        R.string.media_projection_entry_app_permission_dialog_option_text_entire_screen
     private val resIdSingleAppDisabled =
         R.string.media_projection_entry_app_permission_dialog_single_app_disabled
 
@@ -115,6 +118,36 @@
         assertEquals(context.getString(resIdFullScreen), secondOptionText)
     }
 
+    @Test
+    fun startButtonText_entireScreenSelected() {
+        setUpAndShowDialog()
+        onSpinnerItemSelected(ENTIRE_SCREEN)
+
+        val startButtonText = dialog.requireViewById<TextView>(android.R.id.button1).text
+
+        assertThat(startButtonText)
+            .isEqualTo(
+                context.getString(
+                    R.string.media_projection_entry_app_permission_dialog_continue_entire_screen
+                )
+            )
+    }
+
+    @Test
+    fun startButtonText_singleAppSelected() {
+        setUpAndShowDialog()
+        onSpinnerItemSelected(SINGLE_APP)
+
+        val startButtonText = dialog.requireViewById<TextView>(android.R.id.button1).text
+
+        assertThat(startButtonText)
+            .isEqualTo(
+                context.getString(
+                    R.string.media_projection_entry_generic_permission_dialog_continue_single_app
+                )
+            )
+    }
+
     private fun setUpAndShowDialog(
         mediaProjectionConfig: MediaProjectionConfig? = null,
         overrideDisableSingleAppOption: Boolean = false,
@@ -142,4 +175,10 @@
         delegate.onCreate(dialog, savedInstanceState = null)
         dialog.show()
     }
+
+    private fun onSpinnerItemSelected(position: Int) {
+        val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options)
+        checkNotNull(spinner.onItemSelectedListener)
+            .onItemSelected(spinner, mock(), position, /* id= */ 0)
+    }
 }
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 79c206c..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,10 +128,6 @@
 
         // Mock notifications
         doNothing().when(mRecordingService).createRecordingNotification();
-        doReturn(mNotification).when(mRecordingService).createProcessingNotification();
-        doReturn(mNotification).when(mRecordingService).createSaveNotification(any());
-        doNothing().when(mRecordingService).createErrorStartingNotification();
-        doNothing().when(mRecordingService).createErrorSavingNotification();
         doNothing().when(mRecordingService).showErrorToast(anyInt());
         doNothing().when(mRecordingService).stopForeground(anyInt());
 
@@ -228,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();
@@ -235,7 +262,11 @@
 
         mRecordingService.onStopped();
 
-        verify(mRecordingService).createErrorSavingNotification();
+        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/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/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/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index 86d21e8..6916bbd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -16,7 +16,6 @@
 
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
 import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT;
-import static android.inputmethodservice.InputMethodService.IME_INVISIBLE;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowInsetsController.BEHAVIOR_DEFAULT;
 
@@ -207,7 +206,7 @@
 
         mCommandQueue.setImeWindowStatus(SECONDARY_DISPLAY, 1, 2, true);
         waitForIdleSync();
-        verify(mCallbacks).setImeWindowStatus(eq(DEFAULT_DISPLAY), eq(IME_INVISIBLE),
+        verify(mCallbacks).setImeWindowStatus(eq(DEFAULT_DISPLAY), eq(0),
                 eq(BACK_DISPOSITION_DEFAULT), eq(false));
         verify(mCallbacks).setImeWindowStatus(eq(SECONDARY_DISPLAY), eq(1), eq(2), eq(true));
     }
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/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/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/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/education/data/repository/ContextualEducationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt
index aac5e57..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() }
 
-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/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/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/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/NotificationsShadeSceneContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneContentViewModelKosmos.kt
new file mode 100644
index 0000000..9240102
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneContentViewModelKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+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.notifications.ui.viewmodel.NotificationsShadeSceneContentViewModel
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.notificationsShadeSceneContentViewModel:
+    NotificationsShadeSceneContentViewModel by Fixture {
+    NotificationsShadeSceneContentViewModel(
+        deviceEntryInteractor = deviceEntryInteractor,
+        sceneInteractor = sceneInteractor,
+    )
+}
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/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/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/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 4faf03c..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: [
-        ":hoststubgen_framework-minus-apex_stats.csv",
-        ":hoststubgen_framework-minus-apex_apis.csv",
-        ":hoststubgen_framework-minus-apex_keep_all.txt",
-        ":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/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/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/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/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/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 30efa3e..e57fe13 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -439,6 +439,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 +518,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 +530,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 +1276,7 @@
                 return;
             }
             uss.setState(UserState.STATE_SHUTDOWN);
+            mDoNotAbortShutdownUserIds.remove(userId);
         }
         TimingsTraceAndSlog t = new TimingsTraceAndSlog();
         t.traceBegin("setUserState-STATE_SHUTDOWN-" + userId + "-[stopUser]");
@@ -1555,7 +1585,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 +1639,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 +1935,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 +2340,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;
@@ -3425,7 +3472,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 +3569,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);
@@ -3896,10 +3944,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/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/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/HdmiCecMessageValidator.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
index 7746276..3161b77 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
@@ -523,8 +523,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/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index 94b1473..079b724 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -109,10 +109,6 @@
      * <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>
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 a1e6790..8afbd56 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;
@@ -272,11 +274,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";
@@ -333,7 +330,7 @@
     @UserIdInt
     @BinderThread
     private int resolveImeUserIdLocked(@UserIdInt int callingProcessUserId) {
-        return mConcurrentMultiUserModeEnabled ? callingProcessUserId : mCurrentUserId;
+        return mConcurrentMultiUserModeEnabled ? callingProcessUserId : mCurrentImeUserId;
     }
 
     /**
@@ -346,7 +343,7 @@
     @UserIdInt
     private int resolveImeUserIdFromDisplayIdLocked(int displayId) {
         return mConcurrentMultiUserModeEnabled
-                ? mUserManagerInternal.getUserAssignedToDisplay(displayId) : mCurrentUserId;
+                ? mUserManagerInternal.getUserAssignedToDisplay(displayId) : mCurrentImeUserId;
     }
 
     /**
@@ -362,7 +359,7 @@
             final int displayId = mWindowManagerInternal.getDisplayIdForWindow(windowToken);
             return mUserManagerInternal.getUserAssignedToDisplay(displayId);
         }
-        return mCurrentUserId;
+        return mCurrentImeUserId;
     }
 
     final Context mContext;
@@ -373,10 +370,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
@@ -543,7 +553,7 @@
     @GuardedBy("ImfLock.class")
     @Nullable
     IInputMethodInvoker getCurMethodLocked() {
-        return getInputMethodBindingController(mCurrentUserId).getCurMethod();
+        return getInputMethodBindingController(mCurrentImeUserId).getCurMethod();
     }
 
     /**
@@ -590,7 +600,7 @@
         switch (key) {
             case Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD: {
                 if (!Flags.imeSwitcherRevamp()) {
-                    if (userId == mCurrentUserId) {
+                    if (userId == mCurrentImeUserId) {
                         mMenuController.updateKeyboardFromSettingsLocked(userId);
                     }
                 }
@@ -652,11 +662,11 @@
                 // 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;
+                    final int userId = mCurrentImeUserId;
                     if (Flags.imeSwitcherRevamp()) {
                         final var bindingController = getInputMethodBindingController(userId);
                         mMenuControllerNew.hide(bindingController.getCurTokenDisplayId(), userId);
@@ -1190,7 +1200,7 @@
 
             mShowOngoingImeSwitcherForPhones = false;
 
-            mCurrentUserId = mActivityManagerInternal.getCurrentUserId();
+            mCurrentImeUserId = mActivityManagerInternal.getCurrentUserId();
             final IntFunction<InputMethodBindingController>
                     bindingControllerFactory = userId -> new InputMethodBindingController(userId,
                     InputMethodManagerService.this);
@@ -1270,7 +1280,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
@@ -1285,7 +1295,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);
@@ -1310,7 +1320,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);
 
@@ -1410,13 +1420,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) {
@@ -1596,7 +1606,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
@@ -2010,7 +2020,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;
         }
@@ -2548,7 +2558,7 @@
         final int userId = userData.mUserId;
         // 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 (iconId == 0) {
@@ -2580,7 +2590,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) {
@@ -2628,8 +2638,7 @@
                 && 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() && !Flags.imeSwitcherRevamp()) {
@@ -2781,7 +2790,7 @@
     private void updateSystemUiLocked(int vis, 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);
@@ -2794,7 +2803,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
@@ -2906,7 +2915,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);
@@ -2937,7 +2946,7 @@
         // TODO(b/357663774): Figure out how to better handle this scenario.
         userData.mSubtypeForKeyboardLayoutMapping =
                 Pair.create(newSubtypeHandle, normalizedSubtype);
-        if (userId != mCurrentUserId) {
+        if (userId != mCurrentImeUserId) {
             return;
         }
         mInputManagerInternal.onInputMethodSubtypeChangedForKeyboardLayoutMapping(
@@ -2945,12 +2954,12 @@
     }
 
     @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) {
@@ -2967,25 +2976,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(),
@@ -3012,9 +3021,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().
@@ -3709,7 +3716,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);
@@ -3756,9 +3763,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.
@@ -4027,11 +4034,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.
      */
@@ -4039,17 +4047,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());
             }
         }
@@ -4064,19 +4072,19 @@
             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);
@@ -4133,10 +4141,10 @@
                 imi.getPackageName(), callingUid, userId, settings)) {
             throw getExceptionForUnknownImeId(id);
         }
-        final int subtypeId = subtype != null
-                ? SubtypeUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode())
-                : NOT_A_SUBTYPE_ID;
-        setInputMethodWithSubtypeIdLocked(id, subtypeId, userId);
+        final int subtypeIndex = subtype != null
+                ? SubtypeUtils.getSubtypeIndexFromHashCode(imi, subtype.hashCode())
+                : NOT_A_SUBTYPE_INDEX;
+        setInputMethodWithSubtypeIndexLocked(id, subtypeIndex, userId);
     }
 
     @BinderThread
@@ -4154,18 +4162,18 @@
         }
         final var currentSubtype = bindingController.getCurrentSubtype();
         String targetLastImiId = null;
-        int subtypeId = NOT_A_SUBTYPE_ID;
+        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_ID
+            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;
-                subtypeId = SubtypeUtils.getSubtypeIdFromHashCode(lastImi, lastSubtypeHash);
+                subtypeIndex = SubtypeUtils.getSubtypeIndexFromHashCode(lastImi, lastSubtypeHash);
             }
         }
 
@@ -4175,29 +4183,27 @@
             // 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;
-                            }
+            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;
                         }
                     }
                 }
@@ -4208,9 +4214,9 @@
             if (DEBUG) {
                 Slog.d(TAG, "Switch to: " + lastImi.getId() + ", " + lastIme.second
                         + ", from: " + bindingController.getSelectedMethodId() + ", "
-                        + subtypeId);
+                        + subtypeIndex);
             }
-            setInputMethodWithSubtypeIdLocked(targetLastImiId, subtypeId, userId);
+            setInputMethodWithSubtypeIndexLocked(targetLastImiId, subtypeIndex, userId);
             return true;
         } else {
             return false;
@@ -4230,7 +4236,7 @@
         if (nextSubtype == null) {
             return false;
         }
-        setInputMethodWithSubtypeIdLocked(nextSubtype.mImi.getId(), nextSubtype.mSubtypeId,
+        setInputMethodWithSubtypeIndexLocked(nextSubtype.mImi.getId(), nextSubtype.mSubtypeIndex,
                 userData.mUserId);
         return true;
     }
@@ -4447,7 +4453,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();
@@ -4636,7 +4642,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;
@@ -4717,7 +4723,7 @@
     }
 
     @GuardedBy("ImfLock.class")
-    private void setInputMethodWithSubtypeIdLocked(String id, int subtypeId,
+    private void setInputMethodWithSubtypeIndexLocked(String id, int subtypeIndex,
             @UserIdInt int userId) {
         final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
         if (settings.getMethodMap().get(id) != null
@@ -4727,7 +4733,7 @@
         }
         final long ident = Binder.clearCallingIdentity();
         try {
-            setInputMethodLocked(id, subtypeId, userId);
+            setInputMethodLocked(id, subtypeIndex, userId);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -4888,7 +4894,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(
@@ -4908,24 +4915,24 @@
                         + " 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);
         }
     }
 
@@ -4954,7 +4961,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
@@ -5020,7 +5027,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()) {
@@ -5104,7 +5112,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;
@@ -5444,7 +5452,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);
@@ -5454,12 +5462,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 {
@@ -5495,20 +5503,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);
     }
 
     /**
@@ -5532,14 +5540,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;
     }
 
@@ -5595,8 +5603,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 =
@@ -5615,7 +5623,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;
         }
@@ -5693,10 +5701,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);
             }
         }
 
@@ -6063,7 +6071,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:");
@@ -6095,7 +6103,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="
@@ -6188,7 +6196,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) {
@@ -6419,7 +6427,7 @@
         }
         final int[] userIds;
         synchronized (ImfLock.class) {
-            userIds = InputMethodUtils.resolveUserId(userIdToBeResolved, mCurrentUserId,
+            userIds = InputMethodUtils.resolveUserId(userIdToBeResolved, mCurrentImeUserId,
                     shellCommand.getErrPrintWriter());
         }
         try (PrintWriter pr = shellCommand.getOutPrintWriter()) {
@@ -6465,7 +6473,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;
@@ -6560,13 +6568,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);
@@ -6580,6 +6588,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;
                 }
@@ -6601,7 +6631,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;
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 b5c278c..c77b768 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;
                 }
             }
@@ -843,6 +855,9 @@
     /**
      * 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.
@@ -861,6 +876,9 @@
      * 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.
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/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 17f6561..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();
@@ -760,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
@@ -1038,6 +1045,7 @@
 
             mUseMeteredFirewallChains = Flags.useMeteredFirewallChains();
             mUseDifferentDelaysForBackgroundChain = Flags.useDifferentDelaysForBackgroundChain();
+            mNeverApplyRulesToCoreUids = Flags.neverApplyRulesToCoreUids();
 
             synchronized (mUidRulesFirstLock) {
                 synchronized (mNetworkPoliciesSecondLock) {
@@ -4088,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);
@@ -4878,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.
@@ -5180,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);
     }
 
@@ -6194,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) {
@@ -6241,6 +6254,9 @@
      */
     @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);
@@ -6249,8 +6265,6 @@
             if (chain == FIREWALL_CHAIN_STANDBY) {
                 mUidFirewallStandbyRules.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);
@@ -6295,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 a44e553..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.ORIGIN_USER_IN_SYSTEMUI;
-
 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 == ORIGIN_USER_IN_SYSTEMUI) {
-                        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/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 45b8da5..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());
                 }
             }
         });
@@ -8667,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) {
@@ -9390,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());
         }
     }
 
@@ -10372,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")
@@ -10539,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);
     }
 
@@ -10558,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/ZenModeConditions.java b/services/core/java/com/android/server/notification/ZenModeConditions.java
index 268d835..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();
             }
         }
 
@@ -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/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 8c280ed..db48835 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -37,6 +37,8 @@
 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;
@@ -643,10 +645,10 @@
                 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, ORIGIN_APP,
                         "applyGlobalZenModeAsImplicitZenRule", callingUid);
@@ -867,8 +869,8 @@
                 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);
             }
@@ -885,7 +887,12 @@
             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;
+            }
         }
     }
 
@@ -943,12 +950,40 @@
         }
 
         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 (condition.state == STATE_TRUE) {
+                    // Manually turn on a rule -> Apply override.
+                    rule.setConditionOverride(OVERRIDE_ACTIVATE);
+                } else if (condition.state == STATE_FALSE) {
+                    // Manually turn off a rule. If the rule was manually activated before, reset
+                    // override -- but only if this will not result in the rule turning on
+                    // immediately because of a previously snoozed condition! In that case, apply
+                    // deactivate-override.
+                    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<>();
@@ -971,15 +1006,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;
@@ -1181,7 +1207,7 @@
 
             if (rule.enabled != azr.isEnabled()) {
                 rule.enabled = azr.isEnabled();
-                rule.snoozing = false;
+                rule.resetConditionOverride();
                 modified = true;
             }
             if (!Objects.equals(rule.configurationActivity, azr.getConfigurationActivity())) {
@@ -1271,7 +1297,7 @@
             return modified;
         } else {
             if (rule.enabled != azr.isEnabled()) {
-                rule.snoozing = false;
+                rule.resetConditionOverride();
             }
             rule.name = azr.getName();
             rule.condition = null;
@@ -1573,18 +1599,16 @@
                     // 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);
                         }
                     }
 
@@ -1626,13 +1650,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);
                 }
             }
@@ -1695,8 +1717,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;
                     }
 
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/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index a0d5ea8..98e3e24 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -1801,26 +1801,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/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/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 829ee27..57d7d79 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -2105,6 +2105,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 +2137,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 +4073,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 +5471,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);
     }
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/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/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 f2f5eda..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;
@@ -191,8 +190,6 @@
     @GuardedBy("mLock")
     private boolean mVibrateOn;
     @GuardedBy("mLock")
-    private boolean mKeyboardVibrationOn;
-    @GuardedBy("mLock")
     private int mRingerMode;
     @GuardedBy("mLock")
     private boolean mOnWirelessCharger;
@@ -532,14 +529,6 @@
             return false;
         }
 
-        if (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;
     }
@@ -556,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));
@@ -654,7 +644,6 @@
             return "VibrationSettings{"
                     + "mVibratorConfig=" + mVibrationConfig
                     + ", mVibrateOn=" + mVibrateOn
-                    + ", mKeyboardVibrationOn=" + mKeyboardVibrationOn
                     + ", mVibrateInputDevices=" + mVibrateInputDevices
                     + ", mBatterySaverMode=" + mBatterySaverMode
                     + ", mRingerMode=" + ringerModeToString(mRingerMode)
@@ -671,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));
@@ -698,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));
@@ -774,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 76872cf..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);
@@ -1698,7 +1700,7 @@
             IBinder.DeathRecipient {
 
         public final ExternalVibration externalVibration;
-        public ExternalVibrationScale scale = new ExternalVibrationScale();
+        public final ExternalVibrationScale scale = new ExternalVibrationScale();
 
         private Vibration.Status mStatus;
 
@@ -1712,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);
         }
@@ -2047,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;
 
@@ -2146,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..48a9311
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java
@@ -0,0 +1,463 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.InsetsSource;
+import android.view.InsetsState;
+import android.view.RoundedCorner;
+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;
+
+    private boolean mLastShouldShowLetterboxUi;
+
+    AppCompatLetterboxPolicy(@NonNull ActivityRecord  activityRecord) {
+        mActivityRecord = activityRecord;
+        mLetterboxPolicyState = new LetterboxPolicyState();
+    }
+
+    /** 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;
+        }
+        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) {
+        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
+        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.
+    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;
+    }
+
+    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 =
+                AppCompatUtils.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(@NonNull InsetsState insetsState,
+            @RoundedCorner.Position int position) {
+        final RoundedCorner corner = insetsState.getRoundedCorners().getRoundedCorner(position);
+        return corner == null ? 0 : corner.getRadius();
+    }
+
+    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();
+        }
+    }
+
+    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));
+    }
+
+    private boolean requiresRoundedCorners(@NonNull final WindowState mainWindow) {
+        final AppCompatLetterboxOverrides letterboxOverrides = mActivityRecord
+                .mAppCompatController.getAppCompatLetterboxOverrides();
+        return isLetterboxedNotForDisplayCutout(mainWindow)
+                && letterboxOverrides.isLetterboxActivityCornersRounded();
+    }
+
+    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) {
+                    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/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java
index 0244d27..91205fc 100644
--- a/services/core/java/com/android/server/wm/AppCompatUtils.java
+++ b/services/core/java/com/android/server/wm/AppCompatUtils.java
@@ -28,6 +28,9 @@
 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;
 
@@ -212,6 +215,23 @@
         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;
+    }
+
     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/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/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/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/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 0093e9d..58c48ad 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();
     }
@@ -1386,6 +1393,12 @@
                 task.setTrimmableFromRecents(hop.isTrimmableFromRecents());
                 break;
             }
+            case HIERARCHY_OP_TYPE_RESTORE_BACK_NAVIGATION: {
+                if (mService.mBackNavigationController.restoreBackNavigation()) {
+                    effects |= TRANSACT_EFFECTS_LIFECYCLE;
+                }
+                break;
+            }
         }
         return effects;
     }
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/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/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
index dc03732..a27ad9a 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;
@@ -61,7 +62,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 +103,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);
@@ -913,52 +913,100 @@
         final var controller = ControllerImpl.createFrom(null /* currentInstance */, List.of(),
                 List.of());
 
-        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)));
+                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);
 
-        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. */
@@ -1199,25 +1247,26 @@
     }
 
     /**
-     * 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 ControllerImpl 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/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/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index 1a3af13..957ee06 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -1320,16 +1320,6 @@
     }
 
     @Test
-    public void testSetScaleTo2() {
-        testSetScaleAndZoom(2.0f);
-    }
-
-    @Test
-    public void testSetScaleTo20() {
-        testSetScaleAndZoom(20.0f);
-    }
-
-    @Test
     public void testTransitToPanningState_scaleDifferenceOverThreshold_startDetecting() {
         final float scale = 2.0f;
         final float threshold = FullScreenMagnificationGestureHandler.PanningScalingState
@@ -1708,18 +1698,6 @@
         assertActionsInOrder(eventCaptor.mEvents, expectedActions);
     }
 
-    private void testSetScaleAndZoom(float scale) {
-        Settings.Secure.putFloatForUser(mContext.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, scale,
-                UserHandle.USER_SYSTEM);
-
-        goFromStateIdleTo(STATE_ACTIVATED_2TAPS);
-
-        check(mMgh.mCurrentState == mMgh.mDetectingState, STATE_IDLE);
-        assertThat(mMgh.mFullScreenMagnificationController.getScale(DISPLAY_0))
-                .isEqualTo(scale);
-    }
-
     private void enableOneFingerPanning(boolean enable) {
         mMockOneFingerPanningEnabled = enable;
         when(mMockOneFingerPanningSettingsProvider.isOneFingerPanningEnabled()).thenReturn(enable);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java
index 1c0ddc2..87fe6cf 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java
@@ -300,7 +300,7 @@
         mMagnificationConnectionManager.setConnection(mMockConnection.getConnection());
         mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 2.5f, NaN, NaN);
 
-        mMagnificationConnectionManager.setScale(TEST_DISPLAY, 22.0f);
+        mMagnificationConnectionManager.setScale(TEST_DISPLAY, 10.0f);
 
         assertEquals(mMagnificationConnectionManager.getScale(TEST_DISPLAY),
                 MagnificationScaleProvider.MAX_SCALE);
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..a8e350b 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;
@@ -201,7 +203,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());
@@ -936,6 +939,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.
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..1074f7b 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);
@@ -308,7 +311,7 @@
         // Invalid Recording Sequence
         assertMessageValidity("04:99:12:06:0C:2D:5A:19: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
@@ -354,7 +357,7 @@
         // Invalid Recording Sequence
         assertMessageValidity("40:A2:14:09:12:28:4B:19: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);
         // Invalid External PLug
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/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 4bb8017..5a8de58 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -13064,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);
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 60c4ac7..e70ed5f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
@@ -33,6 +33,8 @@
 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;
@@ -524,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;
 
@@ -546,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);
@@ -625,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()
@@ -662,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);
@@ -1115,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);
 
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 776a840..212e61e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -51,6 +51,7 @@
 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;
@@ -61,6 +62,9 @@
 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;
@@ -2999,11 +3003,13 @@
             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);
         }
     }
 
@@ -3038,16 +3044,20 @@
                 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);
             }
         }
     }
@@ -4288,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;
 
@@ -5000,7 +5010,8 @@
         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
@@ -5713,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);
     }
 
@@ -6039,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, ORIGIN_APP, "test", "test", 0);
-        assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).snoozing).isTrue();
+        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);
     }
@@ -6451,6 +6465,160 @@
                 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);
+        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();
+    }
+
+    @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, SYSTEM_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,
             @Nullable ZenPolicy zenPolicy) {
         ZenRule rule = new ZenRule();
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/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 72ef888..6f06050 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
@@ -119,6 +119,7 @@
             USAGE_PHYSICAL_EMULATION,
             USAGE_RINGTONE,
             USAGE_TOUCH,
+            USAGE_IME_FEEDBACK
     };
 
     @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
@@ -525,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);
@@ -601,14 +602,14 @@
 
     @Test
     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);
@@ -617,7 +618,7 @@
         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());
@@ -625,9 +626,9 @@
 
     @Test
     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);
@@ -635,16 +636,16 @@
         // Keyboard touch not ignored.
         assertVibrationNotIgnoredForAttributes(
                 new VibrationAttributes.Builder()
-                        .setUsage(USAGE_TOUCH)
+                        .setUsage(USAGE_IME_FEEDBACK)
                         .setCategory(VibrationAttributes.CATEGORY_KEYBOARD)
                         .build());
     }
 
     @Test
-    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);
@@ -652,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/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/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/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/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/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/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/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/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/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/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 5dde265..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.
@@ -91,16 +93,18 @@
 
         // 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")
         }
     }
 
@@ -221,47 +225,48 @@
         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
 
-        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 ->
 
-        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
 
-                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,
+                    maybeWithZipOutputStream(outStubJar) { stubOutStream ->
+                        maybeWithZipOutputStream(outImplJar) { implOutStream ->
+                            val inEntries = inZip.entries()
+                            while (inEntries.hasMoreElements()) {
+                                val entry = inEntries.nextElement()
+                                val inShard = (shardStart <= itemIndex)
+                                        && (itemIndex < shardNextStart)
+                                itemIndex++
+                                if (!inShard) {
+                                    continue
+                                }
+                                convertSingleEntry(
+                                    inZip, entry, stubOutStream, implOutStream,
                                     filter, packageRedirector, remapper,
-                                    enableChecker, classes, errors, stats)
-                            numItemsProcessed++
+                                    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). %d / %d item(s) processed.",
-            (end - start) / 1000.0, numItemsProcessed, numItems)
     }
 
     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/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/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.