Merge "Add Axis options to clock interface" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index bd17d6d..0ca9789 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -25,6 +25,7 @@
         "android.app.appfunctions.flags-aconfig-java",
         "android.app.contextualsearch.flags-aconfig-java",
         "android.app.flags-aconfig-java",
+        "android.app.jank.flags-aconfig-java",
         "android.app.ondeviceintelligence-aconfig-java",
         "android.app.smartspace.flags-aconfig-java",
         "android.app.supervision.flags-aconfig-java",
@@ -77,6 +78,7 @@
         "android.view.inputmethod.flags-aconfig-java",
         "android.webkit.flags-aconfig-java",
         "android.widget.flags-aconfig-java",
+        "art_exported_aconfig_flags_lib",
         "backstage_power_flags_lib",
         "backup_flags_lib",
         "camera_platform_flags_core_java_lib",
@@ -139,6 +141,14 @@
     libs: ["fake_device_config"],
 }
 
+// ART
+java_aconfig_library {
+    name: "art_exported_aconfig_flags_lib",
+    aconfig_declarations: "art-aconfig-flags",
+    mode: "exported",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // Camera
 java_aconfig_library {
     name: "camera_platform_flags_core_java_lib",
@@ -1606,3 +1616,17 @@
     aconfig_declarations: "interaction_jank_monitor_flags",
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
+
+// App Jank
+aconfig_declarations {
+    name: "android.app.jank.flags-aconfig",
+    package: "android.app.jank",
+    container: "system",
+    srcs: ["core/java/android/app/jank/flags.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.app.jank.flags-aconfig-java",
+    aconfig_declarations: "android.app.jank.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/Android.bp b/Android.bp
index 258440f..5b9f2cb 100644
--- a/Android.bp
+++ b/Android.bp
@@ -427,6 +427,7 @@
         "modules-utils-expresslog",
         "perfetto_trace_javastream_protos_jarjar",
         "libaconfig_java_proto_nano",
+        "aconfig_device_paths_java",
     ],
 }
 
diff --git a/TEST_MAPPING b/TEST_MAPPING
index dfacbc4..e469f16 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -1,44 +1,17 @@
 {
   "presubmit-large": [
     {
-      "name": "FrameworksServicesTests",
-      "options": [
-        {
-          "include-annotation": "android.platform.test.annotations.Presubmit"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        }
-      ]
+      "name": "FrameworksServicesTests_Presubmit"
     }
   ],
   "presubmit-pm": [
     {
-      "name": "PackageManagerServiceServerTests",
-      "options": [
-        {
-          "include-annotation": "android.platform.test.annotations.Presubmit"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        }
-      ]
+      "name": "PackageManagerServiceServerTests_Presubmit"
     }
   ],
   "presubmit": [
     {
-      "name": "ManagedProvisioningTests",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "ManagedProvisioningTests"
     },
     {
       "file_patterns": [
@@ -46,86 +19,28 @@
         "SystemServer\\.java",
         "services/tests/apexsystemservices/.*"
       ],
-      "name": "ApexSystemServicesTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        }
-      ]
+      "name": "ApexSystemServicesTestCases"
     },
     {
-      "name": "FrameworksUiServicesTests",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "FrameworksUiServicesTests"
     },
     {
-      "name": "FrameworksInputMethodSystemServerTests",
-      "options": [
-        {"include-filter": "com.android.server.inputmethod"},
-        {"exclude-annotation": "androidx.test.filters.FlakyTest"},
-        {"exclude-annotation": "org.junit.Ignore"}
-      ]
+      "name": "FrameworksInputMethodSystemServerTests_server_inputmethod"
     },
     {
-      "name": "ExtServicesUnitTests-tplus",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "ExtServicesUnitTests-tplus"
     },
     {
-      "name": "ExtServicesUnitTests-sminus",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "ExtServicesUnitTests-sminus"
     },
     {
-      "name": "FrameworksCoreTests",
-      "options": [
-        {
-          "include-annotation": "android.platform.test.annotations.Presubmit"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        }
-      ]
+      "name": "FrameworksCoreTests_Presubmit"
     },
     {
-      "name": "FrameworkPermissionTests",
-      "options": [
-        {
-          "include-annotation": "android.platform.test.annotations.Presubmit"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        }
-      ]
+      "name": "FrameworkPermissionTests_Presubmit"
     },
     {
-      "name": "FrameworksInProcessTests",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        }
-      ]
+      "name": "FrameworksInProcessTests"
     },
     {
       "name": "vts_treble_vintf_framework_test"
@@ -166,78 +81,25 @@
    // infra during the hardening phase.
    // TODO: this tag to be removed once the above is no longer an issue.
    {
-     "name": "FrameworksUiServicesTests",
-     "options": [
-       {
-         "exclude-annotation": "androidx.test.filters.FlakyTest"
-       }
-     ]
+     "name": "FrameworksUiServicesTests"
    },
    {
-     "name": "ExtServicesUnitTests-tplus",
-     "options": [
-       {
-         "exclude-annotation": "androidx.test.filters.FlakyTest"
-       }
-     ]
+     "name": "ExtServicesUnitTests-tplus"
    },
    {
-     "name": "ExtServicesUnitTests-sminus",
-     "options": [
-       {
-         "exclude-annotation": "androidx.test.filters.FlakyTest"
-       }
-     ]
+     "name": "ExtServicesUnitTests-sminus"
    },
    {
-     "name": "TestablesTests",
-     "options": [
-       {
-         "exclude-annotation": "androidx.test.filters.FlakyTest"
-       }
-     ]
+     "name": "TestablesTests"
    },
    {
-     "name": "FrameworksCoreTests",
-     "options": [
-       {
-         "include-annotation": "android.platform.test.annotations.Presubmit"
-       },
-       {
-         "exclude-annotation": "androidx.test.filters.FlakyTest"
-       },
-       {
-         "exclude-annotation": "org.junit.Ignore"
-       }
-     ]
+     "name": "FrameworksCoreTests_Presubmit"
    },
    {
-     "name": "FrameworksServicesTests",
-     "options": [
-       {
-         "include-annotation": "android.platform.test.annotations.Presubmit"
-       },
-       {
-         "exclude-annotation": "androidx.test.filters.FlakyTest"
-       },
-       {
-         "exclude-annotation": "org.junit.Ignore"
-       }
-     ]
+     "name": "FrameworksServicesTests_presubmit"
    },
    {
-     "name": "PackageManagerServiceServerTests",
-     "options": [
-       {
-         "include-annotation": "android.platform.test.annotations.Presubmit"
-       },
-       {
-         "exclude-annotation": "androidx.test.filters.FlakyTest"
-       },
-       {
-         "exclude-annotation": "org.junit.Ignore"
-       }
-     ]
+     "name": "PackageManagerServiceServerTests_Presubmit"
    }
  ]
 }
diff --git a/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING
index b58cb88..e3e72f4 100644
--- a/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING
+++ b/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING
@@ -11,10 +11,7 @@
   ],
   "postsubmit": [
     {
-      "name": "FrameworksMockingServicesTests",
-      "options": [
-        {"include-filter": "com.android.server"}
-      ]
+      "name": "FrameworksMockingServicesTests_android_server"
     }
   ]
 }
diff --git a/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING
index afa509c..ed8851c 100644
--- a/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING
+++ b/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING
@@ -6,10 +6,7 @@
   ],
   "postsubmit": [
     {
-      "name": "FrameworksMockingServicesTests",
-      "options": [
-        {"include-filter": "com.android.server"}
-      ]
+      "name": "FrameworksMockingServicesTests_android_server"
     }
   ]
 }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING
index a0bf78f..d198bfd 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING
+++ b/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING
@@ -1,11 +1,7 @@
 {
     "presubmit": [
         {
-            "name": "CtsJobSchedulerTestCases",
-            "options": [
-                {"exclude-annotation": "androidx.test.filters.FlakyTest"},
-                {"exclude-annotation": "androidx.test.filters.LargeTest"}
-            ]
+            "name": "CtsJobSchedulerTestCases_com_android_server_job"
         },
         {
             "name": "FrameworksMockingServicesTests_com_android_server_job_Presubmit"
@@ -19,26 +15,16 @@
             "name": "CtsJobSchedulerTestCases"
         },
         {
-            "name": "FrameworksMockingServicesTests",
-            "options": [
-                {"include-filter": "com.android.server.job"}
-            ]
+            "name": "FrameworksMockingServicesTests_com_android_server_job"
         },
         {
             "name": "FrameworksServicesTests_com_android_server_job"
         },
         {
-            "name": "CtsHostsideNetworkPolicyTests",
-            "options": [
-                {"include-filter": "com.android.cts.netpolicy.HostsideRestrictBackgroundNetworkTests#testMeteredNetworkAccess_expeditedJob"},
-                {"include-filter": "com.android.cts.netpolicy.HostsideRestrictBackgroundNetworkTests#testNonMeteredNetworkAccess_expeditedJob"}
-            ]
+            "name": "CtsHostsideNetworkPolicyTests_com_android_server_job"
         },
         {
-            "name": "CtsStatsdAtomHostTestCases",
-            "options": [
-                {"include-filter": "android.cts.statsdatom.jobscheduler"}
-            ]
+            "name": "CtsStatsdAtomHostTestCases_statsdatom_jobscheduler"
         }
     ]
 }
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING
index f56c14d..1a2013d 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING
+++ b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING
@@ -1,13 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "CtsUsageStatsTestCases",
-      "options": [
-        {"include-filter": "android.app.usage.cts.UsageStatsTest"},
-        {"exclude-annotation": "androidx.test.filters.FlakyTest"},
-        {"exclude-annotation": "androidx.test.filters.MediumTest"},
-        {"exclude-annotation": "androidx.test.filters.LargeTest"}
-      ]
+      "name": "CtsUsageStatsTestCases_cts_usagestatstest"
     },
     {
       "name": "CtsBRSTestCases"
diff --git a/cmds/locksettings/TEST_MAPPING b/cmds/locksettings/TEST_MAPPING
index af54a2d..0f502c9 100644
--- a/cmds/locksettings/TEST_MAPPING
+++ b/cmds/locksettings/TEST_MAPPING
@@ -1,15 +1,7 @@
 {
     "presubmit-large": [
         {
-            "name": "CtsDevicePolicyManagerTestCases",
-            "options": [
-                {
-                    "include-annotation": "com.android.cts.devicepolicy.annotations.LockSettingsTest"
-                },
-                {
-                    "exclude-annotation": "android.platform.test.annotations.FlakyTest"
-                }
-            ]
+            "name": "CtsDevicePolicyManagerTestCases_LockSettingsTest"
         }
     ],
     "postsubmit": [
diff --git a/cmds/screencap/Android.bp b/cmds/screencap/Android.bp
index c009c1f..16026ec 100644
--- a/cmds/screencap/Android.bp
+++ b/cmds/screencap/Android.bp
@@ -17,6 +17,7 @@
         "libutils",
         "libbinder",
         "libjnigraphics",
+        "libhwui",
         "libui",
         "libgui",
     ],
diff --git a/cmds/screencap/screencap.cpp b/cmds/screencap/screencap.cpp
index 01b20f4..12de82a 100644
--- a/cmds/screencap/screencap.cpp
+++ b/cmds/screencap/screencap.cpp
@@ -15,36 +15,28 @@
  */
 
 #include <android/bitmap.h>
+#include <android/graphics/bitmap.h>
 #include <android/gui/DisplayCaptureArgs.h>
 #include <binder/ProcessState.h>
 #include <errno.h>
-#include <unistd.h>
-#include <stdio.h>
 #include <fcntl.h>
-#include <stdlib.h>
-#include <string.h>
-#include <getopt.h>
-
-#include <linux/fb.h>
-#include <sys/ioctl.h>
-#include <sys/mman.h>
-#include <sys/wait.h>
-
-#include <android/bitmap.h>
-
-#include <binder/ProcessState.h>
-
 #include <ftl/concat.h>
 #include <ftl/optional.h>
+#include <getopt.h>
 #include <gui/ISurfaceComposer.h>
 #include <gui/SurfaceComposerClient.h>
 #include <gui/SyncScreenCaptureListener.h>
-
+#include <linux/fb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/wait.h>
+#include <system/graphics.h>
 #include <ui/GraphicTypes.h>
 #include <ui/PixelFormat.h>
 
-#include <system/graphics.h>
-
 using namespace android;
 
 #define COLORSPACE_UNKNOWN    0
@@ -85,11 +77,12 @@
 };
 }
 
-static const struct option LONG_OPTIONS[] = {
-        {"png", no_argument, nullptr, 'p'},
-        {"help", no_argument, nullptr, 'h'},
-        {"hint-for-seamless", no_argument, nullptr, LongOpts::HintForSeamless},
-        {0, 0, 0, 0}};
+static const struct option LONG_OPTIONS[] = {{"png", no_argument, nullptr, 'p'},
+                                             {"jpeg", no_argument, nullptr, 'j'},
+                                             {"help", no_argument, nullptr, 'h'},
+                                             {"hint-for-seamless", no_argument, nullptr,
+                                              LongOpts::HintForSeamless},
+                                             {0, 0, 0, 0}};
 
 static int32_t flinger2bitmapFormat(PixelFormat f)
 {
@@ -170,10 +163,11 @@
     return 0;
 }
 
-status_t saveImage(const char* fn, bool png, const ScreenCaptureResults& captureResults) {
+status_t saveImage(const char* fn, std::optional<AndroidBitmapCompressFormat> format,
+                   const ScreenCaptureResults& captureResults) {
     void* base = nullptr;
     ui::Dataspace dataspace = captureResults.capturedDataspace;
-    sp<GraphicBuffer> buffer = captureResults.buffer;
+    const sp<GraphicBuffer>& buffer = captureResults.buffer;
 
     status_t result = buffer->lock(GraphicBuffer::USAGE_SW_READ_OFTEN, &base);
 
@@ -188,22 +182,48 @@
         return 1;
     }
 
+    void* gainmapBase = nullptr;
+    sp<GraphicBuffer> gainmap = captureResults.optionalGainMap;
+
+    if (gainmap) {
+        result = gainmap->lock(GraphicBuffer::USAGE_SW_READ_OFTEN, &gainmapBase);
+        if (gainmapBase == nullptr || result != NO_ERROR) {
+            fprintf(stderr, "Failed to capture gainmap with error code (%d)\n", result);
+            gainmapBase = nullptr;
+            // Fall-through: just don't attempt to write the gainmap
+        }
+    }
+
     int fd = -1;
     if (fn == nullptr) {
         fd = dup(STDOUT_FILENO);
         if (fd == -1) {
             fprintf(stderr, "Error writing to stdout. (%s)\n", strerror(errno));
+            if (gainmapBase) {
+                gainmap->unlock();
+            }
+
+            if (base) {
+                buffer->unlock();
+            }
             return 1;
         }
     } else {
         fd = open(fn, O_WRONLY | O_CREAT | O_TRUNC, 0664);
         if (fd == -1) {
             fprintf(stderr, "Error opening file: %s (%s)\n", fn, strerror(errno));
+            if (gainmapBase) {
+                gainmap->unlock();
+            }
+
+            if (base) {
+                buffer->unlock();
+            }
             return 1;
         }
     }
 
-    if (png) {
+    if (format) {
         AndroidBitmapInfo info;
         info.format = flinger2bitmapFormat(buffer->getPixelFormat());
         info.flags = ANDROID_BITMAP_FLAGS_ALPHA_PREMUL;
@@ -211,16 +231,31 @@
         info.height = buffer->getHeight();
         info.stride = buffer->getStride() * bytesPerPixel(buffer->getPixelFormat());
 
-        int result = AndroidBitmap_compress(&info, static_cast<int32_t>(dataspace), base,
-                                            ANDROID_BITMAP_COMPRESS_FORMAT_PNG, 100, &fd,
+        int result;
+
+        if (gainmapBase) {
+            result = ABitmap_compressWithGainmap(&info, static_cast<ADataSpace>(dataspace), base,
+                                                 gainmapBase, captureResults.hdrSdrRatio, *format,
+                                                 100, &fd,
+                                                 [](void* fdPtr, const void* data,
+                                                    size_t size) -> bool {
+                                                     int bytesWritten =
+                                                             write(*static_cast<int*>(fdPtr), data,
+                                                                   size);
+                                                     return bytesWritten == size;
+                                                 });
+        } else {
+            result = AndroidBitmap_compress(&info, static_cast<int32_t>(dataspace), base, *format,
+                                            100, &fd,
                                             [](void* fdPtr, const void* data, size_t size) -> bool {
                                                 int bytesWritten = write(*static_cast<int*>(fdPtr),
                                                                          data, size);
                                                 return bytesWritten == size;
                                             });
+        }
 
         if (result != ANDROID_BITMAP_RESULT_SUCCESS) {
-            fprintf(stderr, "Failed to compress PNG (error code: %d)\n", result);
+            fprintf(stderr, "Failed to compress (error code: %d)\n", result);
         }
 
         if (fn != NULL) {
@@ -245,6 +280,14 @@
     }
     close(fd);
 
+    if (gainmapBase) {
+        gainmap->unlock();
+    }
+
+    if (base) {
+        buffer->unlock();
+    }
+
     return 0;
 }
 
@@ -262,13 +305,17 @@
     gui::CaptureArgs captureArgs;
     const char* pname = argv[0];
     bool png = false;
+    bool jpeg = false;
     bool all = false;
     int c;
-    while ((c = getopt_long(argc, argv, "aphd:", LONG_OPTIONS, nullptr)) != -1) {
+    while ((c = getopt_long(argc, argv, "apjhd:", LONG_OPTIONS, nullptr)) != -1) {
         switch (c) {
             case 'p':
                 png = true;
                 break;
+            case 'j':
+                jpeg = true;
+                break;
             case 'd': {
                 errno = 0;
                 char* end = nullptr;
@@ -325,6 +372,14 @@
             baseName = filename.substr(0, filename.size()-4);
             suffix = ".png";
             png = true;
+        } else if (filename.ends_with(".jpeg")) {
+            baseName = filename.substr(0, filename.size() - 5);
+            suffix = ".jpeg";
+            jpeg = true;
+        } else if (filename.ends_with(".jpg")) {
+            baseName = filename.substr(0, filename.size() - 4);
+            suffix = ".jpg";
+            jpeg = true;
         } else {
             baseName = filename;
         }
@@ -350,6 +405,20 @@
         }
     }
 
+    if (png && jpeg) {
+        fprintf(stderr, "Ambiguous file type");
+        return 1;
+    }
+
+    std::optional<AndroidBitmapCompressFormat> format = std::nullopt;
+
+    if (png) {
+        format = ANDROID_BITMAP_COMPRESS_FORMAT_PNG;
+    } else if (jpeg) {
+        format = ANDROID_BITMAP_COMPRESS_FORMAT_JPEG;
+        captureArgs.attachGainmap = true;
+    }
+
     // setThreadPoolMaxThreadCount(0) actually tells the kernel it's
     // not allowed to spawn any additional threads, but we still spawn
     // a binder thread from userspace when we call startThreadPool().
@@ -385,7 +454,7 @@
         if (!filename.empty()) {
             fn = filename.c_str();
         }
-        if (const status_t saveImageStatus = saveImage(fn, png, result) != 0) {
+        if (const status_t saveImageStatus = saveImage(fn, format, result) != 0) {
             fprintf(stderr, "Saving image failed.\n");
             return saveImageStatus;
         }
diff --git a/core/api/current.txt b/core/api/current.txt
index dada20e..520c7d1 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -6396,6 +6396,7 @@
     method public android.graphics.drawable.Icon getLargeIcon();
     method @Nullable public android.content.LocusId getLocusId();
     method public CharSequence getSettingsText();
+    method @FlaggedApi("android.app.api_rich_ongoing") @Nullable public String getShortCriticalText();
     method public String getShortcutId();
     method public android.graphics.drawable.Icon getSmallIcon();
     method public String getSortKey();
@@ -6719,6 +6720,7 @@
     method @NonNull public android.app.Notification.Builder setPublicVersion(android.app.Notification);
     method @NonNull public android.app.Notification.Builder setRemoteInputHistory(CharSequence[]);
     method @NonNull public android.app.Notification.Builder setSettingsText(CharSequence);
+    method @FlaggedApi("android.app.api_rich_ongoing") @NonNull public android.app.Notification.Builder setShortCriticalText(@Nullable String);
     method @NonNull public android.app.Notification.Builder setShortcutId(String);
     method @NonNull public android.app.Notification.Builder setShowWhen(boolean);
     method @NonNull public android.app.Notification.Builder setSmallIcon(@DrawableRes int);
@@ -19168,7 +19170,7 @@
     method @NonNull public java.util.List<android.hardware.camera2.CaptureRequest.Key<?>> getAvailableCaptureRequestKeys();
     method @NonNull public java.util.List<android.hardware.camera2.CaptureResult.Key<?>> getAvailableCaptureResultKeys();
     method public java.util.List<android.hardware.camera2.CaptureRequest.Key<?>> getAvailablePhysicalCameraRequestKeys();
-    method @FlaggedApi("com.android.internal.camera.flags.feature_combination_query") @NonNull public java.util.List<android.hardware.camera2.CameraCharacteristics.Key<?>> getAvailableSessionCharacteristicsKeys();
+    method @NonNull public java.util.List<android.hardware.camera2.CameraCharacteristics.Key<?>> getAvailableSessionCharacteristicsKeys();
     method public java.util.List<android.hardware.camera2.CaptureRequest.Key<?>> getAvailableSessionKeys();
     method @NonNull public java.util.List<android.hardware.camera2.CameraCharacteristics.Key<?>> getKeys();
     method @NonNull public java.util.List<android.hardware.camera2.CameraCharacteristics.Key<?>> getKeysNeedingPermission();
@@ -19211,7 +19213,7 @@
     field @FlaggedApi("com.android.internal.camera.flags.camera_manual_flash_strength_control") @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> FLASH_TORCH_STRENGTH_MAX_LEVEL;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.DeviceStateSensorOrientationMap> INFO_DEVICE_STATE_SENSOR_ORIENTATION_MAP;
-    field @FlaggedApi("com.android.internal.camera.flags.feature_combination_query") @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> INFO_SESSION_CONFIGURATION_QUERY_VERSION;
+    field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> INFO_SESSION_CONFIGURATION_QUERY_VERSION;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> INFO_SUPPORTED_HARDWARE_LEVEL;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.String> INFO_VERSION;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Size[]> JPEG_AVAILABLE_THUMBNAIL_SIZES;
@@ -20081,14 +20083,14 @@
 
   public final class ExtensionSessionConfiguration {
     ctor public ExtensionSessionConfiguration(int, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraExtensionSession.StateCallback);
-    method @FlaggedApi("com.android.internal.camera.flags.extension_10_bit") public void clearColorSpace();
-    method @FlaggedApi("com.android.internal.camera.flags.extension_10_bit") @Nullable public android.graphics.ColorSpace getColorSpace();
+    method public void clearColorSpace();
+    method @Nullable public android.graphics.ColorSpace getColorSpace();
     method @NonNull public java.util.concurrent.Executor getExecutor();
     method public int getExtension();
     method @NonNull public java.util.List<android.hardware.camera2.params.OutputConfiguration> getOutputConfigurations();
     method @Nullable public android.hardware.camera2.params.OutputConfiguration getPostviewOutputConfiguration();
     method @NonNull public android.hardware.camera2.CameraExtensionSession.StateCallback getStateCallback();
-    method @FlaggedApi("com.android.internal.camera.flags.extension_10_bit") public void setColorSpace(@NonNull android.graphics.ColorSpace.Named);
+    method public void setColorSpace(@NonNull android.graphics.ColorSpace.Named);
     method public void setPostviewOutputConfiguration(@Nullable android.hardware.camera2.params.OutputConfiguration);
   }
 
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index df707d1..1e94c2f 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -673,8 +673,8 @@
     method @Nullable public android.content.pm.PackageInfo getCurrentWebViewPackage();
     method @Nullable public String getCurrentWebViewPackageName();
     method @FlaggedApi("android.webkit.update_service_v2") @NonNull public android.webkit.WebViewProviderInfo getDefaultWebViewPackage();
-    method @Nullable public static android.webkit.WebViewUpdateManager getInstance();
-    method @NonNull @RequiresPermission(allOf={android.Manifest.permission.INTERACT_ACROSS_USERS, android.Manifest.permission.QUERY_ALL_PACKAGES}) public android.webkit.WebViewProviderInfo[] getValidWebViewPackages();
+    method @NonNull public static android.webkit.WebViewUpdateManager getInstance();
+    method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public android.webkit.WebViewProviderInfo[] getValidWebViewPackages();
     method @NonNull public android.webkit.WebViewProviderResponse waitForAndGetProvider();
   }
 
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index fb425a9..9c807af 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -4972,12 +4972,12 @@
 
   @FlaggedApi("com.android.internal.camera.flags.concert_mode") public final class CameraOutputSurface {
     ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public CameraOutputSurface(@NonNull android.view.Surface, @NonNull android.util.Size);
-    method @FlaggedApi("com.android.internal.camera.flags.extension_10_bit") public int getColorSpace();
-    method @FlaggedApi("com.android.internal.camera.flags.extension_10_bit") public long getDynamicRangeProfile();
+    method public int getColorSpace();
+    method public long getDynamicRangeProfile();
     method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public int getImageFormat();
     method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public android.util.Size getSize();
     method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public android.view.Surface getSurface();
-    method @FlaggedApi("com.android.internal.camera.flags.extension_10_bit") public void setDynamicRangeProfile(long);
+    method public void setDynamicRangeProfile(long);
   }
 
   @FlaggedApi("com.android.internal.camera.flags.concert_mode") public class CharacteristicsMap {
@@ -4987,7 +4987,7 @@
 
   @FlaggedApi("com.android.internal.camera.flags.concert_mode") public class ExtensionConfiguration {
     ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public ExtensionConfiguration(int, int, @NonNull java.util.List<android.hardware.camera2.extension.ExtensionOutputConfiguration>, @Nullable android.hardware.camera2.CaptureRequest);
-    method @FlaggedApi("com.android.internal.camera.flags.extension_10_bit") public void setColorSpace(int);
+    method public void setColorSpace(int);
   }
 
   @FlaggedApi("com.android.internal.camera.flags.concert_mode") public class ExtensionOutputConfiguration {
@@ -18769,7 +18769,7 @@
   public final class WebViewUpdateService {
     method public static android.webkit.WebViewProviderInfo[] getAllWebViewPackages();
     method public static String getCurrentWebViewPackageName();
-    method public static android.webkit.WebViewProviderInfo[] getValidWebViewPackages();
+    method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public static android.webkit.WebViewProviderInfo[] getValidWebViewPackages();
   }
 
 }
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 45852c7..93a9489 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -2408,9 +2408,9 @@
      * @hide
      */
     @android.ravenwood.annotation.RavenwoodKeep
-    public final void basicInit(Context context) {
-        mInstrContext = context;
-        mAppContext = context;
+    public final void basicInit(Context instrContext, Context appContext) {
+        mInstrContext = instrContext;
+        mAppContext = appContext;
     }
 
     /** @hide */
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 7a36fbb..81d2c89 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1281,6 +1281,15 @@
     public static final String EXTRA_BIG_TEXT = "android.bigText";
 
     /**
+     * {@link #extras} key: very short text summarizing the most critical information contained in
+     * the notification.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
+    public static final String EXTRA_SHORT_CRITICAL_TEXT = "android.shortCriticalText";
+
+    /**
      * {@link #extras} key: this is the resource ID of the notification's main small icon, as
      * supplied to {@link Builder#setSmallIcon(int)}.
      *
@@ -4050,6 +4059,17 @@
         return String.join("|", defaultStrings);
     }
 
+
+    /**
+     * Returns the very short text summarizing the most critical information contained in the
+     * notification, or null if this field was not set.
+     */
+    @Nullable
+    @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
+    public String getShortCriticalText() {
+        return extras.getString(EXTRA_SHORT_CRITICAL_TEXT);
+    }
+
     /**
      * @hide
      */
@@ -4991,6 +5011,18 @@
         }
 
         /**
+         * Sets a very short string summarizing the most critical information contained in the
+         * notification. Suggested max length is 5 characters, and there is no guarantee how much or
+         * how little of this text will be shown.
+         */
+        @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
+        @NonNull
+        public Builder setShortCriticalText(@Nullable String shortCriticalText) {
+            mN.extras.putString(EXTRA_SHORT_CRITICAL_TEXT, shortCriticalText);
+            return this;
+        }
+
+        /**
          * Set the progress this notification represents.
          *
          * The platform template will represent this using a {@link ProgressBar}.
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 32e9542..4a2b016 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -55,7 +55,9 @@
 import java.io.PrintWriter;
 import java.io.StringReader;
 import java.io.StringWriter;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -100,6 +102,11 @@
     @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
     public static final String RECS_ID = "android.app.recs";
 
+    /** @hide */
+    @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+    public static final ArrayList<String> SYSTEM_RESERVED_IDS = new ArrayList<>(
+            List.of(NEWS_ID, SOCIAL_MEDIA_ID, PROMOTIONS_ID, RECS_ID));
+
     /**
      * The formatter used by the system to create an id for notification
      * channels when it automatically creates conversation channels on behalf of an app. The format
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 5147f12..337939f 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -593,6 +593,11 @@
             @Override
             public TextServicesManager createService(ContextImpl ctx)
                     throws ServiceNotFoundException {
+                 if (ctx.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)
+                        && ServiceManager.getService(Context.TEXT_SERVICES_MANAGER_SERVICE) == null
+                        && android.server.Flags.removeTextService()) {
+                    return null;
+                }
                 return TextServicesManager.createInstance(ctx);
             }});
 
@@ -1887,6 +1892,12 @@
                         return null;
                     }
                     break;
+                case Context.TEXT_SERVICES_MANAGER_SERVICE:
+                    if (android.server.Flags.removeTextService()
+                            && hasSystemFeatureOpportunistic(ctx, PackageManager.FEATURE_WATCH)) {
+                        return null;
+                    }
+                    break;
             }
             Slog.wtf(TAG, "Manager wrapper not available: " + name);
             return null;
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index 2358d67..5ed1f4e 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -16,12 +16,7 @@
         },
         {
             "file_patterns": ["(/|^)AppOpsManager.java"],
-            "name": "CtsAppOpsTestCases",
-            "options": [
-                {
-                    "exclude-annotation": "androidx.test.filters.FlakyTest"
-                }
-            ]
+            "name": "CtsAppOpsTestCases"
         },
         {
             "file_patterns": ["(/|^)AppOpsManager.java"],
@@ -54,12 +49,7 @@
             "file_patterns": ["INotificationManager\\.aidl"]
         },
         {
-            "name": "CtsWindowManagerDeviceWindow",
-            "options": [
-                {
-                    "include-filter": "android.server.wm.window.ToastWindowTest"
-                }
-            ],
+            "name": "CtsWindowManagerDeviceWindow_window_toastwindowtest",
             "file_patterns": ["INotificationManager\\.aidl"]
         },
         {
@@ -67,42 +57,15 @@
             "file_patterns": ["(/|^)InstantAppResolve[^/]*"]
         },
         {
-            "name": "CtsAutoFillServiceTestCases",
-            "options": [
-                {
-                    "include-filter": "android.autofillservice.cts.saveui.AutofillSaveDialogTest"
-                },
-                {
-                    "exclude-annotation": "androidx.test.filters.FlakyTest"
-                }
-            ],
+            "name": "CtsAutoFillServiceTestCases_saveui_autofillsavedialogtest",
             "file_patterns": ["(/|^)Activity.java"]
         },
         {
-            "name": "CtsAutoFillServiceTestCases",
-            "options": [
-                {
-                    "include-filter": "android.autofillservice.cts.saveui.PreSimpleSaveActivityTest"
-                },
-                {
-                    "exclude-annotation": "androidx.test.filters.FlakyTest"
-                }
-            ],
+            "name": "CtsAutoFillServiceTestCases_saveui_presimplesaveactivitytest",
             "file_patterns": ["(/|^)Activity.java"]
         },
         {
-            "name": "CtsAutoFillServiceTestCases",
-            "options": [
-                {
-                    "include-filter": "android.autofillservice.cts.saveui.SimpleSaveActivityTest"
-                },
-                {
-                    "exclude-annotation": "androidx.test.filters.FlakyTest"
-                },
-                {
-                    "exclude-annotation": "android.platform.test.annotations.AppModeFull"
-                }
-            ],
+            "name": "CtsAutoFillServiceTestCases_saveui_simplesaveactivitytest",
             "file_patterns": ["(/|^)Activity.java"]
         },
         {
@@ -119,32 +82,10 @@
         },
         {
             "name": "CtsLocalVoiceInteraction",
-            "options": [
-                {
-                    "exclude-annotation": "androidx.test.filters.FlakyTest"
-                }
-            ],
             "file_patterns": ["(/|^)VoiceInteract[^/]*"]
         },
         {
-            "name": "CtsOsTestCases",
-            "options": [
-                {
-                    "include-annotation": "android.platform.test.annotations.Presubmit"
-                },
-                {
-                    "exclude-annotation": "androidx.test.filters.LargeTest"
-                },
-                {
-                    "exclude-annotation": "androidx.test.filters.FlakyTest"
-                },
-                {
-                    "exclude-annotation": "org.junit.Ignore"
-                },
-                {
-                    "include-filter": "android.os.cts.StrictModeTest"
-                }
-            ],
+            "name": "CtsOsTestCases_cts_strictmodetest_Presubmit",
             "file_patterns": ["(/|^)ContextImpl.java"]
         },
         {
@@ -153,12 +94,7 @@
         },
         {
             "file_patterns": ["(/|^)LocaleManager.java"],
-            "name": "CtsLocaleManagerTestCases",
-            "options": [
-                {
-                    "exclude-annotation": "androidx.test.filters.FlakyTest"
-                }
-            ]
+            "name": "CtsLocaleManagerTestCases"
         },
         {
             "name": "FrameworksCoreTests_keyguard_manager",
@@ -173,172 +109,51 @@
             ]
         },
         {
-            "name": "FrameworksCoreGameManagerTests",
-            "options": [
-                {
-                    "exclude-annotation": "androidx.test.filters.FlakyTest"
-                },
-                {
-                    "exclude-annotation": "org.junit.Ignore"
-                },
-                {
-                    "include-filter": "android.app"
-                }
-            ],
+            "name": "FrameworksCoreGameManagerTests_android_app",
             "file_patterns": [
                 "(/|^)GameManager[^/]*", "(/|^)GameMode[^/]*"
             ]
         },
         {
-            "name": "HdmiCecTests",
-            "options": [
-                {
-                    "exclude-annotation": "androidx.test.filters.FlakyTest"
-                },
-                {
-                    "exclude-annotation": "org.junit.Ignore"
-                },
-                {
-                    "include-filter": "android.hardware.hdmi"
-                }
-            ],
+            "name": "HdmiCecTests_hardware_hdmi",
             "file_patterns": [
                 "(/|^)DeviceFeature[^/]*", "(/|^)Hdmi[^/]*"
             ]
         },
         {
-            "name": "CtsWindowManagerDeviceActivity",
-            "options": [
-                {
-                    "exclude-annotation": "androidx.test.filters.FlakyTest"
-                },
-                {
-                    "exclude-annotation": "org.junit.Ignore"
-                },
-                {
-                    "include-filter": "android.content.wm.cts"
-                }
-            ],
+            "name": "CtsWindowManagerDeviceActivity_wm_cts",
             "file_patterns": ["(/|^)ContextImpl.java"]
         },
         {
-            "name": "CtsWindowManagerDeviceAm",
-            "options": [
-                {
-                    "exclude-annotation": "androidx.test.filters.FlakyTest"
-                },
-                {
-                    "exclude-annotation": "org.junit.Ignore"
-                },
-                {
-                    "include-filter": "android.content.wm.cts"
-                }
-            ],
+            "name": "CtsWindowManagerDeviceAm_wm_cts",
             "file_patterns": ["(/|^)ContextImpl.java"]
         },
         {
-            "name": "CtsWindowManagerDeviceBackNavigation",
-            "options": [
-                {
-                    "exclude-annotation": "androidx.test.filters.FlakyTest"
-                },
-                {
-                    "exclude-annotation": "org.junit.Ignore"
-                },
-                {
-                    "include-filter": "android.content.wm.cts"
-                }
-            ],
+            "name": "CtsWindowManagerDeviceBackNavigation_wm_cts",
             "file_patterns": ["(/|^)ContextImpl.java"]
         },
         {
-            "name": "CtsWindowManagerDeviceDisplay",
-            "options": [
-                {
-                    "exclude-annotation": "androidx.test.filters.FlakyTest"
-                },
-                {
-                    "exclude-annotation": "org.junit.Ignore"
-                },
-                {
-                    "include-filter": "android.content.wm.cts"
-                }
-            ],
+            "name": "CtsWindowManagerDeviceDisplay_wm_cts",
             "file_patterns": ["(/|^)ContextImpl.java"]
         },
         {
-            "name": "CtsWindowManagerDeviceKeyguard",
-            "options": [
-                {
-                    "exclude-annotation": "androidx.test.filters.FlakyTest"
-                },
-                {
-                    "exclude-annotation": "org.junit.Ignore"
-                },
-                {
-                    "include-filter": "android.content.wm.cts"
-                }
-            ],
+            "name": "CtsWindowManagerDeviceKeyguard_wm_cts",
             "file_patterns": ["(/|^)ContextImpl.java"]
         },
         {
-            "name": "CtsWindowManagerDeviceInsets",
-            "options": [
-                {
-                    "exclude-annotation": "androidx.test.filters.FlakyTest"
-                },
-                {
-                    "exclude-annotation": "org.junit.Ignore"
-                },
-                {
-                    "include-filter": "android.content.wm.cts"
-                }
-            ],
+            "name": "CtsWindowManagerDeviceInsets_wm_cts",
             "file_patterns": ["(/|^)ContextImpl.java"]
         },
         {
-            "name": "CtsWindowManagerDeviceTaskFragment",
-            "options": [
-                {
-                    "exclude-annotation": "androidx.test.filters.FlakyTest"
-                },
-                {
-                    "exclude-annotation": "org.junit.Ignore"
-                },
-                {
-                    "include-filter": "android.content.wm.cts"
-                }
-            ],
+            "name": "CtsWindowManagerDeviceTaskFragment_wm_cts",
             "file_patterns": ["(/|^)ContextImpl.java"]
         },
         {
-            "name": "CtsWindowManagerDeviceWindow",
-            "options": [
-                {
-                    "exclude-annotation": "androidx.test.filters.FlakyTest"
-                },
-                {
-                    "exclude-annotation": "org.junit.Ignore"
-                },
-                {
-                    "include-filter": "android.content.wm.cts"
-                }
-            ],
+            "name": "CtsWindowManagerDeviceWindow_wm_cts",
             "file_patterns": ["(/|^)ContextImpl.java"]
         },
         {
-            "name": "CtsWindowManagerDeviceOther",
-            "options": [
-                {
-                    "exclude-annotation": "androidx.test.filters.FlakyTest"
-                },
-                {
-                    "exclude-annotation": "org.junit.Ignore"
-                },
-                {
-                    "include-filter": "android.content.wm.cts"
-                }
-            ],
+            "name": "CtsWindowManagerDeviceOther_wm_cts",
             "file_patterns": ["(/|^)ContextImpl.java"]
         },
         {
diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java
index c7b0be7..46567c4 100644
--- a/core/java/android/app/admin/DeviceAdminReceiver.java
+++ b/core/java/android/app/admin/DeviceAdminReceiver.java
@@ -1107,7 +1107,7 @@
     /**
      * Called to notify the state of operations that can be unsafe to execute has changed.
      *
-     * <p><b>Note:/b> notice that the operation safety state might change between the time this
+     * <p><b>Note:</b> notice that the operation safety state might change between the time this
      * callback is received and the operation's method on {@link DevicePolicyManager} is called, so
      * calls to the latter could still throw a {@link UnsafeStateException} even when this method
      * is called with {@code isSafe} as {@code true}
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 081dfe6..48f0af4 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -323,3 +323,13 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+  name: "user_provisioning_same_state"
+  namespace: "enterprise"
+  description: "Handle exceptions while setting same provisioning state."
+  bug: "326441417"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
index f5c5a11..36daaab 100644
--- a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
+++ b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
@@ -180,13 +180,8 @@
          *
          * @param packageName the name of the package that owns the function.
          * @param functionId the id of the function.
-         * @param staticMetadataQualifiedId the qualified static metadata id that this runtime
-         *     metadata refers to.
          */
-        public Builder(
-                @NonNull String packageName,
-                @NonNull String functionId,
-                @NonNull String staticMetadataQualifiedId) {
+        public Builder(@NonNull String packageName, @NonNull String functionId) {
             super(
                     APP_FUNCTION_RUNTIME_NAMESPACE,
                     getDocumentIdForAppFunction(
@@ -198,7 +193,9 @@
 
             // Set qualified id automatically
             setPropertyString(
-                    PROPERTY_APP_FUNCTION_STATIC_METADATA_QUALIFIED_ID, staticMetadataQualifiedId);
+                    PROPERTY_APP_FUNCTION_STATIC_METADATA_QUALIFIED_ID,
+                    AppFunctionStaticMetadataHelper.getStaticMetadataQualifiedId(
+                            packageName, functionId));
         }
 
         /**
diff --git a/core/java/android/app/jank/flags.aconfig b/core/java/android/app/jank/flags.aconfig
new file mode 100644
index 0000000..5657f7e
--- /dev/null
+++ b/core/java/android/app/jank/flags.aconfig
@@ -0,0 +1,16 @@
+package: "android.app.jank"
+container: "system"
+
+flag {
+  name: "detailed_app_jank_metrics_api"
+  namespace: "system_performance"
+  description: "Control the API portion of Detailed Application Jank Metrics"
+  bug: "366264614"
+}
+
+flag {
+  name: "detailed_app_jank_metrics_logging_enabled"
+  namespace: "system_performance"
+  description: "Controls whether the system will log frame metrics related to app jank"
+  bug: "366265225"
+}
\ No newline at end of file
diff --git a/core/java/android/app/time/TEST_MAPPING b/core/java/android/app/time/TEST_MAPPING
index 7673aca..9e41638 100644
--- a/core/java/android/app/time/TEST_MAPPING
+++ b/core/java/android/app/time/TEST_MAPPING
@@ -1,31 +1,13 @@
 {
   "presubmit": [
     {
-      "name": "FrameworksTimeCoreTests",
-      "options": [
-        {
-          "include-filter": "android.app."
-        }
-      ]
+      "name": "FrameworksTimeCoreTests_android_app"
     },
     {
-      "name": "CtsTimeTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsTimeTestCases"
     },
     {
-      "name": "FrameworksTimeServicesTests",
-      "options": [
-        {
-          "include-filter": "com.android.server.timezonedetector."
-        },
-        {
-          "include-filter": "com.android.server.timedetector."
-        }
-      ]
+      "name": "FrameworksTimeServicesTests_time"
     }
   ]
 }
diff --git a/core/java/android/app/timedetector/TEST_MAPPING b/core/java/android/app/timedetector/TEST_MAPPING
index c7ca6a2..d876308 100644
--- a/core/java/android/app/timedetector/TEST_MAPPING
+++ b/core/java/android/app/timedetector/TEST_MAPPING
@@ -1,28 +1,13 @@
 {
   "presubmit": [
     {
-      "name": "FrameworksTimeCoreTests",
-      "options": [
-        {
-          "include-filter": "android.app."
-        }
-      ]
+      "name": "FrameworksTimeCoreTests_android_app"
     },
     {
-      "name": "CtsTimeTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsTimeTestCases"
     },
     {
-      "name": "FrameworksTimeServicesTests",
-      "options": [
-        {
-          "include-filter": "com.android.server.timedetector."
-        }
-      ]
+      "name": "FrameworksTimeServicesTests_server_timedetector"
     }
   ]
 }
diff --git a/core/java/android/app/timezonedetector/TEST_MAPPING b/core/java/android/app/timezonedetector/TEST_MAPPING
index c8d0bb2..dca7bbf 100644
--- a/core/java/android/app/timezonedetector/TEST_MAPPING
+++ b/core/java/android/app/timezonedetector/TEST_MAPPING
@@ -1,28 +1,13 @@
 {
   "presubmit": [
     {
-      "name": "FrameworksTimeCoreTests",
-      "options": [
-        {
-          "include-filter": "android.app."
-        }
-      ]
+      "name": "FrameworksTimeCoreTests_android_app"
     },
     {
-      "name": "CtsTimeTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsTimeTestCases"
     },
     {
-      "name": "FrameworksTimeServicesTests",
-      "options": [
-        {
-          "include-filter": "com.android.server.timezonedetector."
-        }
-      ]
+      "name": "FrameworksTimeServicesTests_server_timezonedetector"
     }
   ]
 }
diff --git a/core/java/android/app/trust/TEST_MAPPING b/core/java/android/app/trust/TEST_MAPPING
index 23923ee..b0dd551 100644
--- a/core/java/android/app/trust/TEST_MAPPING
+++ b/core/java/android/app/trust/TEST_MAPPING
@@ -1,28 +1,12 @@
 {
   "presubmit": [
     {
-      "name": "TrustTests",
-      "options": [
-        {
-          "include-filter": "android.trust.test"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "TrustTests_trust_test"
     }
   ],
   "trust-tablet": [
     {
-      "name": "TrustTests",
-      "options": [
-        {
-          "include-filter": "android.trust.test"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "TrustTests_trust_test"
     }
   ]
 }
\ No newline at end of file
diff --git a/core/java/android/content/TEST_MAPPING b/core/java/android/content/TEST_MAPPING
index e353a01..8d90b02 100644
--- a/core/java/android/content/TEST_MAPPING
+++ b/core/java/android/content/TEST_MAPPING
@@ -1,24 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "CtsOsTestCases",
-      "options": [
-        {
-          "include-annotation": "android.platform.test.annotations.Presubmit"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.LargeTest"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        },
-        {
-          "include-filter": "android.os.cts.StrictModeTest"
-        }
-      ],
+      "name": "CtsOsTestCases_cts_strictmodetest_Presubmit",
       "file_patterns": ["(/|^)Context.java", "(/|^)ContextWrapper.java"]
     },
     {
diff --git a/core/java/android/content/om/TEST_MAPPING b/core/java/android/content/om/TEST_MAPPING
index 82c47a0..b36c895 100644
--- a/core/java/android/content/om/TEST_MAPPING
+++ b/core/java/android/content/om/TEST_MAPPING
@@ -1,12 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "FrameworksServicesTests",
-      "options": [
-        {
-          "include-filter": "com.android.server.om"
-        }
-      ]
+      "name": "FrameworksServicesTests_server_om"
     },
     {
       "name": "OverlayDeviceTests"
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 26bb6e4..fb2655c 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -8842,6 +8842,7 @@
 
         try {
             ParsedPackage pp = parser2.parsePackage(apkFile, parserFlags, false);
+            pp.hideAsFinal();
 
             return PackageInfoCommonUtils.generate(pp, flagsBits, UserHandle.myUserId());
         } catch (PackageParserException e) {
diff --git a/core/java/android/content/pm/verify/domain/TEST_MAPPING b/core/java/android/content/pm/verify/domain/TEST_MAPPING
index 8a1982a..db98c40 100644
--- a/core/java/android/content/pm/verify/domain/TEST_MAPPING
+++ b/core/java/android/content/pm/verify/domain/TEST_MAPPING
@@ -1,12 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "PackageManagerServiceUnitTests",
-      "options": [
-        {
-          "include-filter": "com.android.server.pm.test.verify.domain"
-        }
-      ]
+      "name": "PackageManagerServiceUnitTests_verify_domain"
     },
     {
       "name": "CtsDomainVerificationDeviceStandaloneTestCases"
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 056ca93..7a8a16f 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -567,7 +567,6 @@
      * @see #INFO_SESSION_CONFIGURATION_QUERY_VERSION
      */
     @NonNull
-    @FlaggedApi(Flags.FLAG_FEATURE_COMBINATION_QUERY)
     public List<CameraCharacteristics.Key<?>> getAvailableSessionCharacteristicsKeys() {
         if (mAvailableSessionCharacteristicsKeys != null) {
             return mAvailableSessionCharacteristicsKeys;
@@ -5210,7 +5209,6 @@
      */
     @PublicKey
     @NonNull
-    @FlaggedApi(Flags.FLAG_FEATURE_COMBINATION_QUERY)
     public static final Key<Integer> INFO_SESSION_CONFIGURATION_QUERY_VERSION =
             new Key<Integer>("android.info.sessionConfigurationQueryVersion", int.class);
 
diff --git a/core/java/android/hardware/camera2/extension/CameraOutputSurface.java b/core/java/android/hardware/camera2/extension/CameraOutputSurface.java
index 001b794..32139b8 100644
--- a/core/java/android/hardware/camera2/extension/CameraOutputSurface.java
+++ b/core/java/android/hardware/camera2/extension/CameraOutputSurface.java
@@ -107,7 +107,6 @@
      * {@link android.hardware.camera2.params.DynamicRangeProfiles.STANDARD}
      * unless specified by CameraOutputSurface.setDynamicRangeProfile.
      */
-    @FlaggedApi(Flags.FLAG_EXTENSION_10_BIT)
     public @DynamicRangeProfiles.Profile long getDynamicRangeProfile() {
         return mOutputSurface.dynamicRangeProfile;
     }
@@ -118,7 +117,6 @@
      * unless specified by CameraOutputSurface.setColorSpace.
      */
     @SuppressLint("MethodNameUnits")
-    @FlaggedApi(Flags.FLAG_EXTENSION_10_BIT)
     public int getColorSpace() {
         return mOutputSurface.colorSpace;
     }
@@ -128,7 +126,6 @@
      * will be {@link android.hardware.camera2.params.DynamicRangeProfiles.STANDARD}
      * unless explicitly set using this method.
      */
-    @FlaggedApi(Flags.FLAG_EXTENSION_10_BIT)
     public void setDynamicRangeProfile(
             @DynamicRangeProfiles.Profile long dynamicRangeProfile) {
         mOutputSurface.dynamicRangeProfile = dynamicRangeProfile;
diff --git a/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java b/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java
index 84b7a7f..32de1ce 100644
--- a/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java
+++ b/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java
@@ -83,7 +83,6 @@
      * The default will be -1, indicating an unspecified ColorSpace,
      * unless explicitly set using this method.
      */
-    @FlaggedApi(Flags.FLAG_EXTENSION_10_BIT)
     public void setColorSpace(int colorSpace) {
         mColorSpace = colorSpace;
     }
@@ -98,11 +97,7 @@
         ret.sessionTemplateId = mSessionTemplateId;
         ret.sessionType = mSessionType;
         ret.outputConfigs = new ArrayList<>(mOutputs.size());
-        if (Flags.extension10Bit()) {
-            ret.colorSpace = mColorSpace;
-        } else {
-            ret.colorSpace = ColorSpaceProfiles.UNSPECIFIED;
-        }
+        ret.colorSpace = mColorSpace;
         for (ExtensionOutputConfiguration outputConfig : mOutputs) {
             ret.outputConfigs.add(outputConfig.getOutputConfig());
         }
diff --git a/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java b/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java
index 3a67d61..8a47430 100644
--- a/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java
+++ b/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java
@@ -80,11 +80,7 @@
         config.outputId = new OutputConfigId();
         config.outputId.id = mOutputConfigId;
         config.surfaceGroupId = mSurfaceGroupId;
-        if (Flags.extension10Bit()) {
-            config.dynamicRangeProfile = surface.getDynamicRangeProfile();
-        } else {
-            config.dynamicRangeProfile = DynamicRangeProfiles.STANDARD;
-        }
+        config.dynamicRangeProfile = surface.getDynamicRangeProfile();
     }
 
     @Nullable CameraOutputConfig getOutputConfig() {
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index 2e1e90c..323712d 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -148,7 +148,7 @@
 
         for (OutputConfiguration c : config.getOutputConfigurations()) {
             if (c.getDynamicRangeProfile() != DynamicRangeProfiles.STANDARD) {
-                if (Flags.extension10Bit() && Flags.cameraExtensionsCharacteristicsGet()) {
+                if (Flags.cameraExtensionsCharacteristicsGet()) {
                     DynamicRangeProfiles dynamicProfiles = extensionChars.get(
                             config.getExtension(),
                             CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES);
@@ -190,9 +190,7 @@
                 new IntArray(CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS.length);
         supportedCaptureOutputFormats.addAll(
                 CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS);
-        if (Flags.extension10Bit()) {
-            supportedCaptureOutputFormats.add(ImageFormat.YCBCR_P010);
-        }
+        supportedCaptureOutputFormats.add(ImageFormat.YCBCR_P010);
         for (int format : supportedCaptureOutputFormats.toArray()) {
             List<Size> supportedSizes = extensionChars.getExtensionSupportedSizes(
                     config.getExtension(), format);
@@ -359,22 +357,20 @@
             cameraOutput.setTimestampBase(OutputConfiguration.TIMESTAMP_BASE_SENSOR);
             cameraOutput.setReadoutTimestampEnabled(false);
             cameraOutput.setPhysicalCameraId(output.physicalCameraId);
-            if (Flags.extension10Bit()) {
-                boolean validDynamicRangeProfile = false;
-                for (long profile = DynamicRangeProfiles.STANDARD;
-                        profile < DynamicRangeProfiles.PUBLIC_MAX; profile <<= 1) {
-                    if (output.dynamicRangeProfile == profile) {
-                        validDynamicRangeProfile = true;
-                        break;
-                    }
+            boolean validDynamicRangeProfile = false;
+            for (long profile = DynamicRangeProfiles.STANDARD;
+                    profile < DynamicRangeProfiles.PUBLIC_MAX; profile <<= 1) {
+                if (output.dynamicRangeProfile == profile) {
+                    validDynamicRangeProfile = true;
+                    break;
                 }
-                if (validDynamicRangeProfile) {
-                    cameraOutput.setDynamicRangeProfile(output.dynamicRangeProfile);
-                } else {
-                    Log.e(TAG, "Extension configured dynamic range profile "
-                            + output.dynamicRangeProfile
-                            + " is not valid, using default DynamicRangeProfile.STANDARD");
-                }
+            }
+            if (validDynamicRangeProfile) {
+                cameraOutput.setDynamicRangeProfile(output.dynamicRangeProfile);
+            } else {
+                Log.e(TAG, "Extension configured dynamic range profile "
+                        + output.dynamicRangeProfile
+                        + " is not valid, using default DynamicRangeProfile.STANDARD");
             }
             outputList.add(cameraOutput);
             mCameraConfigMap.put(cameraOutput.getSurface(), output);
@@ -390,15 +386,13 @@
         SessionConfiguration sessionConfiguration = new SessionConfiguration(sessionType,
                 outputList, new CameraExtensionUtils.HandlerExecutor(mHandler),
                 new SessionStateHandler());
-        if (Flags.extension10Bit()) {
-            if (sessionConfig.colorSpace >= 0
-                    && sessionConfig.colorSpace < ColorSpace.Named.values().length) {
-                sessionConfiguration.setColorSpace(
-                        ColorSpace.Named.values()[sessionConfig.colorSpace]);
-            } else {
-                Log.e(TAG, "Extension configured color space " + sessionConfig.colorSpace
-                        + " is not valid, using default unspecified color space");
-            }
+        if (sessionConfig.colorSpace >= 0
+                && sessionConfig.colorSpace < ColorSpace.Named.values().length) {
+            sessionConfiguration.setColorSpace(
+                    ColorSpace.Named.values()[sessionConfig.colorSpace]);
+        } else {
+            Log.e(TAG, "Extension configured color space " + sessionConfig.colorSpace
+                    + " is not valid, using default unspecified color space");
         }
         if ((sessionConfig.sessionParameter != null) &&
                 (!sessionConfig.sessionParameter.isEmpty())) {
@@ -459,16 +453,11 @@
             ret.size.height = surfaceSize.getHeight();
             ret.imageFormat = SurfaceUtils.getSurfaceFormat(s);
 
-            if (Flags.extension10Bit()) {
-                ret.dynamicRangeProfile = o.getDynamicRangeProfile();
-                ColorSpace colorSpace = o.getColorSpace();
-                if (colorSpace != null) {
-                    ret.colorSpace = colorSpace.getId();
-                } else {
-                    ret.colorSpace = ColorSpaceProfiles.UNSPECIFIED;
-                }
+            ret.dynamicRangeProfile = o.getDynamicRangeProfile();
+            ColorSpace colorSpace = o.getColorSpace();
+            if (colorSpace != null) {
+                ret.colorSpace = colorSpace.getId();
             } else {
-                ret.dynamicRangeProfile = DynamicRangeProfiles.STANDARD;
                 ret.colorSpace = ColorSpaceProfiles.UNSPECIFIED;
             }
         } else {
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java b/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java
index a10e250..ab4ff72 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java
@@ -208,10 +208,6 @@
     }
 
     public void onOutputSurface(Surface surface, int format) throws RemoteException {
-        if (!Flags.extension10Bit() && format != ImageFormat.JPEG) {
-            Log.e(TAG, "Unsupported output format: " + format);
-            return;
-        }
         CameraExtensionUtils.SurfaceInfo surfaceInfo = CameraExtensionUtils.querySurface(surface);
         mCaptureFormat = surfaceInfo.mFormat;
         mOutputSurface = surface;
@@ -221,10 +217,6 @@
     public void onPostviewOutputSurface(Surface surface) throws RemoteException {
         CameraExtensionUtils.SurfaceInfo postviewSurfaceInfo =
                 CameraExtensionUtils.querySurface(surface);
-        if (!Flags.extension10Bit() && postviewSurfaceInfo.mFormat != ImageFormat.JPEG) {
-            Log.e(TAG, "Unsupported output format: " + postviewSurfaceInfo.mFormat);
-            return;
-        }
         mPostviewFormat = postviewSurfaceInfo.mFormat;
         mPostviewOutputSurface = surface;
         initializePostviewPipeline();
@@ -240,10 +232,6 @@
     }
 
     public void onImageFormatUpdate(int format) throws RemoteException {
-        if (!Flags.extension10Bit() && format != ImageFormat.YUV_420_888) {
-            Log.e(TAG, "Unsupported input format: " + format);
-            return;
-        }
         mFormat = format;
         initializePipeline();
     }
@@ -251,7 +239,7 @@
     private void initializePipeline() throws RemoteException {
         if ((mFormat != -1) && (mOutputSurface != null) && (mResolution != null) &&
                 (mYuvReader == null)) {
-            if (Flags.extension10Bit() && mCaptureFormat == ImageFormat.YUV_420_888) {
+            if (mCaptureFormat == ImageFormat.YUV_420_888) {
                 // For the case when postview is JPEG and capture is YUV
                 mProcessor.onOutputSurface(mOutputSurface, mCaptureFormat);
             } else {
@@ -274,7 +262,7 @@
     private void initializePostviewPipeline() throws RemoteException {
         if ((mFormat != -1) && (mPostviewOutputSurface != null) && (mPostviewResolution != null)
                 && (mPostviewYuvReader == null)) {
-            if (Flags.extension10Bit() && mPostviewFormat == ImageFormat.YUV_420_888) {
+            if (mPostviewFormat == ImageFormat.YUV_420_888) {
                 // For the case when postview is YUV and capture is JPEG
                 mProcessor.onPostviewOutputSurface(mPostviewOutputSurface);
             } else {
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index a4ae398..ce1609d 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -190,9 +190,7 @@
                 new IntArray(CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS.length);
         supportedCaptureOutputFormats.addAll(
                 CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS);
-        if (Flags.extension10Bit()) {
-            supportedCaptureOutputFormats.add(ImageFormat.YCBCR_P010);
-        }
+        supportedCaptureOutputFormats.add(ImageFormat.YCBCR_P010);
         for (int format : supportedCaptureOutputFormats.toArray()) {
             List<Size> supportedSizes = extensionChars.getExtensionSupportedSizes(
                     config.getExtension(), format);
@@ -401,7 +399,7 @@
                 if (surfaceInfo.mFormat == ImageFormat.JPEG) {
                     mImageJpegProcessor = new CameraExtensionJpegProcessor(mImageProcessor);
                     mImageProcessor = mImageJpegProcessor;
-                } else if (Flags.extension10Bit() && mClientPostviewSurface != null) {
+                } else if (mClientPostviewSurface != null) {
                     // Handles case when postview is JPEG and capture is YUV
                     CameraExtensionUtils.SurfaceInfo postviewSurfaceInfo =
                             CameraExtensionUtils.querySurface(mClientPostviewSurface);
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
index 40f0477..f91d277 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
@@ -113,32 +113,13 @@
 
         SurfaceInfo surfaceInfo = querySurface(outputConfig.getSurface());
 
-        if (Flags.extension10Bit()) {
-            Size postviewSize = new Size(surfaceInfo.mWidth, surfaceInfo.mHeight);
-            if (supportedPostviewSizes.get(surfaceInfo.mFormat)
-                    .contains(postviewSize)) {
-                return outputConfig.getSurface();
-            } else {
-                throw new IllegalArgumentException("Postview size not supported!");
-            }
+        Size postviewSize = new Size(surfaceInfo.mWidth, surfaceInfo.mHeight);
+        if (supportedPostviewSizes.get(surfaceInfo.mFormat)
+                .contains(postviewSize)) {
+            return outputConfig.getSurface();
         } else {
-            if (surfaceInfo.mFormat == captureFormat) {
-                if (supportedPostviewSizes.containsKey(captureFormat)) {
-                    Size postviewSize = new Size(surfaceInfo.mWidth, surfaceInfo.mHeight);
-                    if (supportedPostviewSizes.get(surfaceInfo.mFormat)
-                            .contains(postviewSize)) {
-                        return outputConfig.getSurface();
-                    } else {
-                        throw new IllegalArgumentException("Postview size not supported!");
-                    }
-                }
-            } else {
-                throw new IllegalArgumentException("Postview format should be equivalent to "
-                        + " the capture format!");
-            }
+            throw new IllegalArgumentException("Postview size not supported!");
         }
-
-        return null;
     }
 
     public static Surface getBurstCaptureSurface(
@@ -148,9 +129,7 @@
                 new IntArray(CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS.length);
         supportedCaptureOutputFormats.addAll(
                 CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS);
-        if (Flags.extension10Bit()) {
-            supportedCaptureOutputFormats.add(ImageFormat.YCBCR_P010);
-        }
+        supportedCaptureOutputFormats.add(ImageFormat.YCBCR_P010);
         for (OutputConfiguration config : outputConfigs) {
             SurfaceInfo surfaceInfo = querySurface(config.getSurface());
             for (int supportedFormat : supportedCaptureOutputFormats.toArray()) {
diff --git a/core/java/android/hardware/camera2/params/ExtensionSessionConfiguration.java b/core/java/android/hardware/camera2/params/ExtensionSessionConfiguration.java
index bf3f59f..73f4ff4 100644
--- a/core/java/android/hardware/camera2/params/ExtensionSessionConfiguration.java
+++ b/core/java/android/hardware/camera2/params/ExtensionSessionConfiguration.java
@@ -136,7 +136,6 @@
      * or the color space implied by the dataSpace passed into an {@link ImageReader}'s
      * constructor.</p>
      */
-    @FlaggedApi(Flags.FLAG_EXTENSION_10_BIT)
     public void setColorSpace(@NonNull ColorSpace.Named colorSpace) {
         mColorSpace = colorSpace.ordinal();
         for (OutputConfiguration outputConfiguration : mOutputs) {
@@ -150,7 +149,6 @@
     /**
      * Clear the color space, such that the default color space will be used.
      */
-    @FlaggedApi(Flags.FLAG_EXTENSION_10_BIT)
     public void clearColorSpace() {
         mColorSpace = ColorSpaceProfiles.UNSPECIFIED;
         for (OutputConfiguration outputConfiguration : mOutputs) {
@@ -167,7 +165,6 @@
      * @return the currently set color space, or null
      *         if not set
      */
-    @FlaggedApi(Flags.FLAG_EXTENSION_10_BIT)
     @SuppressLint("MethodNameUnits")
     public @Nullable ColorSpace getColorSpace() {
         if (mColorSpace != ColorSpaceProfiles.UNSPECIFIED) {
diff --git a/core/java/android/hardware/input/VirtualInputDeviceConfig.java b/core/java/android/hardware/input/VirtualInputDeviceConfig.java
index a87980c..e8ef8cd 100644
--- a/core/java/android/hardware/input/VirtualInputDeviceConfig.java
+++ b/core/java/android/hardware/input/VirtualInputDeviceConfig.java
@@ -57,7 +57,7 @@
         mVendorId = builder.mVendorId;
         mProductId = builder.mProductId;
         mAssociatedDisplayId = builder.mAssociatedDisplayId;
-        mInputDeviceName = Objects.requireNonNull(builder.mInputDeviceName);
+        mInputDeviceName = Objects.requireNonNull(builder.mInputDeviceName, "Missing device name");
 
         if (mAssociatedDisplayId == Display.INVALID_DISPLAY) {
             throw new IllegalArgumentException(
@@ -77,7 +77,7 @@
         mVendorId = in.readInt();
         mProductId = in.readInt();
         mAssociatedDisplayId = in.readInt();
-        mInputDeviceName = Objects.requireNonNull(in.readString8());
+        mInputDeviceName = Objects.requireNonNull(in.readString8(), "Missing device name");
     }
 
     /**
diff --git a/core/java/android/net/TEST_MAPPING b/core/java/android/net/TEST_MAPPING
index 3df5616..ea509bb 100644
--- a/core/java/android/net/TEST_MAPPING
+++ b/core/java/android/net/TEST_MAPPING
@@ -19,21 +19,7 @@
   ],
   "presubmit": [
     {
-      "name": "FrameworksCoreTests",
-      "options": [
-        {
-          "include-filter": "android.net"
-        },
-        {
-          "include-annotation": "android.platform.test.annotations.Presubmit"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        }
-      ]
+      "name": "FrameworksCoreTests_android_net"
     }
   ]
 }
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index c4d12d4..996a288 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -2066,9 +2066,11 @@
         public static final int EVENT_LONG_WAKE_LOCK = 0x0014;
         // Event for reporting change of some device states, triggered by a specific UID
         public static final int EVENT_STATE_CHANGE = 0x0015;
+        // Event for reporting change of screen states.
+        public static final int EVENT_DISPLAY_STATE_CHANGED = 0x0016;
 
         // Number of event types.
-        public static final int EVENT_COUNT = 0x0016;
+        public static final int EVENT_COUNT = 0x0017;
         // Mask to extract out only the type part of the event.
         public static final int EVENT_TYPE_MASK = ~(EVENT_FLAG_START|EVENT_FLAG_FINISH);
 
@@ -3079,13 +3081,14 @@
     public static final String[] HISTORY_EVENT_NAMES = new String[] {
             "null", "proc", "fg", "top", "sync", "wake_lock_in", "job", "user", "userfg", "conn",
             "active", "pkginst", "pkgunin", "alarm", "stats", "pkginactive", "pkgactive",
-            "tmpwhitelist", "screenwake", "wakeupap", "longwake", "state"
+            "tmpwhitelist", "screenwake", "wakeupap", "longwake", "state",
+            "display_state_changed"
     };
 
     public static final String[] HISTORY_EVENT_CHECKIN_NAMES = new String[] {
             "Enl", "Epr", "Efg", "Etp", "Esy", "Ewl", "Ejb", "Eur", "Euf", "Ecn",
             "Eac", "Epi", "Epu", "Eal", "Est", "Eai", "Eaa", "Etw",
-            "Esw", "Ewa", "Elw", "Eec", "Esc"
+            "Esw", "Ewa", "Elw", "Eec", "Esc", "Eds"
     };
 
     @FunctionalInterface
diff --git a/core/java/android/os/IpcDataCache.java b/core/java/android/os/IpcDataCache.java
index bf44d65..0776cf4 100644
--- a/core/java/android/os/IpcDataCache.java
+++ b/core/java/android/os/IpcDataCache.java
@@ -16,6 +16,7 @@
 
 package android.os;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.StringDef;
@@ -551,7 +552,7 @@
     }
 
     /**
-     * An interface suitable for a lambda expression instead of a QueryHandler.
+     * An interface suitable for a lambda expression instead of a QueryHandler applying remote call.
      * @hide
      */
     public interface RemoteCall<Query, Result> {
@@ -559,6 +560,14 @@
     }
 
     /**
+     * An interface suitable for a lambda expression instead of a QueryHandler bypassing the cache.
+     * @hide
+     */
+    public interface BypassCall<Query> {
+        Boolean apply(Query query);
+    }
+
+    /**
      * This is a query handler that is created with a lambda expression that is invoked
      * every time the handler is called.  The handler is specifically meant for services
      * hosted by system_server; the handler automatically rethrows RemoteException as a
@@ -580,11 +589,54 @@
         }
     }
 
+
     /**
      * Create a cache using a config and a lambda expression.
+     * @param config The configuration for the cache.
+     * @param remoteCall The lambda expression that will be invoked to fetch the data.
      * @hide
      */
-    public IpcDataCache(@NonNull Config config, @NonNull RemoteCall<Query, Result> computer) {
-        this(config, new SystemServerCallHandler<>(computer));
+    public IpcDataCache(@NonNull Config config, @NonNull RemoteCall<Query, Result> remoteCall) {
+      this(config, android.multiuser.Flags.cachingDevelopmentImprovements() ?
+        new QueryHandler<Query, Result>() {
+            @Override
+            public Result apply(Query query) {
+                try {
+                    return remoteCall.apply(query);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+        } : new SystemServerCallHandler<>(remoteCall));
+    }
+
+
+    /**
+     * Create a cache using a config and a lambda expression.
+     * @param config The configuration for the cache.
+     * @param remoteCall The lambda expression that will be invoked to fetch the data.
+     * @param bypass The lambda expression that will be invoked to determine if the cache should be
+     *     bypassed.
+     * @hide
+     */
+    @FlaggedApi(android.multiuser.Flags.FLAG_CACHING_DEVELOPMENT_IMPROVEMENTS)
+    public IpcDataCache(@NonNull Config config,
+            @NonNull RemoteCall<Query, Result> remoteCall,
+            @NonNull BypassCall<Query> bypass) {
+        this(config, new QueryHandler<Query, Result>() {
+            @Override
+            public Result apply(Query query) {
+                try {
+                    return remoteCall.apply(query);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+
+            @Override
+            public boolean shouldBypassCache(Query query) {
+                return bypass.apply(query);
+            }
+        });
     }
 }
diff --git a/core/java/android/os/SystemVibratorManager.java b/core/java/android/os/SystemVibratorManager.java
index 58ab5b6..cfbf528 100644
--- a/core/java/android/os/SystemVibratorManager.java
+++ b/core/java/android/os/SystemVibratorManager.java
@@ -138,11 +138,14 @@
             Log.w(TAG, "Failed to vibrate; no vibrator manager service.");
             return;
         }
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate, reason=" + reason);
         try {
             mService.vibrate(uid, mContext.getDeviceId(), opPkg, effect, attributes, reason,
                     mToken);
         } catch (RemoteException e) {
             Log.w(TAG, "Failed to vibrate.", e);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
         }
     }
 
@@ -152,11 +155,14 @@
             Log.w(TAG, "Failed to perform haptic feedback; no vibrator manager service.");
             return;
         }
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "performHapticFeedback, reason=" + reason);
         try {
             mService.performHapticFeedback(mUid, mContext.getDeviceId(), mPackageName, constant,
                     reason, flags, privFlags);
         } catch (RemoteException e) {
             Log.w(TAG, "Failed to perform haptic feedback.", e);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
         }
     }
 
@@ -168,11 +174,15 @@
                             + " no vibrator manager service.");
             return;
         }
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR,
+                "performHapticFeedbackForInputDevice, reason=" + reason);
         try {
             mService.performHapticFeedbackForInputDevice(mUid, mContext.getDeviceId(), mPackageName,
                     constant, inputDeviceId, inputSource, reason, flags, privFlags);
         } catch (RemoteException e) {
             Log.w(TAG, "Failed to perform haptic feedback for input device.", e);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
         }
     }
 
diff --git a/core/java/android/os/TEST_MAPPING b/core/java/android/os/TEST_MAPPING
index 728db27..effe555 100644
--- a/core/java/android/os/TEST_MAPPING
+++ b/core/java/android/os/TEST_MAPPING
@@ -38,33 +38,15 @@
     },
     {
       "file_patterns": ["Bugreport[^/]*\\.java"],
-      "name": "BugreportManagerTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.LargeTest"
-        }
-      ]
+      "name": "BugreportManagerTestCases_android_server_os"
     },
     {
       "file_patterns": ["Bugreport[^/]*\\.java"],
-      "name": "CtsBugreportTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.LargeTest"
-        }
-      ]
+      "name": "CtsBugreportTestCases_android_server_os"
     },
     {
       "file_patterns": ["Bugreport[^/]*\\.java"],
-      "name": "ShellTests",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.LargeTest"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "ShellTests_android_server_os"
     },
     {
       "file_patterns": [
@@ -99,12 +81,7 @@
         "Parcel\\.java",
         "[^/]*Bundle[^/]*\\.java"
       ],
-      "name": "FrameworksMockingCoreTests",
-      "options": [
-        { "include-filter":  "android.os.BundleRecyclingTest"},
-        { "exclude-annotation": "androidx.test.filters.FlakyTest" },
-        { "exclude-annotation": "org.junit.Ignore" }
-      ]
+      "name": "FrameworksMockingCoreTests_os_bundlerecyclingtest"
     },
     {
       "file_patterns": [
@@ -116,12 +93,7 @@
     },
     {
       "file_patterns": ["SharedMemory[^/]*\\.java"],
-      "name": "CtsOsTestCases",
-      "options": [
-        {
-          "include-filter": "android.os.cts.SharedMemoryTest"
-        }
-      ]
+      "name": "CtsOsTestCases_cts_sharedmemorytest"
     },
     {
       "file_patterns": ["Environment[^/]*\\.java"],
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index f1ec0e4e..a4a7a98 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1539,10 +1539,19 @@
      * Specifies that the managed profile is not allowed to have unified lock screen challenge with
      * the primary user.
      *
-     * <p><strong>Note:</strong> Setting this restriction alone doesn't automatically set a
-     * separate challenge. Profile owner can ask the user to set a new password using
-     * {@link DevicePolicyManager#ACTION_SET_NEW_PASSWORD} and verify it using
-     * {@link DevicePolicyManager#isUsingUnifiedPassword(ComponentName)}.
+     * <p>To ensure that there is a separate work profile password, IT admins
+     * have to:
+     * <ol>
+     *   <li>Enforce {@link UserManager#DISALLOW_UNIFIED_PASSWORD}</li>
+     *   <li>Verify that {@link DevicePolicyManager#isUsingUnifiedPassword(ComponentName)}
+     *       returns true. This indicates that there is now a separate work
+     *       profile password configured and the set up is completed.</li>
+     *   <li>In case {@link DevicePolicyManager#isUsingUnifiedPassword(ComponentName)}
+     *       returns false, invoke {@link DevicePolicyManager#ACTION_SET_NEW_PASSWORD}
+     *       intent and then verify again
+     *       {@link DevicePolicyManager#isUsingUnifiedPassword(ComponentName)}.</li>
+     * </ol>
+     * </p>
      *
      * <p>Can be set by profile owners. It only has effect on managed profiles when set by managed
      * profile owner. Has no effect on non-managed profiles or users.
diff --git a/core/java/android/permission/TEST_MAPPING b/core/java/android/permission/TEST_MAPPING
index b317b80..3e5a131 100644
--- a/core/java/android/permission/TEST_MAPPING
+++ b/core/java/android/permission/TEST_MAPPING
@@ -6,26 +6,10 @@
     ],
     "postsubmit": [
         {
-            "name": "CtsVirtualDevicesAudioTestCases",
-            "options": [
-                {
-                    "exclude-annotation": "androidx.test.filters.FlakyTest"
-                },
-                {
-                    "include-filter": "android.virtualdevice.cts.audio.VirtualAudioPermissionTest"
-                }
-            ]
+            "name": "CtsVirtualDevicesAudioTestCases_audio_virtualaudiopermissiontest"
         },
         {
-            "name": "CtsVirtualDevicesAppLaunchTestCases",
-            "options": [
-                {
-                    "exclude-annotation": "androidx.test.filters.FlakyTest"
-                },
-                {
-                    "include-filter": "android.virtualdevice.cts.applaunch.VirtualDevicePermissionTest"
-                }
-            ]
+            "name": "CtsVirtualDevicesAppLaunchTestCases_applaunch_virtualdevicepermissiontest"
         }
     ]
 }
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 991611a..b0791e3 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -257,3 +257,11 @@
     description: "This flag is used to enable replacing permission BODY_SENSORS(and BODY_SENSORS_BACKGROUND) with granular health permission READ_HEART_RATE(and READ_HEALTH_DATA_IN_BACKGROUND)"
     bug: "364638912"
 }
+
+flag {
+    name: "appop_access_tracking_logging_enabled"
+    is_fixed_read_only: true
+    namespace: "permissions"
+    description: "Enables logging of the AppOp access tracking"
+    bug: "365584286"
+}
diff --git a/core/java/android/print/TEST_MAPPING b/core/java/android/print/TEST_MAPPING
index 4fa8822..1033b1a 100644
--- a/core/java/android/print/TEST_MAPPING
+++ b/core/java/android/print/TEST_MAPPING
@@ -1,12 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "CtsPrintTestCases",
-      "options": [
-        {
-          "include-annotation": "android.platform.test.annotations.Presubmit"
-        }
-      ]
+      "name": "CtsPrintTestCases_Presubmit"
     }
   ]
 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 0a05f70..e32625e 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -17818,6 +17818,12 @@
         public static final String FORCE_NON_DEBUGGABLE_FINAL_BUILD_FOR_COMPAT =
                 "force_non_debuggable_final_build_for_compat";
 
+        /**
+         * Flag to enable the use of ApplicationInfo for getting not-launched status.
+         *
+         * @hide
+         */
+        public static final String ENABLE_USE_APP_INFO_NOT_LAUNCHED = "use_app_info_not_launched";
 
         /**
          * Current version of signed configuration applied.
diff --git a/core/java/android/provider/TEST_MAPPING b/core/java/android/provider/TEST_MAPPING
index 2eb285d..a6fe301 100644
--- a/core/java/android/provider/TEST_MAPPING
+++ b/core/java/android/provider/TEST_MAPPING
@@ -24,12 +24,7 @@
             "name": "SettingsProviderTest"
         },
         {
-            "name": "CtsPackageManagerHostTestCases",
-            "options": [
-                {
-                    "include-filter": "android.appsecurity.cts.ReadableSettingsFieldsTest"
-                }
-            ]
+            "name": "CtsPackageManagerHostTestCases_cts_readablesettingsfieldstest"
         }
     ],
     "postsubmit": [
diff --git a/core/java/android/security/TEST_MAPPING b/core/java/android/security/TEST_MAPPING
index 5a679b1..e1c7f3c 100644
--- a/core/java/android/security/TEST_MAPPING
+++ b/core/java/android/security/TEST_MAPPING
@@ -1,12 +1,7 @@
 {
     "presubmit": [
         {
-            "name": "CtsSecurityTestCases",
-            "options": [
-                {
-                    "include-filter": "android.security.cts.FileIntegrityManagerTest"
-                }
-            ],
+            "name": "CtsSecurityTestCases_cts_fileintegritymanagertest",
             "file_patterns": [
                 "FileIntegrityManager\\.java",
                 "IFileIntegrityService\\.aidl"
diff --git a/core/java/android/security/attestationverification/AttestationVerificationManager.java b/core/java/android/security/attestationverification/AttestationVerificationManager.java
index 2e61db1..acf3382 100644
--- a/core/java/android/security/attestationverification/AttestationVerificationManager.java
+++ b/core/java/android/security/attestationverification/AttestationVerificationManager.java
@@ -322,6 +322,10 @@
     /** Requirements bundle parameter for a challenge. */
     public static final String PARAM_CHALLENGE = "localbinding.challenge";
 
+    /** Requirements bundle parameter for max patch level diff (int) for a peer device. **/
+    public static final String PARAM_MAX_PATCH_LEVEL_DIFF_MONTHS =
+            "param_max_patch_level_diff_months";
+
     /** @hide */
     public static String localBindingTypeToString(@LocalBindingType int localBindingType) {
         final String text;
diff --git a/core/java/android/security/attestationverification/OWNERS b/core/java/android/security/attestationverification/OWNERS
index 80a1f44..15b9ce4 100644
--- a/core/java/android/security/attestationverification/OWNERS
+++ b/core/java/android/security/attestationverification/OWNERS
@@ -2,3 +2,6 @@
 
 dlm@google.com
 dkrahn@google.com
+guojing@google.com
+raphk@google.com
+yukl@google.com
\ No newline at end of file
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index f6f0eff..a86c961 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -99,3 +99,10 @@
   description: "Causes TrustManagerService to listen for credential attempts and ignore reports from upstream"
   bug: "323086607"
 }
+
+flag {
+    name: "clear_strong_auth_on_add_primary_credential"
+    namespace: "biometrics"
+    description: "Clear StrongAuth on add credential"
+    bug: "320817991"
+}
diff --git a/core/java/android/service/notification/TEST_MAPPING b/core/java/android/service/notification/TEST_MAPPING
index 468c451..dc7129cd 100644
--- a/core/java/android/service/notification/TEST_MAPPING
+++ b/core/java/android/service/notification/TEST_MAPPING
@@ -1,32 +1,10 @@
 {
   "presubmit": [
     {
-      "name": "CtsNotificationTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.LargeTest"
-        }
-      ]
+      "name": "CtsNotificationTestCases_notification"
     },
     {
-      "name": "FrameworksUiServicesTests",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.LargeTest"
-        }
-      ]
+      "name": "FrameworksUiServicesTests_notification"
     }
   ],
   "postsubmit": [
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 3d8d933..752f174 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -2553,7 +2553,7 @@
         if (!Flags.modesUi()) {
             return manualRule != null;
         }
-        return manualRule != null && manualRule.isAutomaticActive();
+        return manualRule != null && manualRule.isActive();
     }
 
     public static class ZenRule implements Parcelable {
@@ -2932,8 +2932,7 @@
             }
         }
 
-        // TODO: b/363193376 - Rename to isActive()
-        public boolean isAutomaticActive() {
+        public boolean isActive() {
             if (Flags.modesApi() && Flags.modesUi()) {
                 if (!enabled || getPkg() == null) {
                     return false;
@@ -3173,7 +3172,7 @@
 
         // DND turned on by an automatic rule
         for (ZenRule automaticRule : config.automaticRules.values()) {
-            if (automaticRule.isAutomaticActive()) {
+            if (automaticRule.isActive()) {
                 if (isValidEventConditionId(automaticRule.conditionId)
                         || isValidScheduleConditionId(automaticRule.conditionId)) {
                     // set text if automatic rule end time is the latest active rule end time
diff --git a/core/java/android/service/notification/ZenModeDiff.java b/core/java/android/service/notification/ZenModeDiff.java
index 05c2a9c..60a7d6b 100644
--- a/core/java/android/service/notification/ZenModeDiff.java
+++ b/core/java/android/service/notification/ZenModeDiff.java
@@ -495,8 +495,8 @@
 
             // Even if added or removed, there may be a change in whether or not it was active.
             // This only applies to automatic rules.
-            boolean fromActive = from != null ? from.isAutomaticActive() : false;
-            boolean toActive = to != null ? to.isAutomaticActive() : false;
+            boolean fromActive = from != null ? from.isActive() : false;
+            boolean toActive = to != null ? to.isActive() : false;
             if (fromActive != toActive) {
                 mActiveDiff = new FieldDiff<>(fromActive, toActive);
             }
diff --git a/core/java/android/service/quicksettings/TEST_MAPPING b/core/java/android/service/quicksettings/TEST_MAPPING
index 2d45c5b2..986dc5f 100644
--- a/core/java/android/service/quicksettings/TEST_MAPPING
+++ b/core/java/android/service/quicksettings/TEST_MAPPING
@@ -1,15 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "CtsTileServiceTestCases",
-      "options": [
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsTileServiceTestCases"
     }
   ]
 }
\ No newline at end of file
diff --git a/core/java/android/service/timezone/TEST_MAPPING b/core/java/android/service/timezone/TEST_MAPPING
index bf46ff2..2071717 100644
--- a/core/java/android/service/timezone/TEST_MAPPING
+++ b/core/java/android/service/timezone/TEST_MAPPING
@@ -1,12 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "FrameworksTimeCoreTests",
-      "options": [
-        {
-          "include-filter": "android.service."
-        }
-      ]
+      "name": "FrameworksTimeCoreTests_android_service"
     },
     {
       "name": "CtsLocationTimeZoneManagerHostTest"
diff --git a/core/java/android/speech/TEST_MAPPING b/core/java/android/speech/TEST_MAPPING
index 7b125c2..cb490f5b 100644
--- a/core/java/android/speech/TEST_MAPPING
+++ b/core/java/android/speech/TEST_MAPPING
@@ -1,15 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "CtsVoiceRecognitionTestCases",
-      "options": [
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsVoiceRecognitionTestCases"
     }
   ]
 }
diff --git a/core/java/android/text/TEST_MAPPING b/core/java/android/text/TEST_MAPPING
index c9bd2ca..9f8a72c 100644
--- a/core/java/android/text/TEST_MAPPING
+++ b/core/java/android/text/TEST_MAPPING
@@ -1,15 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "CtsTextTestCases",
-      "options": [
-          {
-              "exclude-annotation": "androidx.test.filters.FlakyTest"
-          },
-          {
-              "exclude-annotation": "androidx.test.filters.LargeTest"
-          }
-      ]
+      "name": "CtsTextTestCases_text"
     }
   ]
 }
diff --git a/core/java/android/view/TEST_MAPPING b/core/java/android/view/TEST_MAPPING
index db35908..ac6cd02 100644
--- a/core/java/android/view/TEST_MAPPING
+++ b/core/java/android/view/TEST_MAPPING
@@ -4,39 +4,11 @@
       "name": "CtsAccelerationTestCases"
     },
     {
-      "name": "CtsOsTestCases",
-      "options": [
-        {
-          "include-annotation": "android.platform.test.annotations.Presubmit"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.LargeTest"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        },
-        {
-          "include-filter": "android.os.cts.StrictModeTest"
-        }
-      ],
+      "name": "CtsOsTestCases_cts_strictmodetest_Presubmit",
       "file_patterns": ["(/|^)ViewConfiguration.java", "(/|^)GestureDetector.java"]
     },
     {
-      "name": "CtsViewReceiveContentTestCases",
-      "options": [
-        {
-          "include-annotation": "android.platform.test.annotations.Presubmit"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        }
-      ],
+      "name": "CtsViewReceiveContentTestCases_Presubmit",
       "file_patterns": ["ContentInfo\\.java", "OnReceiveContentListener\\.java", "View\\.java"]
     }
   ],
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index d2747e4..5129461 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -587,7 +587,14 @@
 
     @Override
     public void updateRequestedVisibleTypes(IWindow window,
-            @InsetsType int requestedVisibleTypes, @Nullable ImeTracker.Token imeStatsToken)  {
+            @InsetsType int requestedVisibleTypes, @Nullable ImeTracker.Token imeStatsToken)
+            throws RemoteException {
+        if (android.view.inputmethod.Flags.refactorInsetsController()) {
+            // Embedded windows do not control insets (except for IME). The host window is
+            // responsible for controlling the insets.
+            mRealWm.updateRequestedVisibleTypes(window,
+                    requestedVisibleTypes & WindowInsets.Type.ime(), imeStatsToken);
+        }
     }
 
     @Override
diff --git a/core/java/android/view/flags/scroll_feedback_flags.aconfig b/core/java/android/view/flags/scroll_feedback_flags.aconfig
index e9c85684..658aa29 100644
--- a/core/java/android/view/flags/scroll_feedback_flags.aconfig
+++ b/core/java/android/view/flags/scroll_feedback_flags.aconfig
@@ -21,4 +21,5 @@
     name: "enable_touch_scroll_feedback"
     description: "Enables touchscreen haptic scroll feedback"
     bug: "331830899"
+    is_fixed_read_only: true
 }
diff --git a/core/java/android/view/inputmethod/TEST_MAPPING b/core/java/android/view/inputmethod/TEST_MAPPING
index ad59463..989b686 100644
--- a/core/java/android/view/inputmethod/TEST_MAPPING
+++ b/core/java/android/view/inputmethod/TEST_MAPPING
@@ -1,21 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "CtsAutoFillServiceTestCases",
-      "options": [
-        {
-          "include-filter": "android.autofillservice.cts.inline"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "android.platform.test.annotations.AppModeFull"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.LargeTest"
-        }
-      ]
+      "name": "CtsAutoFillServiceTestCases_cts_inline_ExcludeAppModeFull"
     }
   ]
 }
diff --git a/core/java/android/view/textclassifier/TEST_MAPPING b/core/java/android/view/textclassifier/TEST_MAPPING
index 050c651..bc7f3b0 100644
--- a/core/java/android/view/textclassifier/TEST_MAPPING
+++ b/core/java/android/view/textclassifier/TEST_MAPPING
@@ -4,20 +4,10 @@
       "name": "FrameworksCoreTests_textclassifier"
     },
     {
-      "name": "CtsTextClassifierTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsTextClassifierTestCases"
     },
     {
-      "name": "TextClassifierServiceTest",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "TextClassifierServiceTest"
     }
   ]
 }
diff --git a/core/java/android/webkit/TEST_MAPPING b/core/java/android/webkit/TEST_MAPPING
index 07f4383..3858059 100644
--- a/core/java/android/webkit/TEST_MAPPING
+++ b/core/java/android/webkit/TEST_MAPPING
@@ -1,28 +1,13 @@
 {
   "presubmit": [
     {
-      "name": "CtsWebkitTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsWebkitTestCases"
     },
     {
-      "name": "CtsSdkSandboxWebkitTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsSdkSandboxWebkitTestCases"
     },
     {
-      "name": "CtsHostsideWebViewTests",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsHostsideWebViewTests"
     },
     {
       "name": "GtsWebViewTestCases",
diff --git a/core/java/android/webkit/WebViewUpdateManager.java b/core/java/android/webkit/WebViewUpdateManager.java
index 0eb71001..b9a5e4ae 100644
--- a/core/java/android/webkit/WebViewUpdateManager.java
+++ b/core/java/android/webkit/WebViewUpdateManager.java
@@ -43,22 +43,29 @@
     /**
      * Get the singleton instance of the manager.
      *
-     * This exists for the benefit of callsites without a {@link Context}; prefer
+     * <p>This exists for the benefit of callsites without a {@link Context}; prefer
      * {@link Context#getSystemService(Class)} otherwise.
      *
-     * This can only be used on devices with {@link PackageManager#FEATURE_WEBVIEW}.
+     * <p>This must only be called on devices with {@link PackageManager#FEATURE_WEBVIEW},
+     * and will WTF or throw {@link UnsupportedOperationException} otherwise.
      */
     @SuppressLint("ManagerLookup") // service opts in to getSystemServiceWithNoContext()
     @RequiresFeature(PackageManager.FEATURE_WEBVIEW)
-    public static @Nullable WebViewUpdateManager getInstance() {
-        return (WebViewUpdateManager) SystemServiceRegistry.getSystemServiceWithNoContext(
-                Context.WEBVIEW_UPDATE_SERVICE);
+    public static @NonNull WebViewUpdateManager getInstance() {
+        WebViewUpdateManager manager =
+                (WebViewUpdateManager) SystemServiceRegistry.getSystemServiceWithNoContext(
+                        Context.WEBVIEW_UPDATE_SERVICE);
+        if (manager == null) {
+            throw new UnsupportedOperationException("WebView not supported by device");
+        } else {
+            return manager;
+        }
     }
 
     /**
      * Block until system-level WebView preparations are complete.
      *
-     * This also makes the current WebView provider package visible to the caller.
+     * <p>This also makes the current WebView provider package visible to the caller.
      *
      * @return the status of WebView preparation and the current provider package.
      */
@@ -86,7 +93,7 @@
     /**
      * Get the complete list of supported WebView providers for this device.
      *
-     * This includes all configured providers, regardless of whether they are currently available
+     * <p>This includes all configured providers, regardless of whether they are currently available
      * or valid.
      */
     @SuppressLint({"ParcelableList", "ArrayReturn"})
@@ -101,13 +108,15 @@
     /**
      * Get the list of currently-valid WebView providers for this device.
      *
-     * This only includes providers that are currently present on the device and meet the validity
-     * criteria (signature, version, etc), but does not check if the provider is installed and
-     * enabled for all users.
+     * <p>This only includes providers that are currently present on the device and meet the
+     * validity criteria (signature, version, etc), but does not check if the provider is installed
+     * and enabled for all users.
+     *
+     * <p>Note that this will be filtered by the caller's package visibility; callers should
+     * have QUERY_ALL_PACKAGES permission to ensure that the list is complete.
      */
     @SuppressLint({"ParcelableList", "ArrayReturn"})
-    @RequiresPermission(allOf = {android.Manifest.permission.INTERACT_ACROSS_USERS,
-            android.Manifest.permission.QUERY_ALL_PACKAGES})
+    @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
     public @NonNull WebViewProviderInfo[] getValidWebViewPackages() {
         try {
             return mService.getValidWebViewPackages();
@@ -132,7 +141,7 @@
     /**
      * Ask the system to switch to a specific WebView implementation if possible.
      *
-     * This choice will be stored persistently.
+     * <p>This choice will be stored persistently.
      *
      * @param newProvider the package name to use.
      * @return the package name which is now in use, which may not be the
@@ -162,7 +171,7 @@
     /**
      * Get the WebView provider which will be used if no explicit choice has been made.
      *
-     * The default provider is not guaranteed to be a valid/usable WebView implementation.
+     * <p>The default provider is not guaranteed to be a valid/usable WebView implementation.
      *
      * @return the default WebView provider.
      */
diff --git a/core/java/android/webkit/WebViewUpdateService.java b/core/java/android/webkit/WebViewUpdateService.java
index 01af182..644d917 100644
--- a/core/java/android/webkit/WebViewUpdateService.java
+++ b/core/java/android/webkit/WebViewUpdateService.java
@@ -16,6 +16,7 @@
 
 package android.webkit;
 
+import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.RemoteException;
@@ -54,7 +55,11 @@
 
     /**
      * Fetch all packages that could potentially implement WebView and are currently valid.
+     *
+     * <p>Note that this will be filtered by the caller's package visibility; callers should
+     * have QUERY_ALL_PACKAGES permission to ensure that the list is complete.
      */
+    @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
     public static WebViewProviderInfo[] getValidWebViewPackages() {
         if (Flags.updateServiceIpcWrapper()) {
             if (WebViewFactory.isWebViewSupported()) {
diff --git a/core/java/android/webkit/WebViewZygote.java b/core/java/android/webkit/WebViewZygote.java
index c7900e4..668cd01 100644
--- a/core/java/android/webkit/WebViewZygote.java
+++ b/core/java/android/webkit/WebViewZygote.java
@@ -22,6 +22,7 @@
 import android.os.Build;
 import android.os.ChildZygoteProcess;
 import android.os.Process;
+import android.os.UserHandle;
 import android.os.ZygoteProcess;
 import android.text.TextUtils;
 import android.util.Log;
@@ -141,12 +142,14 @@
             String abi = sPackage.applicationInfo.primaryCpuAbi;
             int runtimeFlags = Zygote.getMemorySafetyRuntimeFlagsForSecondaryZygote(
                     sPackage.applicationInfo, null);
+            final int[] sharedAppGid = {
+                    UserHandle.getSharedAppGid(UserHandle.getAppId(sPackage.applicationInfo.uid)) };
             sZygote = Process.ZYGOTE_PROCESS.startChildZygote(
                     "com.android.internal.os.WebViewZygoteInit",
                     "webview_zygote",
                     Process.WEBVIEW_ZYGOTE_UID,
                     Process.WEBVIEW_ZYGOTE_UID,
-                    null,  // gids
+                    sharedAppGid,  // Access to shared app GID for ART profiles
                     runtimeFlags,
                     "webview_zygote",  // seInfo
                     abi,  // abi
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 89ea852..e1154ca 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -83,6 +83,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.StrictMode;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.system.Os;
 import android.text.TextUtils;
@@ -7003,6 +7004,18 @@
 
     private View inflateView(Context context, RemoteViews rv, @Nullable ViewGroup parent,
             @StyleRes int applyThemeResId, @Nullable ColorResources colorResources) {
+        try {
+            Trace.beginSection(rv.hasDrawInstructions()
+                    ? "RemoteViews#inflateViewWithDrawInstructions"
+                    : "RemoteViews#inflateView");
+            return inflateViewInternal(context, rv, parent, applyThemeResId, colorResources);
+        } finally {
+            Trace.endSection();
+        }
+    }
+
+    private View inflateViewInternal(Context context, RemoteViews rv, @Nullable ViewGroup parent,
+            @StyleRes int applyThemeResId, @Nullable ColorResources colorResources) {
         // RemoteViews may be built by an application installed in another
         // user. So build a context that loads resources from that user but
         // still returns the current users userId so settings like data / time formats
@@ -7169,10 +7182,17 @@
                 if (mRV.mActions != null) {
                     int count = mRV.mActions.size();
                     mActions = new Action[count];
-                    for (int i = 0; i < count && !isCancelled(); i++) {
-                        // TODO: check if isCancelled in nested views.
-                        mActions[i] = mRV.mActions.get(i)
-                                .initActionAsync(mTree, mParent, mApplyParams);
+                    try {
+                        Trace.beginSection(hasDrawInstructions()
+                                ? "RemoteViews#initActionAsyncWithDrawInstructions"
+                                : "RemoteViews#initActionAsync");
+                        for (int i = 0; i < count && !isCancelled(); i++) {
+                            // TODO: check if isCancelled in nested views.
+                            mActions[i] = mRV.mActions.get(i)
+                                    .initActionAsync(mTree, mParent, mApplyParams);
+                        }
+                    } finally {
+                        Trace.endSection();
                     }
                 } else {
                     mActions = null;
@@ -7194,13 +7214,19 @@
 
                 try {
                     if (mActions != null) {
-
                         ActionApplyParams applyParams = mApplyParams.clone();
                         if (applyParams.handler == null) {
                             applyParams.handler = DEFAULT_INTERACTION_HANDLER;
                         }
-                        for (Action a : mActions) {
-                            a.apply(viewTree.mRoot, mParent, applyParams);
+                        try {
+                            Trace.beginSection(hasDrawInstructions()
+                                    ? "RemoteViews#applyActionsAsyncWithDrawInstructions"
+                                    : "RemoteViews#applyActionsAsync");
+                            for (Action a : mActions) {
+                                a.apply(viewTree.mRoot, mParent, applyParams);
+                            }
+                        } finally {
+                            Trace.endSection();
                         }
                     }
                     // If the parent of the view is has is a root, resolve the recycling.
@@ -7387,8 +7413,15 @@
         }
         if (mActions != null) {
             final int count = mActions.size();
-            for (int i = 0; i < count; i++) {
-                mActions.get(i).apply(v, parent, params);
+            try {
+                Trace.beginSection(hasDrawInstructions()
+                        ? "RemoteViews#applyActionsWithDrawInstructions"
+                        : "RemoteViews#applyActions");
+                for (int i = 0; i < count; i++) {
+                    mActions.get(i).apply(v, parent, params);
+                }
+            } finally {
+                Trace.endSection();
             }
         }
     }
diff --git a/core/java/android/widget/TEST_MAPPING b/core/java/android/widget/TEST_MAPPING
index bc71bee..624fa86 100644
--- a/core/java/android/widget/TEST_MAPPING
+++ b/core/java/android/widget/TEST_MAPPING
@@ -10,52 +10,17 @@
       "file_patterns": ["Toast\\.java"]
     },
     {
-      "name": "CtsWindowManagerDeviceWindow",
-      "options": [
-        {
-          "include-filter": "android.server.wm.window.ToastWindowTest"
-        }
-      ],
+      "name": "CtsWindowManagerDeviceWindow_window_toastwindowtest",
       "file_patterns": ["Toast\\.java"]
     },
     {
-      "name": "CtsAutoFillServiceTestCases",
-      "options": [
-        {
-          "include-filter": "android.autofillservice.cts.dropdown.LoginActivityTest"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "android.platform.test.annotations.AppModeFull"
-        }
-      ]
+      "name": "CtsAutoFillServiceTestCases_dropdown_loginactivitytest"
     },
     {
-      "name": "CtsAutoFillServiceTestCases",
-      "options": [
-        {
-          "include-filter": "android.autofillservice.cts.dropdown.CheckoutActivityTest"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "android.platform.test.annotations.AppModeFull"
-        }
-      ]
+      "name": "CtsAutoFillServiceTestCases_dropdown_checkoutactivitytest"
     },
     {
-      "name": "CtsTextTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.LargeTest"
-        }
-      ]
+      "name": "CtsTextTestCases_text"
     }
   ]
 }
diff --git a/core/java/android/widget/inline/TEST_MAPPING b/core/java/android/widget/inline/TEST_MAPPING
index 82c6f61..eb412f1 100644
--- a/core/java/android/widget/inline/TEST_MAPPING
+++ b/core/java/android/widget/inline/TEST_MAPPING
@@ -1,18 +1,7 @@
 {
   "presubmit-large": [
     {
-      "name": "CtsAutoFillServiceTestCases",
-      "options": [
-        {
-          "include-filter": "android.autofillservice.cts.inline"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.LargeTest"
-        }
-      ]
+      "name": "CtsAutoFillServiceTestCases_cts_inline"
     }
   ]
 }
diff --git a/core/java/android/window/OnBackInvokedDispatcher.java b/core/java/android/window/OnBackInvokedDispatcher.java
index bccee92..0632a37 100644
--- a/core/java/android/window/OnBackInvokedDispatcher.java
+++ b/core/java/android/window/OnBackInvokedDispatcher.java
@@ -76,7 +76,7 @@
      * @param callback The callback to be registered. If the callback instance has been already
      *                 registered, the existing instance (no matter its priority) will be
      *                 unregistered and registered again.
-     * @throws {@link IllegalArgumentException} if the priority is negative.
+     * @throws IllegalArgumentException if the priority is negative.
      */
     @SuppressLint({"ExecutorRegistration"})
     void registerOnBackInvokedCallback(
diff --git a/services/core/java/com/android/server/wm/utils/DesktopModeFlagsUtil.java b/core/java/android/window/flags/DesktopModeFlags.java
similarity index 82%
rename from services/core/java/com/android/server/wm/utils/DesktopModeFlagsUtil.java
rename to core/java/android/window/flags/DesktopModeFlags.java
index d33313e..5c53d66 100644
--- a/services/core/java/com/android/server/wm/utils/DesktopModeFlagsUtil.java
+++ b/core/java/android/window/flags/DesktopModeFlags.java
@@ -14,9 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.wm.utils;
-
-import static com.android.server.wm.utils.DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET;
+package android.window.flags;
 
 import android.annotation.Nullable;
 import android.content.Context;
@@ -28,18 +26,18 @@
 import java.util.function.Supplier;
 
 /**
- * Util to check desktop mode flags state.
+ * Checks desktop mode flag state.
  *
- * This utility is used to allow developer option toggles to override flags related to desktop
- * windowing.
+ * <p>This enum provides a centralized way to control the behavior of flags related to desktop
+ * windowing features which are aiming for developer preview before their release. It allows
+ * developer option to override the default behavior of these flags.
  *
- * Computes whether Desktop Windowing related flags should be enabled by using the aconfig flag
- * value and the developer option override state (if applicable).
+ * <p>NOTE: Flags should only be added to this enum when they have received Product and UX
+ * alignment that the feature is ready for developer preview, otherwise just do a flag check.
  *
- * This is a partial copy of {@link com.android.wm.shell.shared.desktopmode.DesktopModeFlags} which
- * is to be used in WM core.
+ * @hide
  */
-public enum DesktopModeFlagsUtil {
+public enum DesktopModeFlags {
     // All desktop mode related flags to be overridden by developer option toggle will be added here
     DESKTOP_WINDOWING_MODE(
             Flags::enableDesktopWindowingMode, /* shouldOverrideByDevOption= */ true),
@@ -55,7 +53,7 @@
     // be refreshed only on reboots as overridden state is expected to take effect on reboots.
     private static ToggleOverride sCachedToggleOverride;
 
-    DesktopModeFlagsUtil(Supplier<Boolean> flagFunction, boolean shouldOverrideByDevOption) {
+    DesktopModeFlags(Supplier<Boolean> flagFunction, boolean shouldOverrideByDevOption) {
         this.mFlagFunction = flagFunction;
         this.mShouldOverrideByDevOption = shouldOverrideByDevOption;
     }
@@ -101,13 +99,13 @@
         int settingValue = Settings.Global.getInt(
                 context.getContentResolver(),
                 Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES,
-                OVERRIDE_UNSET.getSetting()
+                ToggleOverride.OVERRIDE_UNSET.getSetting()
         );
-        return ToggleOverride.fromSetting(settingValue, OVERRIDE_UNSET);
+        return ToggleOverride.fromSetting(settingValue, ToggleOverride.OVERRIDE_UNSET);
     }
 
     /** Override state of desktop mode developer option toggle. */
-    enum ToggleOverride {
+    private enum ToggleOverride {
         OVERRIDE_UNSET,
         OVERRIDE_OFF,
         OVERRIDE_ON;
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 6e89c49..0f401d3 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -145,6 +145,13 @@
 }
 
 flag {
+    name: "enable_resizing_metrics"
+    namespace: "lse_desktop_experience"
+    description: "Whether to enable log collection for task resizing in desktop windowing mode"
+    bug: "341319100"
+}
+
+flag {
     name: "enable_caption_compat_inset_force_consumption"
     namespace: "lse_desktop_experience"
     description: "Enables force-consumption of caption bar insets for immersive apps in freeform"
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 03e230c..e1402f8 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -144,6 +144,13 @@
 }
 
 flag {
+  name: "universal_resizable_by_default"
+  namespace: "windowing_frontend"
+  description: "The orientation, aspect ratio, resizability of activity will follow system behavior by default"
+  bug: "357141415"
+}
+
+flag {
   name: "respect_non_top_visible_fixed_orientation"
   namespace: "windowing_frontend"
   description: "If top activity is not opaque, respect the fixed orientation of activity behind it"
@@ -239,15 +246,6 @@
 }
 
 flag {
-  name: "custom_animations_behind_translucent"
-  namespace: "windowing_frontend"
-  description: "A change can use its own layer parameters to animate behind a translucent activity"
-  bug: "327332488"
-  metadata {
-    purpose: PURPOSE_BUGFIX
-  }
-}
-flag {
   name: "migrate_predictive_back_transition"
   namespace: "windowing_frontend"
   description: "Create transition when visibility change from predictive back"
@@ -267,4 +265,4 @@
   metadata {
     purpose: PURPOSE_BUGFIX
   }
-}
\ No newline at end of file
+}
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index 8077a55..13648de 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -115,3 +115,10 @@
     bug: "289875940"
     is_fixed_read_only: true
 }
+
+flag {
+    namespace: "windowing_sdk"
+    name: "touch_pass_through_opt_in"
+    description: "Requires apps to opt-in to overlay pass through touches and provide APIs to opt-in"
+    bug: "358129114"
+}
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index ebcae27..a5e166b 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -144,9 +144,9 @@
     @EnforcePermission("UPDATE_DEVICE_STATS")
     void noteGpsSignalQuality(int signalLevel);
     @EnforcePermission("UPDATE_DEVICE_STATS")
-    void noteScreenState(int state);
+    void noteScreenState(int displayId, int state, int reason);
     @EnforcePermission("UPDATE_DEVICE_STATS")
-    void noteScreenBrightness(int brightness);
+    void noteScreenBrightness(int displayId, int brightness);
     @EnforcePermission("UPDATE_DEVICE_STATS")
     void noteUserActivity(int uid, int event);
     @EnforcePermission("UPDATE_DEVICE_STATS")
diff --git a/core/java/com/android/internal/infra/TEST_MAPPING b/core/java/com/android/internal/infra/TEST_MAPPING
index 35f0553..092aa20 100644
--- a/core/java/com/android/internal/infra/TEST_MAPPING
+++ b/core/java/com/android/internal/infra/TEST_MAPPING
@@ -1,12 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "CtsRoleTestCases",
-      "options": [
-          {
-              "exclude-annotation": "androidx.test.filters.FlakyTest"
-          }
-      ]
+      "name": "CtsRoleTestCases"
     },
     {
       "name": "CtsPermissionTestCases_Platform"
diff --git a/core/java/com/android/internal/os/TEST_MAPPING b/core/java/com/android/internal/os/TEST_MAPPING
index 258f402..4400ed1 100644
--- a/core/java/com/android/internal/os/TEST_MAPPING
+++ b/core/java/com/android/internal/os/TEST_MAPPING
@@ -49,12 +49,7 @@
   ],
   "postsubmit": [
     {
-      "name": "PowerStatsTests",
-      "options": [
-        {
-          "include-filter": "com.android.server.power.stats.BstatsCpuTimesValidationTest"
-        }
-      ],
+      "name": "PowerStatsTests_stats_bstatscputimesvalidationtest",
       "file_patterns": [
         "Kernel[^/]*\\.java"
       ]
diff --git a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
index b873175..39aadfb 100644
--- a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
+++ b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
@@ -18,6 +18,7 @@
 
 import static com.android.internal.pm.pkg.parsing.ParsingUtils.ANDROID_RES_NAMESPACE;
 
+import android.aconfig.DeviceProtos;
 import android.aconfig.nano.Aconfig;
 import android.aconfig.nano.Aconfig.parsed_flag;
 import android.aconfig.nano.Aconfig.parsed_flags;
@@ -40,7 +41,7 @@
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
-import java.util.List;
+import java.util.Arrays;
 import java.util.Map;
 
 /**
@@ -54,12 +55,6 @@
 public class AconfigFlags {
     private static final String LOG_TAG = "AconfigFlags";
 
-    private static final List<String> sTextProtoFilesOnDevice = List.of(
-            "/system/etc/aconfig_flags.pb",
-            "/system_ext/etc/aconfig_flags.pb",
-            "/product/etc/aconfig_flags.pb",
-            "/vendor/etc/aconfig_flags.pb");
-
     public enum Permission {
         READ_WRITE,
         READ_ONLY
@@ -73,7 +68,10 @@
             Slog.v(LOG_TAG, "Feature disabled, skipped all loading");
             return;
         }
-        for (String fileName : sTextProtoFilesOnDevice) {
+        final var defaultFlagProtoFiles =
+                (Process.myUid() == Process.SYSTEM_UID) ? DeviceProtos.parsedFlagsProtoPaths()
+                        : Arrays.asList(DeviceProtos.PATHS);
+        for (String fileName : defaultFlagProtoFiles) {
             try (var inputStream = new FileInputStream(fileName)) {
                 loadAconfigDefaultValues(inputStream.readAllBytes());
             } catch (IOException e) {
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index ef6ff05..0837b45 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -215,6 +215,25 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+    public void testGetShortCriticalText_noneSet() {
+        Notification n = new Notification.Builder(mContext, "test")
+                .build();
+
+        assertSame(n.getShortCriticalText(), null);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+    public void testGetShortCriticalText_isSet() {
+        Notification n = new Notification.Builder(mContext, "test")
+                .setShortCriticalText("short critical text here")
+                .build();
+
+        assertSame(n.getShortCriticalText(), "short critical text here");
+    }
+
+    @Test
     public void largeIconMultipleReferences_keptAfterParcelling() {
         Icon originalIcon = Icon.createWithBitmap(BitmapFactory.decodeResource(
                 mContext.getResources(), com.android.frameworks.coretests.R.drawable.test128x96));
diff --git a/core/tests/coretests/src/android/content/pm/TEST_MAPPING b/core/tests/coretests/src/android/content/pm/TEST_MAPPING
index 9ab438e..b350d7d 100644
--- a/core/tests/coretests/src/android/content/pm/TEST_MAPPING
+++ b/core/tests/coretests/src/android/content/pm/TEST_MAPPING
@@ -6,21 +6,7 @@
   ],
   "postsubmit": [
     {
-      "name": "FrameworksCoreTests",
-      "options": [
-        {
-          "include-filter": "android.content.pm."
-        },
-        {
-          "include-annotation": "android.platform.test.annotations.Postsubmit"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        }
-      ]
+      "name": "FrameworksCoreTests_android_content_pm_PostSubmit"
     }
   ]
 }
diff --git a/core/tests/coretests/src/android/os/IpcDataCacheTest.java b/core/tests/coretests/src/android/os/IpcDataCacheTest.java
index b03fd64..64f77b3 100644
--- a/core/tests/coretests/src/android/os/IpcDataCacheTest.java
+++ b/core/tests/coretests/src/android/os/IpcDataCacheTest.java
@@ -18,7 +18,9 @@
 
 import static org.junit.Assert.assertEquals;
 
+import android.multiuser.Flags;
 import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.filters.SmallTest;
@@ -151,8 +153,6 @@
         tester.verify(9);
     }
 
-    // This test is disabled pending an sepolicy change that allows any app to set the
-    // test property.
     @Test
     public void testRemoteCall() {
 
@@ -193,6 +193,44 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_CACHING_DEVELOPMENT_IMPROVEMENTS)
+    public void testRemoteCallBypass() {
+
+        // A stand-in for the binder.  The test verifies that calls are passed through to
+        // this class properly.
+        ServerProxy tester = new ServerProxy();
+
+        // Create a cache that uses simple arithmetic to computer its values.
+        IpcDataCache.Config config = new IpcDataCache.Config(4, MODULE, API, "testCache3");
+        IpcDataCache<Integer, Boolean> testCache =
+                new IpcDataCache<>(config, (x) -> tester.query(x), (x) -> x % 9 == 0);
+
+        IpcDataCache.setTestMode(true);
+        testCache.testPropertyName();
+
+        tester.verify(0);
+        assertEquals(tester.value(3), testCache.query(3));
+        tester.verify(1);
+        assertEquals(tester.value(3), testCache.query(3));
+        tester.verify(2);
+        testCache.invalidateCache();
+        assertEquals(tester.value(3), testCache.query(3));
+        tester.verify(3);
+        assertEquals(tester.value(5), testCache.query(5));
+        tester.verify(4);
+        assertEquals(tester.value(5), testCache.query(5));
+        tester.verify(4);
+        assertEquals(tester.value(3), testCache.query(3));
+        tester.verify(4);
+        assertEquals(tester.value(9), testCache.query(9));
+        tester.verify(5);
+        assertEquals(tester.value(3), testCache.query(3));
+        tester.verify(5);
+        assertEquals(tester.value(5), testCache.query(5));
+        tester.verify(5);
+    }
+
+    @Test
     public void testDisableCache() {
 
         // A stand-in for the binder.  The test verifies that calls are passed through to
diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/DesktopModeFlagsUtilTest.java b/core/tests/coretests/src/android/window/flags/DesktopModeFlagsTest.java
similarity index 77%
rename from services/tests/wmtests/src/com/android/server/wm/utils/DesktopModeFlagsUtilTest.java
rename to core/tests/coretests/src/android/window/flags/DesktopModeFlagsTest.java
index 46b8e3a..32345e6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/utils/DesktopModeFlagsUtilTest.java
+++ b/core/tests/coretests/src/android/window/flags/DesktopModeFlagsTest.java
@@ -14,11 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.server.wm.utils;
+package android.window.flags;
 
-import static com.android.server.wm.utils.DesktopModeFlagsUtil.DESKTOP_WINDOWING_MODE;
-import static com.android.server.wm.utils.DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_OFF;
-import static com.android.server.wm.utils.DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_ON;
+import static android.window.flags.DesktopModeFlags.DESKTOP_WINDOWING_MODE;
+
 import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE;
 import static com.android.window.flags.Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS;
 import static com.android.window.flags.Flags.FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION;
@@ -26,16 +25,16 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import android.content.ContentResolver;
+import android.content.Context;
 import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-
-import com.android.server.wm.WindowTestRunner;
-import com.android.server.wm.WindowTestsBase;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -45,21 +44,28 @@
 import java.lang.reflect.Field;
 
 /**
- * Test class for [DesktopModeFlagsUtil]
+ * Test class for {@link DesktopModeFlags}
  *
  * Build/Install/Run:
- * atest WmTests:DesktopModeFlagsUtilTest
+ * atest FrameworksCoreTests:DesktopModeFlagsTest
  */
 @SmallTest
 @Presubmit
-@RunWith(WindowTestRunner.class)
-public class DesktopModeFlagsUtilTest extends WindowTestsBase {
+@RunWith(AndroidJUnit4.class)
+public class DesktopModeFlagsTest {
 
     @Rule
     public SetFlagsRule setFlagsRule = new SetFlagsRule();
 
+    private Context mContext;
+
+    private static final int OVERRIDE_OFF_SETTING = 0;
+    private static final int OVERRIDE_ON_SETTING = 1;
+    private static final int OVERRIDE_UNSET_SETTING = -1;
+
     @Before
     public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
         resetCache();
     }
 
@@ -67,7 +73,7 @@
     @DisableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
     @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
     public void isEnabled_devOptionFlagDisabled_overrideOff_featureFlagOn_returnsTrue() {
-        setOverride(OVERRIDE_OFF.getSetting());
+        setOverride(OVERRIDE_OFF_SETTING);
         // In absence of dev options, follow flag
         assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue();
     }
@@ -76,7 +82,7 @@
     @Test
     @DisableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE})
     public void isEnabled_devOptionFlagDisabled_overrideOn_featureFlagOff_returnsFalse() {
-        setOverride(OVERRIDE_ON.getSetting());
+        setOverride(OVERRIDE_ON_SETTING);
 
         assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse();
     }
@@ -84,7 +90,7 @@
     @Test
     @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE})
     public void isEnabled_overrideUnset_featureFlagOn_returnsTrue() {
-        setOverride(DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET.getSetting());
+        setOverride(OVERRIDE_UNSET_SETTING);
 
         // For overridableFlag, for unset overrides, follow flag
         assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue();
@@ -94,7 +100,7 @@
     @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
     @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
     public void isEnabled_overrideUnset_featureFlagOff_returnsFalse() {
-        setOverride(DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET.getSetting());
+        setOverride(OVERRIDE_UNSET_SETTING);
 
         // For overridableFlag, for unset overrides, follow flag
         assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse();
@@ -141,7 +147,7 @@
     @Test
     @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE})
     public void isEnabled_overrideOff_featureFlagOn_returnsFalse() {
-        setOverride(OVERRIDE_OFF.getSetting());
+        setOverride(OVERRIDE_OFF_SETTING);
 
         // For overridableFlag, follow override if they exist
         assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse();
@@ -151,7 +157,7 @@
     @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
     @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
     public void isEnabled_overrideOn_featureFlagOff_returnsTrue() {
-        setOverride(OVERRIDE_ON.getSetting());
+        setOverride(OVERRIDE_ON_SETTING);
 
         // For overridableFlag, follow override if they exist
         assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue();
@@ -160,12 +166,12 @@
     @Test
     @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE})
     public void isEnabled_overrideOffThenOn_featureFlagOn_returnsFalseAndFalse() {
-        setOverride(OVERRIDE_OFF.getSetting());
+        setOverride(OVERRIDE_OFF_SETTING);
 
         // For overridableFlag, follow override if they exist
         assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse();
 
-        setOverride(OVERRIDE_ON.getSetting());
+        setOverride(OVERRIDE_ON_SETTING);
 
         // Keep overrides constant through the process
         assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse();
@@ -175,12 +181,12 @@
     @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
     @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
     public void isEnabled_overrideOnThenOff_featureFlagOff_returnsTrueAndTrue() {
-        setOverride(OVERRIDE_ON.getSetting());
+        setOverride(OVERRIDE_ON_SETTING);
 
         // For overridableFlag, follow override if they exist
         assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue();
 
-        setOverride(OVERRIDE_OFF.getSetting());
+        setOverride(OVERRIDE_OFF_SETTING);
 
         // Keep overrides constant through the process
         assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue();
@@ -190,19 +196,19 @@
     @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
             FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS})
     public void isEnabled_dwFlagOn_overrideUnset_featureFlagOn_returnsTrue() {
-        setOverride(DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET.getSetting());
+        setOverride(OVERRIDE_UNSET_SETTING);
 
         // For unset overrides, follow flag
-        assertThat(DesktopModeFlagsUtil.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue();
+        assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue();
     }
 
     @Test
     @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE})
     @DisableFlags(FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
     public void isEnabled_dwFlagOn_overrideUnset_featureFlagOff_returnsFalse() {
-        setOverride(DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET.getSetting());
+        setOverride(OVERRIDE_UNSET_SETTING);
         // For unset overrides, follow flag
-        assertThat(DesktopModeFlagsUtil.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse();
+        assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse();
     }
 
     @Test
@@ -212,20 +218,20 @@
             FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS
     })
     public void isEnabled_dwFlagOn_overrideOn_featureFlagOn_returnsTrue() {
-        setOverride(OVERRIDE_ON.getSetting());
+        setOverride(OVERRIDE_ON_SETTING);
 
         // When toggle override matches its default state (dw flag), don't override flags
-        assertThat(DesktopModeFlagsUtil.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue();
+        assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue();
     }
 
     @Test
     @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE})
     @DisableFlags(FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
     public void isEnabled_dwFlagOn_overrideOn_featureFlagOff_returnsFalse() {
-        setOverride(OVERRIDE_ON.getSetting());
+        setOverride(OVERRIDE_ON_SETTING);
 
         // When toggle override matches its default state (dw flag), don't override flags
-        assertThat(DesktopModeFlagsUtil.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse();
+        assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse();
     }
 
     @Test
@@ -235,20 +241,20 @@
             FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS
     })
     public void isEnabled_dwFlagOn_overrideOff_featureFlagOn_returnsTrue() {
-        setOverride(OVERRIDE_OFF.getSetting());
+        setOverride(OVERRIDE_OFF_SETTING);
 
         // Follow override if they exist, and is not equal to default toggle state (dw flag)
-        assertThat(DesktopModeFlagsUtil.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue();
+        assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue();
     }
 
     @Test
     @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE})
     @DisableFlags(FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
     public void isEnabled_dwFlagOn_overrideOff_featureFlagOff_returnsFalse() {
-        setOverride(OVERRIDE_OFF.getSetting());
+        setOverride(OVERRIDE_OFF_SETTING);
 
         // Follow override if they exist, and is not equal to default toggle state (dw flag)
-        assertThat(DesktopModeFlagsUtil.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse();
+        assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse();
     }
 
     @Test
@@ -258,10 +264,10 @@
     })
     @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
     public void isEnabled_dwFlagOff_overrideUnset_featureFlagOn_returnsTrue() {
-        setOverride(DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET.getSetting());
+        setOverride(OVERRIDE_UNSET_SETTING);
 
         // For unset overrides, follow flag
-        assertThat(DesktopModeFlagsUtil.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue();
+        assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue();
     }
 
     @Test
@@ -271,10 +277,10 @@
             FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS
     })
     public void isEnabled_dwFlagOff_overrideUnset_featureFlagOff_returnsFalse() {
-        setOverride(DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET.getSetting());
+        setOverride(OVERRIDE_UNSET_SETTING);
 
         // For unset overrides, follow flag
-        assertThat(DesktopModeFlagsUtil.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse();
+        assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse();
     }
 
     @Test
@@ -284,10 +290,10 @@
     })
     @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
     public void isEnabled_dwFlagOff_overrideOn_featureFlagOn_returnsTrue() {
-        setOverride(OVERRIDE_ON.getSetting());
+        setOverride(OVERRIDE_ON_SETTING);
 
         // Follow override if they exist, and is not equal to default toggle state (dw flag)
-        assertThat(DesktopModeFlagsUtil.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue();
+        assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue();
     }
 
     @Test
@@ -297,10 +303,10 @@
             FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS
     })
     public void isEnabled_dwFlagOff_overrideOn_featureFlagOff_returnFalse() {
-        setOverride(OVERRIDE_ON.getSetting());
+        setOverride(OVERRIDE_ON_SETTING);
 
         // Follow override if they exist, and is not equal to default toggle state (dw flag)
-        assertThat(DesktopModeFlagsUtil.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse();
+        assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse();
     }
 
     @Test
@@ -310,10 +316,10 @@
     })
     @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
     public void isEnabled_dwFlagOff_overrideOff_featureFlagOn_returnsTrue() {
-        setOverride(OVERRIDE_OFF.getSetting());
+        setOverride(OVERRIDE_OFF_SETTING);
 
         // When toggle override matches its default state (dw flag), don't override flags
-        assertThat(DesktopModeFlagsUtil.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue();
+        assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue();
     }
 
     @Test
@@ -323,10 +329,10 @@
             FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS
     })
     public void isEnabled_dwFlagOff_overrideOff_featureFlagOff_returnsFalse() {
-        setOverride(OVERRIDE_OFF.getSetting());
+        setOverride(OVERRIDE_OFF_SETTING);
 
         // When toggle override matches its default state (dw flag), don't override flags
-        assertThat(DesktopModeFlagsUtil.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse();
+        assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse();
     }
 
     private void setOverride(Integer setting) {
@@ -341,7 +347,7 @@
     }
 
     private void resetCache() throws Exception {
-        Field cachedToggleOverride = DesktopModeFlagsUtil.class.getDeclaredField(
+        Field cachedToggleOverride = DesktopModeFlags.class.getDeclaredField(
                 "sCachedToggleOverride");
         cachedToggleOverride.setAccessible(true);
         cachedToggleOverride.set(null, null);
diff --git a/core/tests/overlaytests/handle_config_change/test-apps/OverlayResApp/Android.bp b/core/tests/overlaytests/handle_config_change/test-apps/OverlayResApp/Android.bp
index e0f1012..74c7b4c 100644
--- a/core/tests/overlaytests/handle_config_change/test-apps/OverlayResApp/Android.bp
+++ b/core/tests/overlaytests/handle_config_change/test-apps/OverlayResApp/Android.bp
@@ -32,8 +32,8 @@
         "truth",
     ],
     libs: [
-        "android.test.runner",
-        "android.test.base",
+        "android.test.runner.stubs.system",
+        "android.test.base.stubs.system",
     ],
     test_suites: [
         "device-tests",
diff --git a/core/tests/vibrator/TEST_MAPPING b/core/tests/vibrator/TEST_MAPPING
index 54a5ff1..d91b883 100644
--- a/core/tests/vibrator/TEST_MAPPING
+++ b/core/tests/vibrator/TEST_MAPPING
@@ -1,12 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "FrameworksVibratorCoreTests",
-      "options": [
-        {"exclude-annotation": "androidx.test.filters.LargeTest"},
-        {"exclude-annotation": "androidx.test.filters.FlakyTest"},
-        {"exclude-annotation": "org.junit.Ignore"}
-      ]
+      "name": "FrameworksVibratorCoreTests"
     }
   ],
   "postsubmit": [
diff --git a/graphics/TEST_MAPPING b/graphics/TEST_MAPPING
index 8afc30d..75cb87c 100644
--- a/graphics/TEST_MAPPING
+++ b/graphics/TEST_MAPPING
@@ -1,23 +1,10 @@
 {
   "presubmit": [
     {
-      "name": "CtsGraphicsTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsGraphicsTestCases"
     },
     {
-      "name": "CtsTextTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.LargeTest"
-        }
-      ],
+      "name": "CtsTextTestCases_text",
       "file_patterns": ["(/|^)Typeface\\.java", "(/|^)Paint\\.java"]
     }
   ]
diff --git a/graphics/java/android/graphics/TEST_MAPPING b/graphics/java/android/graphics/TEST_MAPPING
index df91222..5cc31ba 100644
--- a/graphics/java/android/graphics/TEST_MAPPING
+++ b/graphics/java/android/graphics/TEST_MAPPING
@@ -1,15 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "CtsTextTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.LargeTest"
-        }
-      ],
+      "name": "CtsTextTestCases_text",
       "file_patterns": [
         "Typeface\\.java",
         "Paint\\.java",
diff --git a/graphics/java/android/graphics/drawable/TEST_MAPPING b/graphics/java/android/graphics/drawable/TEST_MAPPING
index 4f06452..da0a721 100644
--- a/graphics/java/android/graphics/drawable/TEST_MAPPING
+++ b/graphics/java/android/graphics/drawable/TEST_MAPPING
@@ -1,14 +1,8 @@
 {
   "presubmit": [
     {
-
-      "name": "CtsGraphicsTestCases",
-      "file_patterns": ["(/|^)Icon\\.java"],
-      "options" : [
-        {
-          "include-filter": "android.graphics.drawable.cts.IconTest"
-        }
-      ]
+      "name": "CtsGraphicsTestCases_cts_icontest",
+      "file_patterns": ["(/|^)Icon\\.java"]
     },
     {
 
diff --git a/graphics/java/android/graphics/fonts/TEST_MAPPING b/graphics/java/android/graphics/fonts/TEST_MAPPING
index 99cbfe7..9f8a72c 100644
--- a/graphics/java/android/graphics/fonts/TEST_MAPPING
+++ b/graphics/java/android/graphics/fonts/TEST_MAPPING
@@ -1,15 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "CtsTextTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.LargeTest"
-        }
-      ]
+      "name": "CtsTextTestCases_text"
     }
   ]
 }
diff --git a/graphics/java/android/graphics/pdf/TEST_MAPPING b/graphics/java/android/graphics/pdf/TEST_MAPPING
index afec35c..8720b95 100644
--- a/graphics/java/android/graphics/pdf/TEST_MAPPING
+++ b/graphics/java/android/graphics/pdf/TEST_MAPPING
@@ -1,12 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "CtsPdfTestCases",
-      "options": [
-        {
-          "include-filter": "android.graphics.pdf.cts.PdfDocumentTest"
-        }
-      ]
+      "name": "CtsPdfTestCases_cts_pdfdocumenttest"
     }
   ]
 }
diff --git a/graphics/java/android/graphics/text/TEST_MAPPING b/graphics/java/android/graphics/text/TEST_MAPPING
index 99cbfe7..9f8a72c 100644
--- a/graphics/java/android/graphics/text/TEST_MAPPING
+++ b/graphics/java/android/graphics/text/TEST_MAPPING
@@ -1,15 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "CtsTextTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.LargeTest"
-        }
-      ]
+      "name": "CtsTextTestCases_text"
     }
   ]
 }
diff --git a/libs/WindowManager/Jetpack/src/TEST_MAPPING b/libs/WindowManager/Jetpack/src/TEST_MAPPING
index f8f6400..600c79b 100644
--- a/libs/WindowManager/Jetpack/src/TEST_MAPPING
+++ b/libs/WindowManager/Jetpack/src/TEST_MAPPING
@@ -1,32 +1,10 @@
 {
   "presubmit": [
     {
-      "name": "WMJetpackUnitTests",
-      "options": [
-        {
-          "include-annotation": "android.platform.test.annotations.Presubmit"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        }
-      ]
+      "name": "WMJetpackUnitTests_Presubmit"
     },
     {
-      "name": "CtsWindowManagerJetpackTestCases",
-      "options": [
-        {
-          "include-annotation": "android.platform.test.annotations.Presubmit"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        }
-      ]
+      "name": "CtsWindowManagerJetpackTestCases_Presubmit"
     }
   ],
   "imports": [
diff --git a/libs/WindowManager/Shell/OWNERS b/libs/WindowManager/Shell/OWNERS
index c6044a4..394093c6 100644
--- a/libs/WindowManager/Shell/OWNERS
+++ b/libs/WindowManager/Shell/OWNERS
@@ -1,4 +1,6 @@
 xutan@google.com
+pbdr@google.com
+pragyabajoria@google.com
 
 # Give submodule owners in shell resource approval
 per-file res*/*/*.xml = atsjenk@google.com, hwwang@google.com, jorgegil@google.com, lbill@google.com, madym@google.com, vaniadesmonda@google.com, pbdr@google.com, tkachenkoi@google.com, mpodolian@google.com, liranb@google.com, pragyabajoria@google.com, uysalorhan@google.com, gsennton@google.com, mattsziklay@google.com, mdehaini@google.com
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp b/libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp
index 1871203..b6db6d9 100644
--- a/libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp
@@ -72,8 +72,8 @@
         "platform-screenshot-diff-core",
     ],
     libs: [
-        "android.test.base",
-        "android.test.runner",
+        "android.test.base.stubs.system",
+        "android.test.runner.stubs.system",
     ],
     jni_libs: [
         "libdexmakerjvmtiagent",
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_manage_windows.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_manage_windows.xml
new file mode 100644
index 0000000..7d912a2
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_manage_windows.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="960" android:viewportHeight="960" android:tint="?attr/colorControlNormal">
+    <path android:fillColor="@android:color/black" android:pathData="M160,880Q127,880 103.5,856.5Q80,833 80,800L80,440Q80,407 103.5,383.5Q127,360 160,360L240,360L240,160Q240,127 263.5,103.5Q287,80 320,80L800,80Q833,80 856.5,103.5Q880,127 880,160L880,520Q880,553 856.5,576.5Q833,600 800,600L720,600L720,800Q720,833 696.5,856.5Q673,880 640,880L160,880ZM160,800L640,800Q640,800 640,800Q640,800 640,800L640,520L160,520L160,800Q160,800 160,800Q160,800 160,800ZM720,520L800,520Q800,520 800,520Q800,520 800,520L800,240L320,240L320,360L640,360Q673,360 696.5,383.5Q720,407 720,440L720,520Z"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
index eea3de8..64f71c7 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
@@ -147,6 +147,14 @@
             android:drawableStart="@drawable/desktop_mode_ic_handle_menu_new_window"
             android:drawableTint="?androidprv:attr/materialColorOnSurface"
             style="@style/DesktopModeHandleMenuActionButton" />
+
+        <Button
+            android:id="@+id/manage_windows_button"
+            android:contentDescription="@string/manage_windows_text"
+            android:text="@string/manage_windows_text"
+            android:drawableStart="@drawable/desktop_mode_ic_handle_menu_manage_windows"
+            android:drawableTint="?androidprv:attr/materialColorOnSurface"
+            style="@style/DesktopModeHandleMenuActionButton" />
     </LinearLayout>
 
     <LinearLayout
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 755e0d5..c76c470 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -504,9 +504,9 @@
     <!-- The width of the handle menu in desktop mode.  -->
     <dimen name="desktop_mode_handle_menu_width">216dp</dimen>
 
-    <!-- The maximum height of the handle menu in desktop mode. Four pills (52dp each) plus 2dp
-        spacing between them plus 4dp top padding. -->
-    <dimen name="desktop_mode_handle_menu_height">270dp</dimen>
+    <!-- The maximum height of the handle menu in desktop mode. Three pills at 52dp each,
+         additional actions pill 156dp, plus 2dp spacing between them plus 4dp top padding. -->
+    <dimen name="desktop_mode_handle_menu_height">322dp</dimen>
 
     <!-- The elevation set on the handle menu pills. -->
     <dimen name="desktop_mode_handle_menu_pill_elevation">1dp</dimen>
@@ -520,6 +520,9 @@
     <!-- The maximum height of the handle menu's "New Window" button in desktop mode. -->
     <dimen name="desktop_mode_handle_menu_new_window_height">52dp</dimen>
 
+    <!-- The maximum height of the handle menu's "Manage Windows" button in desktop mode. -->
+    <dimen name="desktop_mode_handle_menu_manage_windows_height">52dp</dimen>
+
     <!-- The maximum height of the handle menu's "Screenshot" button in desktop mode. -->
     <dimen name="desktop_mode_handle_menu_screenshot_height">52dp</dimen>
 
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index a353db7..a6da421 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -294,6 +294,8 @@
     <string name="open_in_browser_text">Open in browser</string>
     <!-- Accessibility text for the handle menu new window button [CHAR LIMIT=NONE] -->
     <string name="new_window_text">New Window</string>
+    <!-- Accessibility text for the handle menu new window button [CHAR LIMIT=NONE] -->
+    <string name="manage_windows_text">Manage Windows</string>
     <!-- Accessibility text for the handle menu close button [CHAR LIMIT=NONE] -->
     <string name="close_text">Close</string>
     <!-- Accessibility text for the handle menu close menu button [CHAR LIMIT=NONE] -->
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/ManageWindowsViewContainer.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/ManageWindowsViewContainer.kt
new file mode 100644
index 0000000..79becb0
--- /dev/null
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/ManageWindowsViewContainer.kt
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.shared.desktopmode
+import android.annotation.ColorInt
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.drawable.ShapeDrawable
+import android.graphics.drawable.shapes.RoundRectShape
+import android.util.TypedValue
+import android.view.MotionEvent.ACTION_OUTSIDE
+import android.view.SurfaceView
+import android.view.ViewGroup.MarginLayoutParams
+import android.widget.LinearLayout
+import android.window.TaskSnapshot
+
+/**
+ * View for the All Windows menu option, used by both Desktop Windowing and Taskbar.
+ * The menu displays icons of all open instances of an app. Clicking the icon should launch
+ * the instance, which will be performed by the child class.
+ */
+abstract class ManageWindowsViewContainer(
+    val context: Context,
+    @ColorInt private val menuBackgroundColor: Int
+) {
+    lateinit var menuView: ManageWindowsView
+
+    /** Creates the base menu view and fills it with icon views. */
+    fun show(snapshotList: List<Pair<Int, TaskSnapshot>>,
+             onIconClickListener: ((Int) -> Unit),
+             onOutsideClickListener: (() -> Unit)): ManageWindowsView {
+        menuView = ManageWindowsView(context, menuBackgroundColor).apply {
+            this.onOutsideClickListener = onOutsideClickListener
+            this.onIconClickListener = onIconClickListener
+            this.generateIconViews(snapshotList)
+        }
+        addToContainer(menuView)
+        return menuView
+    }
+
+    /** Adds the menu view to the container responsible for displaying it. */
+    abstract fun addToContainer(menuView: ManageWindowsView)
+
+    /** Dispose of the menu, perform needed cleanup. */
+    abstract fun close()
+
+    companion object {
+        const val MANAGE_WINDOWS_MINIMUM_INSTANCES = 2
+    }
+
+    class ManageWindowsView(
+        private val context: Context,
+        menuBackgroundColor: Int
+    ) {
+        val rootView: LinearLayout = LinearLayout(context)
+        var menuHeight = 0
+        var menuWidth = 0
+        var onIconClickListener: ((Int) -> Unit)? = null
+        var onOutsideClickListener: (() -> Unit)? = null
+
+        init {
+            rootView.orientation = LinearLayout.VERTICAL
+            val menuBackground = ShapeDrawable()
+            val menuRadius = getDimensionPixelSize(MENU_RADIUS_DP)
+            menuBackground.shape = RoundRectShape(
+                FloatArray(8) { menuRadius },
+                null,
+                null
+            )
+            menuBackground.paint.color = menuBackgroundColor
+            rootView.background = menuBackground
+            rootView.elevation = getDimensionPixelSize(MENU_ELEVATION_DP)
+            rootView.setOnTouchListener { _, event ->
+                if (event.actionMasked == ACTION_OUTSIDE) {
+                    onOutsideClickListener?.invoke()
+                }
+                return@setOnTouchListener true
+            }
+        }
+
+        private fun getDimensionPixelSize(sizeDp: Float): Float {
+            return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                sizeDp, context.resources.displayMetrics)
+        }
+
+        fun generateIconViews(
+            snapshotList: List<Pair<Int, TaskSnapshot>>
+        ) {
+            menuWidth = 0
+            menuHeight = 0
+            rootView.removeAllViews()
+            val instanceIconHeight = getDimensionPixelSize(ICON_HEIGHT_DP)
+            val instanceIconWidth = getDimensionPixelSize(ICON_WIDTH_DP)
+            val iconRadius = getDimensionPixelSize(ICON_RADIUS_DP)
+            val iconMargin = getDimensionPixelSize(ICON_MARGIN_DP)
+            var rowLayout: LinearLayout? = null
+            // Add each icon to the menu, adding a new row when needed.
+            for ((iconCount, taskInfoSnapshotPair) in snapshotList.withIndex()) {
+                val taskId = taskInfoSnapshotPair.first
+                val snapshot = taskInfoSnapshotPair.second
+                // Once a row is filled, make a new row and increase the menu height.
+                if (iconCount % MENU_MAX_ICONS_PER_ROW == 0) {
+                    rowLayout = LinearLayout(context)
+                    rowLayout.orientation = LinearLayout.HORIZONTAL
+                    rootView.addView(rowLayout)
+                    menuHeight += (instanceIconHeight + iconMargin).toInt()
+                }
+                val snapshotBitmap = Bitmap.wrapHardwareBuffer(
+                    snapshot.hardwareBuffer,
+                    snapshot.colorSpace
+                )
+                val scaledSnapshotBitmap = snapshotBitmap?.let {
+                    Bitmap.createScaledBitmap(
+                        it, instanceIconWidth.toInt(), instanceIconHeight.toInt(), true /* filter */
+                    )
+                }
+                val appSnapshotButton = SurfaceView(context)
+                appSnapshotButton.cornerRadius = iconRadius
+                appSnapshotButton.setZOrderOnTop(true)
+                appSnapshotButton.setOnClickListener {
+                    onIconClickListener?.invoke(taskId)
+                }
+                val lp = MarginLayoutParams(
+                    instanceIconWidth.toInt(), instanceIconHeight.toInt()
+                )
+                lp.apply {
+                    marginStart = iconMargin.toInt()
+                    topMargin = iconMargin.toInt()
+                }
+                appSnapshotButton.layoutParams = lp
+                // If we haven't already reached one full row, increment width.
+                if (iconCount < MENU_MAX_ICONS_PER_ROW) {
+                    menuWidth += (instanceIconWidth + iconMargin).toInt()
+                }
+                rowLayout?.addView(appSnapshotButton)
+                appSnapshotButton.requestLayout()
+                rowLayout?.post {
+                    appSnapshotButton.holder.surface
+                        .attachAndQueueBufferWithColorSpace(
+                            scaledSnapshotBitmap?.hardwareBuffer,
+                            scaledSnapshotBitmap?.colorSpace
+                        )
+                }
+            }
+            // Add margin again for the right/bottom of the menu.
+            menuWidth += iconMargin.toInt()
+            menuHeight += iconMargin.toInt()
+        }
+
+        companion object {
+            private const val MENU_RADIUS_DP = 26f
+            private const val ICON_WIDTH_DP = 204f
+            private const val ICON_HEIGHT_DP = 127.5f
+            private const val ICON_RADIUS_DP = 16f
+            private const val ICON_MARGIN_DP = 16f
+            private const val MENU_ELEVATION_DP = 1f
+            private const val MENU_MAX_ICONS_PER_ROW = 3
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TEST_MAPPING b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TEST_MAPPING
index f02559f..df3a369 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TEST_MAPPING
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TEST_MAPPING
@@ -1,32 +1,10 @@
 {
   "presubmit": [
     {
-      "name": "WMShellUnitTests",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "include-filter": "com.android.wm.shell.back"
-        }
-      ]
+      "name": "WMShellUnitTests_shell_back"
     },
     {
-      "name": "CtsWindowManagerDeviceBackNavigation",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "include-filter": "android.server.wm.backnavigation.BackGestureInvokedTest"
-        },
-        {
-          "include-filter": "android.server.wm.backnavigation.BackNavigationTests"
-        },
-        {
-          "include-filter": "android.server.wm.backnavigation.OnBackInvokedCallbackGestureTest"
-        }
-      ]
+      "name": "CtsWindowManagerDeviceBackNavigation_com_android_wm_shell_back"
     }
   ]
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index f03daad..c4082d9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -303,21 +303,29 @@
                             lastSurfacePosition);
                 } else {
                     if (!haveSameLeash(mImeSourceControl, imeSourceControl)) {
-                        applyVisibilityToLeash(imeSourceControl);
-
                         if (android.view.inputmethod.Flags.refactorInsetsController()) {
                             pendingImeStartAnimation = true;
+                            // The starting point for the IME should be it's previous state
+                            // (whether it is initiallyVisible or not)
+                            updateImeVisibility(imeSourceControl.isInitiallyVisible());
                         }
+                        applyVisibilityToLeash(imeSourceControl);
                     }
                     if (!mImeShowing) {
                         removeImeSurface(mDisplayId);
                     }
                 }
-            } else if (!android.view.inputmethod.Flags.refactorInsetsController()
-                    && mAnimation != null) {
-                // we don"t want to cancel the hide animation, when the control is lost, but
-                // continue the bar to slide to the end (even without visible IME)
-                mAnimation.cancel();
+            } else {
+                if (!android.view.inputmethod.Flags.refactorInsetsController()
+                        && mAnimation != null) {
+                    // we don't want to cancel the hide animation, when the control is lost, but
+                    // continue the bar to slide to the end (even without visible IME)
+                    mAnimation.cancel();
+                } else if (android.view.inputmethod.Flags.refactorInsetsController() && mImeShowing
+                        && mAnimation == null) {
+                    // There is no leash, so the IME cannot be in a showing state
+                    updateImeVisibility(false);
+                }
             }
 
             // Make mImeSourceControl point to the new control before starting the animation.
@@ -341,7 +349,7 @@
 
             if (android.view.inputmethod.Flags.refactorInsetsController()) {
                 if (pendingImeStartAnimation) {
-                    startAnimation(true, true /* forceRestart */);
+                    startAnimation(mImeRequestedVisible, true /* forceRestart */);
                 }
             }
         }
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 7054c17c..8c7dcf2 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
@@ -18,6 +18,7 @@
 
 import static com.android.wm.shell.shared.desktopmode.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASK_LIMIT;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.KeyguardManager;
 import android.content.Context;
@@ -114,6 +115,8 @@
 import com.android.wm.shell.windowdecor.CaptionWindowDecorViewModel;
 import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel;
 import com.android.wm.shell.windowdecor.WindowDecorViewModel;
+import com.android.wm.shell.windowdecor.viewhost.DefaultWindowDecorViewHostSupplier;
+import com.android.wm.shell.windowdecor.viewhost.WindowDecorViewHostSupplier;
 
 import dagger.Binds;
 import dagger.Lazy;
@@ -244,7 +247,8 @@
             AssistContentRequester assistContentRequester,
             MultiInstanceHelper multiInstanceHelper,
             Optional<DesktopTasksLimiter> desktopTasksLimiter,
-            Optional<DesktopActivityOrientationChangeHandler> desktopActivityOrientationHandler) {
+            Optional<DesktopActivityOrientationChangeHandler> desktopActivityOrientationHandler,
+            WindowDecorViewHostSupplier windowDecorViewHostSupplier) {
         if (DesktopModeStatus.canEnterDesktopMode(context)) {
             return new DesktopModeWindowDecorViewModel(
                     context,
@@ -268,7 +272,8 @@
                     assistContentRequester,
                     multiInstanceHelper,
                     desktopTasksLimiter,
-                    desktopActivityOrientationHandler);
+                    desktopActivityOrientationHandler,
+                    windowDecorViewHostSupplier);
         }
         return new CaptionWindowDecorViewModel(
                 context,
@@ -282,7 +287,8 @@
                 displayController,
                 rootTaskDisplayAreaOrganizer,
                 syncQueue,
-                transitions);
+                transitions,
+                windowDecorViewHostSupplier);
     }
 
     @WMSingleton
@@ -371,6 +377,13 @@
                 context, shellInit, transitions, windowDecorViewModel);
     }
 
+    @WMSingleton
+    @Provides
+    static WindowDecorViewHostSupplier provideWindowDecorViewHostSupplier(
+            @ShellMainThread @NonNull CoroutineScope mainScope) {
+        return new DefaultWindowDecorViewHostSupplier(mainScope);
+    }
+
     //
     // One handed mode
     //
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 1d16980..7e96253 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
@@ -1077,45 +1077,47 @@
             request.triggerTask != null
     }
 
+    /** Open an existing instance of an app. */
+    fun openInstance(
+        callingTask: RunningTaskInfo,
+        requestedTaskId: Int
+    ) {
+        val wct = WindowContainerTransaction()
+        val options = createNewWindowOptions(callingTask)
+        if (options.launchWindowingMode == WINDOWING_MODE_FREEFORM) {
+            wct.startTask(requestedTaskId, options.toBundle())
+            transitions.startTransition(TRANSIT_OPEN, wct, null)
+        } else {
+            val splitPosition = splitScreenController.determineNewInstancePosition(callingTask)
+            splitScreenController.startTask(requestedTaskId, splitPosition,
+                options.toBundle(), null /* hideTaskToken */)
+        }
+    }
+
+    /** Create an Intent to open a new window of a task. */
     fun openNewWindow(
-        taskInfo: RunningTaskInfo
+        callingTaskInfo: RunningTaskInfo
     ) {
         // TODO(b/337915660): Add a transition handler for these; animations
         //  need updates in some cases.
-        val newTaskWindowingMode = when {
-            taskInfo.isFreeform -> {
-                WINDOWING_MODE_FREEFORM
-            }
-            taskInfo.isFullscreen || taskInfo.isMultiWindow -> {
-                WINDOWING_MODE_MULTI_WINDOW
-            }
-            else -> {
-                error("Invalid windowing mode: ${taskInfo.windowingMode}")
-            }
-        }
-
-        val baseActivity = taskInfo.baseActivity ?: return
+        val baseActivity = callingTaskInfo.baseActivity ?: return
         val fillIn: Intent = context.packageManager
             .getLaunchIntentForPackage(
                 baseActivity.packageName
             ) ?: return
         fillIn
             .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
-        val options =
-            ActivityOptions.makeBasic().apply {
-                launchWindowingMode = newTaskWindowingMode
-                pendingIntentBackgroundActivityStartMode =
-                    ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
-            }
         val launchIntent = PendingIntent.getActivity(
             context,
             /* requestCode= */ 0,
             fillIn,
             PendingIntent.FLAG_IMMUTABLE
         )
-        when (newTaskWindowingMode) {
+        val options = createNewWindowOptions(callingTaskInfo)
+        when (options.launchWindowingMode) {
             WINDOWING_MODE_MULTI_WINDOW -> {
-                val splitPosition = splitScreenController.determineNewInstancePosition(taskInfo)
+                val splitPosition = splitScreenController
+                    .determineNewInstancePosition(callingTaskInfo)
                 splitScreenController.startIntent(
                     launchIntent, context.userId, fillIn, splitPosition,
                     options.toBundle(), null /* hideTaskToken */
@@ -1130,6 +1132,25 @@
         }
     }
 
+    private fun createNewWindowOptions(callingTask: RunningTaskInfo): ActivityOptions {
+        val newTaskWindowingMode = when {
+            callingTask.isFreeform -> {
+                WINDOWING_MODE_FREEFORM
+            }
+            callingTask.isFullscreen || callingTask.isMultiWindow -> {
+                WINDOWING_MODE_MULTI_WINDOW
+            }
+            else -> {
+                error("Invalid windowing mode: ${callingTask.windowingMode}")
+            }
+        }
+        return ActivityOptions.makeBasic().apply {
+            launchWindowingMode = newTaskWindowingMode
+            pendingIntentBackgroundActivityStartMode =
+                ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
+        }
+    }
+
     /**
      * Handles the case where a freeform task is launched from recents.
      *
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 d72ec90..dfc5ab3 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
@@ -629,15 +629,20 @@
         finishTransaction: SurfaceControl.Transaction?
     ) {
         val state = transitionState ?: return
-        if (aborted && state.startTransitionToken == transition) {
+        if (!aborted) {
+            return
+        }
+        if (state.startTransitionToken == transition) {
             ProtoLog.v(
                 ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
                 "DragToDesktop: onTransitionConsumed() start transition aborted"
             )
             state.startAborted = true
-            // Cancel CUJ interaction if the transition is aborted.
+            // The start-transition (DRAG_HOLD) is aborted, cancel its jank interaction.
             interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD)
         } else if (state.cancelTransitionToken != transition) {
+            // This transition being aborted is neither the start, nor the cancel transition, so
+            // it must be the finish transition (DRAG_RELEASE); cancel its jank interaction.
             interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE)
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index cf02fb5..22e8dc1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -26,7 +26,6 @@
 import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-import static android.view.WindowManager.LayoutParams.MATCH_PARENT;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
@@ -247,9 +246,8 @@
                 R.layout.global_drop_target, null);
         rootView.setOnDragListener(this);
         rootView.setVisibility(View.INVISIBLE);
-        DragLayout dragLayout = new DragLayout(context, mSplitScreen, mIconProvider);
-        rootView.addView(dragLayout,
-                new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+        DragLayoutProvider dragLayout = new DragLayout(context, mSplitScreen, mIconProvider);
+        dragLayout.addDraggingView(rootView);
         try {
             wm.addView(rootView, layoutParams);
             addDisplayDropTarget(displayId, context, wm, rootView, dragLayout);
@@ -261,7 +259,7 @@
 
     @VisibleForTesting
     void addDisplayDropTarget(int displayId, Context context, WindowManager wm,
-            FrameLayout rootView, DragLayout dragLayout) {
+            FrameLayout rootView, DragLayoutProvider dragLayout) {
         mDisplayDropTargets.put(displayId,
                 new PerDisplay(displayId, context, wm, rootView, dragLayout));
     }
@@ -564,7 +562,7 @@
         final Context context;
         final WindowManager wm;
         final FrameLayout rootView;
-        final DragLayout dragLayout;
+        final DragLayoutProvider dragLayout;
         // Tracks whether the window has fully drawn since it was last made visible
         boolean hasDrawn;
 
@@ -575,7 +573,7 @@
         // The active drag session
         DragSession dragSession;
 
-        PerDisplay(int dispId, Context c, WindowManager w, FrameLayout rv, DragLayout dl) {
+        PerDisplay(int dispId, Context c, WindowManager w, FrameLayout rv, DragLayoutProvider dl) {
             displayId = dispId;
             context = c;
             wm = w;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
index 3fecbe7..dfa2437 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -23,10 +23,10 @@
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
 
-import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_BOTTOM;
-import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_LEFT;
-import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT;
-import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_TOP;
+import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_SPLIT_BOTTOM;
+import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_SPLIT_LEFT;
+import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_SPLIT_RIGHT;
+import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_SPLIT_TOP;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
 
@@ -47,6 +47,7 @@
 import android.graphics.drawable.Drawable;
 import android.view.DragEvent;
 import android.view.SurfaceControl;
+import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
 import android.view.WindowInsets;
 import android.view.WindowInsets.Type;
@@ -66,13 +67,13 @@
 import com.android.wm.shell.splitscreen.SplitScreenController;
 
 import java.io.PrintWriter;
-import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Coordinates the visible drop targets for the current drag within a single display.
  */
 public class DragLayout extends LinearLayout
-        implements ViewTreeObserver.OnComputeInternalInsetsListener {
+        implements ViewTreeObserver.OnComputeInternalInsetsListener, DragLayoutProvider {
 
     // While dragging the status bar is hidden.
     private static final int HIDE_STATUS_BAR_FLAGS = StatusBarManager.DISABLE_NOTIFICATION_ICONS
@@ -80,7 +81,7 @@
             | StatusBarManager.DISABLE_CLOCK
             | StatusBarManager.DISABLE_SYSTEM_INFO;
 
-    private final DragAndDropPolicy mPolicy;
+    private final DropTarget mPolicy;
     private final SplitScreenController mSplitScreenController;
     private final IconProvider mIconProvider;
     private final StatusBarManager mStatusBarManager;
@@ -91,7 +92,7 @@
     // Whether the device is currently in left/right split mode
     private boolean mIsLeftRightSplit;
 
-    private DragAndDropPolicy.Target mCurrentTarget = null;
+    private SplitDragPolicy.Target mCurrentTarget = null;
     private DropZoneView mDropZoneView1;
     private DropZoneView mDropZoneView2;
 
@@ -113,7 +114,7 @@
         super(context);
         mSplitScreenController = splitScreenController;
         mIconProvider = iconProvider;
-        mPolicy = new DragAndDropPolicy(context, splitScreenController);
+        mPolicy = new SplitDragPolicy(context, splitScreenController);
         mStatusBarManager = context.getSystemService(StatusBarManager.class);
         mLastConfiguration.setTo(context.getResources().getConfiguration());
 
@@ -387,6 +388,13 @@
         recomputeDropTargets();
     }
 
+    @NonNull
+    @Override
+    public void addDraggingView(ViewGroup rootView) {
+        // TODO(b/349828130) We need to separate out view + logic here
+        rootView.addView(this, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+    }
+
     /**
      * Recalculates the drop targets based on the current policy.
      */
@@ -394,9 +402,9 @@
         if (!mIsShowing) {
             return;
         }
-        final ArrayList<DragAndDropPolicy.Target> targets = mPolicy.getTargets(mInsets);
+        final List<SplitDragPolicy.Target> targets = mPolicy.getTargets(mInsets);
         for (int i = 0; i < targets.size(); i++) {
-            final DragAndDropPolicy.Target target = targets.get(i);
+            final SplitDragPolicy.Target target = targets.get(i);
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Add target: %s", target);
             // Inset the draw region by a little bit
             target.drawRegion.inset(mDisplayMargin, mDisplayMargin);
@@ -419,7 +427,7 @@
         }
         // Find containing region, if the same as mCurrentRegion, then skip, otherwise, animate the
         // visibility of the current region
-        DragAndDropPolicy.Target target = mPolicy.getTargetAtLocation(x, y);
+        SplitDragPolicy.Target target = mPolicy.getTargetAtLocation(x, y);
         if (mCurrentTarget != target) {
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Current target: %s", target);
             if (target == null) {
@@ -493,7 +501,7 @@
         mHasDropped = true;
 
         // Process the drop
-        mPolicy.handleDrop(mCurrentTarget, hideTaskToken);
+        mPolicy.onDropped(mCurrentTarget, hideTaskToken);
 
         // Start animating the drop UI out with the drag surface
         hide(event, dropCompleteCallback);
@@ -576,7 +584,7 @@
         }
     }
 
-    private void animateHighlight(DragAndDropPolicy.Target target) {
+    private void animateHighlight(SplitDragPolicy.Target target) {
         if (target.type == TYPE_SPLIT_LEFT || target.type == TYPE_SPLIT_TOP) {
             mDropZoneView1.setShowingHighlight(true);
             mDropZoneView2.setShowingHighlight(false);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayoutProvider.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayoutProvider.kt
new file mode 100644
index 0000000..3d40824
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayoutProvider.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.draganddrop
+
+import android.content.res.Configuration
+import android.view.DragEvent
+import android.view.SurfaceControl
+import android.view.ViewGroup
+import android.window.WindowContainerToken
+import com.android.internal.logging.InstanceId
+import java.io.PrintWriter
+
+/** Interface to be implemented by any controllers providing a layout for DragAndDrop in Shell */
+interface DragLayoutProvider {
+    /**
+     * Updates the drag layout based on the given drag session.
+     */
+    fun updateSession(session: DragSession)
+    /**
+     * Called when a new drag is started.
+     */
+    fun prepare(session: DragSession, loggerSessionId: InstanceId?)
+
+    /**
+     * Shows the drag layout.
+     */
+    fun show()
+
+    /**
+     * Updates the visible drop target as the user drags.
+     */
+    fun update(event: DragEvent?)
+
+    /**
+     * Hides the drag layout and animates out the visible drop targets.
+     */
+    fun hide(event: DragEvent?, hideCompleteCallback: Runnable?)
+
+    /**
+     * Whether target has already been dropped or not
+     */
+    fun hasDropped(): Boolean
+
+    /**
+     * Handles the drop onto a target and animates out the visible drop targets.
+     */
+    fun drop(
+        event: DragEvent?, dragSurface: SurfaceControl,
+        hideTaskToken: WindowContainerToken?, dropCompleteCallback: Runnable?
+    ): Boolean
+
+    /**
+     * Dumps information about this drag layout.
+     */
+    fun dump(pw: PrintWriter, prefix: String?)
+
+    /**
+     * @return a View which will be added to the global root view for drag and drop
+     */
+    fun addDraggingView(viewGroup: ViewGroup)
+
+    /**
+     * Called when the configuration changes.
+     */
+    fun onConfigChanged(newConfig: Configuration?)
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropTarget.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropTarget.kt
new file mode 100644
index 0000000..122a105
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropTarget.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.
+ */
+
+package com.android.wm.shell.draganddrop
+
+import android.graphics.Insets
+import android.window.WindowContainerToken
+import com.android.internal.logging.InstanceId
+
+/**
+ * Interface to be implemented by classes which want to provide drop targets
+ * for DragAndDrop in Shell
+ */
+interface DropTarget {
+    // TODO(b/349828130) Delete after flexible split launches
+    /**
+     * Called at the start of a Drag, before input events are processed.
+     */
+    fun start(dragSession: DragSession, logSessionId: InstanceId)
+    /**
+     * @return [SplitDragPolicy.Target] corresponding to the given coords in display bounds.
+     */
+    fun getTargetAtLocation(x: Int, y: Int) : SplitDragPolicy.Target
+    /**
+     * @return total number of drop targets for the current drag session.
+     */
+    fun getNumTargets() : Int
+    // TODO(b/349828130)
+
+    /**
+     * @return [List<SplitDragPolicy.Target>] to show for the current drag session.
+     */
+    fun getTargets(insets: Insets) : List<SplitDragPolicy.Target>
+    /**
+     * Called when user is hovering Drag object over the given Target
+     */
+    fun onHoveringOver(target: SplitDragPolicy.Target) {}
+    /**
+     * Called when the user has dropped the provided target (need not be the same target as
+     * [onHoveringOver])
+     */
+    fun onDropped(target: SplitDragPolicy.Target, hideTaskToken: WindowContainerToken)
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/SplitDragPolicy.java
similarity index 93%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/SplitDragPolicy.java
index 6fec0c1..2a19d65 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/SplitDragPolicy.java
@@ -32,11 +32,11 @@
 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 
-import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_FULLSCREEN;
-import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_BOTTOM;
-import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_LEFT;
-import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT;
-import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_TOP;
+import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_FULLSCREEN;
+import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_SPLIT_BOTTOM;
+import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_SPLIT_LEFT;
+import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_SPLIT_RIGHT;
+import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_SPLIT_TOP;
 import static com.android.wm.shell.shared.draganddrop.DragAndDropConstants.EXTRA_DISALLOW_HIT_REGION;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
@@ -80,9 +80,9 @@
 /**
  * The policy for handling drag and drop operations to shell.
  */
-public class DragAndDropPolicy {
+public class SplitDragPolicy implements DropTarget {
 
-    private static final String TAG = DragAndDropPolicy.class.getSimpleName();
+    private static final String TAG = SplitDragPolicy.class.getSimpleName();
 
     private final Context mContext;
     // Used only for launching a fullscreen task (or as a fallback if there is no split starter)
@@ -90,18 +90,18 @@
     // Used for launching tasks into splitscreen
     private final Starter mSplitscreenStarter;
     private final SplitScreenController mSplitScreen;
-    private final ArrayList<DragAndDropPolicy.Target> mTargets = new ArrayList<>();
+    private final ArrayList<SplitDragPolicy.Target> mTargets = new ArrayList<>();
     private final RectF mDisallowHitRegion = new RectF();
 
     private InstanceId mLoggerSessionId;
     private DragSession mSession;
 
-    public DragAndDropPolicy(Context context, SplitScreenController splitScreen) {
+    public SplitDragPolicy(Context context, SplitScreenController splitScreen) {
         this(context, splitScreen, new DefaultStarter(context));
     }
 
     @VisibleForTesting
-    DragAndDropPolicy(Context context, SplitScreenController splitScreen,
+    SplitDragPolicy(Context context, SplitScreenController splitScreen,
             Starter fullscreenStarter) {
         mContext = context;
         mSplitScreen = splitScreen;
@@ -112,7 +112,7 @@
     /**
      * Starts a new drag session with the given initial drag data.
      */
-    void start(DragSession session, InstanceId loggerSessionId) {
+    public void start(DragSession session, InstanceId loggerSessionId) {
         mLoggerSessionId = loggerSessionId;
         mSession = session;
         RectF disallowHitRegion = mSession.appData != null
@@ -128,7 +128,8 @@
     /**
      * Returns the number of targets.
      */
-    int getNumTargets() {
+    @Override
+    public int getNumTargets() {
         return mTargets.size();
     }
 
@@ -136,7 +137,8 @@
      * Returns the target's regions based on the current state of the device and display.
      */
     @NonNull
-    ArrayList<Target> getTargets(Insets insets) {
+    @Override
+    public ArrayList<Target> getTargets(@NonNull Insets insets) {
         mTargets.clear();
         if (mSession == null) {
             // Return early if this isn't an app drag
@@ -222,12 +224,12 @@
      * Returns the target at the given position based on the targets previously calculated.
      */
     @Nullable
-    Target getTargetAtLocation(int x, int y) {
+    public Target getTargetAtLocation(int x, int y) {
         if (mDisallowHitRegion.contains(x, y)) {
             return null;
         }
         for (int i = mTargets.size() - 1; i >= 0; i--) {
-            DragAndDropPolicy.Target t = mTargets.get(i);
+            SplitDragPolicy.Target t = mTargets.get(i);
             if (t.hitRegion.contains(x, y)) {
                 return t;
             }
@@ -241,7 +243,7 @@
      * container transaction if possible.
      */
     @VisibleForTesting
-    void handleDrop(Target target, @Nullable WindowContainerToken hideTaskToken) {
+    public void onDropped(Target target, @Nullable WindowContainerToken hideTaskToken) {
         if (target == null || !mTargets.contains(target)) {
             return;
         }
@@ -419,8 +421,9 @@
 
     /**
      * Represents a drop target.
+     * TODO(b/349828130): Move this into {@link DropTarget}
      */
-    static class Target {
+    public static class Target {
         static final int TYPE_FULLSCREEN = 0;
         static final int TYPE_SPLIT_LEFT = 1;
         static final int TYPE_SPLIT_TOP = 2;
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 ab222c9..b4e0329 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
@@ -2033,7 +2033,10 @@
         tx.apply();
     }
 
-    private void cancelCurrentAnimator() {
+    /**
+     * Cancels the currently running animator if there is one and removes an overlay if present.
+     */
+    public void cancelCurrentAnimator() {
         final PipAnimationController.PipTransitionAnimator<?> animator =
                 mPipAnimationController.getCurrentAnimator();
         // remove any overlays if present
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 755e958..deb7691 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -324,7 +324,7 @@
                         return;
                     }
                     onDisplayChanged(mDisplayController.getDisplayLayout(displayId),
-                            false /* saveRestoreSnapFraction */);
+                            true /* saveRestoreSnapFraction */);
                 }
 
                 @Override
@@ -652,9 +652,11 @@
                             // there's a keyguard present
                             return;
                         }
-                        onDisplayChangedUncheck(mDisplayController
-                                        .getDisplayLayout(mPipDisplayLayoutState.getDisplayId()),
-                                false /* saveRestoreSnapFraction */);
+                        mMainExecutor.executeDelayed(() -> {
+                            onDisplayChangedUncheck(mDisplayController.getDisplayLayout(
+                                    mPipDisplayLayoutState.getDisplayId()),
+                                    false /* saveRestoreSnapFraction */);
+                        }, PIP_KEEP_CLEAR_AREAS_DELAY);
                     }
                 });
 
@@ -800,7 +802,7 @@
             }
         };
 
-        if (mPipTaskOrganizer.isInPip() && saveRestoreSnapFraction) {
+        if (mPipTransitionState.hasEnteredPip() && saveRestoreSnapFraction) {
             mMenuController.attachPipMenuView();
             // Calculate the snap fraction of the current stack along the old movement bounds
             final PipSnapAlgorithm pipSnapAlgorithm = mPipBoundsAlgorithm.getSnapAlgorithm();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 793e2aa..87b661d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -92,7 +92,7 @@
 import com.android.wm.shell.common.split.SplitScreenUtils;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
 import com.android.wm.shell.draganddrop.DragAndDropController;
-import com.android.wm.shell.draganddrop.DragAndDropPolicy;
+import com.android.wm.shell.draganddrop.SplitDragPolicy;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.recents.RecentTasksController;
 import com.android.wm.shell.shared.TransactionPool;
@@ -121,7 +121,7 @@
  * @see StageCoordinator
  */
 // TODO(b/198577848): Implement split screen flicker test to consolidate CUJ of split screen.
-public class SplitScreenController implements DragAndDropPolicy.Starter,
+public class SplitScreenController implements SplitDragPolicy.Starter,
         RemoteCallable<SplitScreenController>, KeyguardChangeListener {
     private static final String TAG = SplitScreenController.class.getSimpleName();
 
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 1b143eb..4ba84ee 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
@@ -34,7 +34,6 @@
 import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
 
-import static com.android.wm.shell.Flags.enableFlexibleSplit;
 import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_ALIGN_CENTER;
 import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
 import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage;
@@ -1668,7 +1667,6 @@
         if (mRootTaskInfo == null || mRootTaskInfo.taskId != taskInfo.taskId) {
             throw new IllegalArgumentException(this + "\n Unknown task info changed: " + taskInfo);
         }
-        mWindowDecorViewModel.ifPresent(viewModel -> viewModel.onTaskInfoChanged(taskInfo));
         mRootTaskInfo = taskInfo;
         if (mSplitLayout != null
                 && mSplitLayout.updateConfiguration(mRootTaskInfo.configuration)
@@ -2822,6 +2820,10 @@
                 mSplitLayout.flingDividerToCenter(this::notifySplitAnimationFinished);
             }
             callbackWct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, false);
+            mWindowDecorViewModel.ifPresent(viewModel -> {
+                viewModel.onTaskInfoChanged(finalMainChild.getTaskInfo());
+                viewModel.onTaskInfoChanged(finalSideChild.getTaskInfo());
+            });
             mPausingTasks.clear();
         });
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index f40e0ba..1573291 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -358,9 +358,12 @@
 
             if (mode == TRANSIT_CHANGE && change.hasFlags(FLAG_IS_DISPLAY)) {
                 if (info.getType() == TRANSIT_CHANGE) {
-                    final int anim = getRotationAnimationHint(change, info, mDisplayController);
+                    int anim = getRotationAnimationHint(change, info, mDisplayController);
                     isSeamlessDisplayChange = anim == ROTATION_ANIMATION_SEAMLESS;
                     if (!(isSeamlessDisplayChange || anim == ROTATION_ANIMATION_JUMPCUT)) {
+                        if (wallpaperTransit != WALLPAPER_TRANSITION_NONE) {
+                            anim |= ScreenRotationAnimation.ANIMATION_HINT_HAS_WALLPAPER;
+                        }
                         startRotationAnimation(startTransaction, change, info, anim, animations,
                                 onAnimFinish);
                         isDisplayRotationAnimationStarted = true;
@@ -826,24 +829,26 @@
             @NonNull Runnable finishCallback, @NonNull TransactionPool pool,
             @NonNull ShellExecutor mainExecutor, @Nullable Point position, float cornerRadius,
             @Nullable Rect clipRect, boolean isActivity) {
+        final DefaultAnimationAdapter adapter = new DefaultAnimationAdapter(anim, leash,
+                position, clipRect, cornerRadius, isActivity);
+        buildSurfaceAnimation(animations, anim, finishCallback, pool, mainExecutor, adapter);
+    }
+
+    /** Builds an animator for the surface and adds it to the `animations` list. */
+    static void buildSurfaceAnimation(@NonNull ArrayList<Animator> animations,
+            @NonNull Animation anim, @NonNull Runnable finishCallback,
+            @NonNull TransactionPool pool, @NonNull ShellExecutor mainExecutor,
+            @NonNull AnimationAdapter updateListener) {
         final SurfaceControl.Transaction transaction = pool.acquire();
+        updateListener.setTransaction(transaction);
         final ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
-        final Transformation transformation = new Transformation();
-        final float[] matrix = new float[9];
         // Animation length is already expected to be scaled.
         va.overrideDurationScale(1.0f);
         va.setDuration(anim.computeDurationHint());
-        final ValueAnimator.AnimatorUpdateListener updateListener = animation -> {
-            final long currentPlayTime = Math.min(va.getDuration(), va.getCurrentPlayTime());
-
-            applyTransformation(currentPlayTime, transaction, leash, anim, transformation, matrix,
-                    position, cornerRadius, clipRect, isActivity);
-        };
         va.addUpdateListener(updateListener);
 
         final Runnable finisher = () -> {
-            applyTransformation(va.getDuration(), transaction, leash, anim, transformation, matrix,
-                    position, cornerRadius, clipRect, isActivity);
+            updateListener.onAnimationUpdate(va);
 
             pool.release(transaction);
             mainExecutor.execute(() -> {
@@ -1007,37 +1012,88 @@
                 || animType == ANIM_FROM_STYLE;
     }
 
-    private static void applyTransformation(long time, SurfaceControl.Transaction t,
-            SurfaceControl leash, Animation anim, Transformation tmpTransformation, float[] matrix,
-            Point position, float cornerRadius, @Nullable Rect immutableClipRect,
-            boolean isActivity) {
-        tmpTransformation.clear();
-        anim.getTransformation(time, tmpTransformation);
-        if (com.android.graphics.libgui.flags.Flags.edgeExtensionShader()
-                && anim.getExtensionEdges() != 0x0 && isActivity) {
-            t.setEdgeExtensionEffect(leash, anim.getExtensionEdges());
-        }
-        if (position != null) {
-            tmpTransformation.getMatrix().postTranslate(position.x, position.y);
-        }
-        t.setMatrix(leash, tmpTransformation.getMatrix(), matrix);
-        t.setAlpha(leash, tmpTransformation.getAlpha());
+    /** The animation adapter for buildSurfaceAnimation. */
+    abstract static class AnimationAdapter implements ValueAnimator.AnimatorUpdateListener {
+        @NonNull final SurfaceControl mLeash;
+        @NonNull SurfaceControl.Transaction mTransaction;
+        private Choreographer mChoreographer;
 
-        final Rect clipRect = immutableClipRect == null ? null : new Rect(immutableClipRect);
-        Insets extensionInsets = Insets.min(tmpTransformation.getInsets(), Insets.NONE);
-        if (!extensionInsets.equals(Insets.NONE) && clipRect != null && !clipRect.isEmpty()) {
-            // Clip out any overflowing edge extension
-            clipRect.inset(extensionInsets);
-            t.setCrop(leash, clipRect);
+        AnimationAdapter(@NonNull SurfaceControl leash) {
+            mLeash = leash;
         }
 
-        if (anim.hasRoundedCorners() && cornerRadius > 0 && clipRect != null) {
-            // We can only apply rounded corner if a crop is set
-            t.setCrop(leash, clipRect);
-            t.setCornerRadius(leash, cornerRadius);
+        void setTransaction(@NonNull SurfaceControl.Transaction transaction) {
+            mTransaction = transaction;
         }
 
-        t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
-        t.apply();
+        @Override
+        public void onAnimationUpdate(@NonNull ValueAnimator animator) {
+            applyTransformation(animator);
+            if (mChoreographer == null) {
+                mChoreographer = Choreographer.getInstance();
+            }
+            mTransaction.setFrameTimelineVsync(mChoreographer.getVsyncId());
+            mTransaction.apply();
+        }
+
+        abstract void applyTransformation(@NonNull ValueAnimator animator);
+    }
+
+    private static class DefaultAnimationAdapter extends AnimationAdapter {
+        final Transformation mTransformation = new Transformation();
+        final float[] mMatrix = new float[9];
+        @NonNull final Animation mAnim;
+        @Nullable final Point mPosition;
+        @Nullable final Rect mClipRect;
+        final float mCornerRadius;
+        final boolean mIsActivity;
+
+        DefaultAnimationAdapter(@NonNull Animation anim, @NonNull SurfaceControl leash,
+                @Nullable Point position, @Nullable Rect clipRect, float cornerRadius,
+                boolean isActivity) {
+            super(leash);
+            mAnim = anim;
+            mPosition = (position != null && (position.x != 0 || position.y != 0))
+                    ? position : null;
+            mClipRect = (clipRect != null && !clipRect.isEmpty()) ? clipRect : null;
+            mCornerRadius = cornerRadius;
+            mIsActivity = isActivity;
+        }
+
+        @Override
+        void applyTransformation(@NonNull ValueAnimator animator) {
+            final long currentPlayTime = Math.min(animator.getDuration(),
+                    animator.getCurrentPlayTime());
+            final Transformation transformation = mTransformation;
+            final SurfaceControl.Transaction t = mTransaction;
+            final SurfaceControl leash = mLeash;
+            transformation.clear();
+            mAnim.getTransformation(currentPlayTime, transformation);
+            if (com.android.graphics.libgui.flags.Flags.edgeExtensionShader()
+                    && mIsActivity && mAnim.getExtensionEdges() != 0) {
+                t.setEdgeExtensionEffect(leash, mAnim.getExtensionEdges());
+            }
+            if (mPosition != null) {
+                transformation.getMatrix().postTranslate(mPosition.x, mPosition.y);
+            }
+            t.setMatrix(leash, transformation.getMatrix(), mMatrix);
+            t.setAlpha(leash, transformation.getAlpha());
+
+            if (mClipRect != null) {
+                Rect clipRect = mClipRect;
+                final Insets extensionInsets = Insets.min(transformation.getInsets(), Insets.NONE);
+                if (!extensionInsets.equals(Insets.NONE)) {
+                    // Clip out any overflowing edge extension.
+                    clipRect = new Rect(mClipRect);
+                    clipRect.inset(extensionInsets);
+                    t.setCrop(leash, clipRect);
+                }
+                if (mCornerRadius > 0 && mAnim.hasRoundedCorners()) {
+                    // Rounded corner can only be applied if a crop is set.
+                    t.setCrop(leash, clipRect);
+                    t.setCornerRadius(leash, mCornerRadius);
+                }
+            }
+        }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
index 5802e2c..b9d11a3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
@@ -25,12 +25,9 @@
 import static com.android.wm.shell.transition.Transitions.TAG;
 
 import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ArgbEvaluator;
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
 import android.content.Context;
-import android.graphics.Color;
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.hardware.HardwareBuffer;
@@ -38,6 +35,7 @@
 import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
+import android.view.animation.AccelerateInterpolator;
 import android.view.animation.Animation;
 import android.view.animation.AnimationUtils;
 import android.window.ScreenCapture;
@@ -74,6 +72,9 @@
  */
 class ScreenRotationAnimation {
     static final int MAX_ANIMATION_DURATION = 10 * 1000;
+    static final int ANIMATION_HINT_HAS_WALLPAPER = 1 << 8;
+    /** It must cover all WindowManager#ROTATION_ANIMATION_*. */
+    private static final int ANIMATION_TYPE_MASK = 0xff;
 
     private final Context mContext;
     private final TransactionPool mTransactionPool;
@@ -81,7 +82,7 @@
     /** The leash of the changing window container. */
     private final SurfaceControl mSurfaceControl;
 
-    private final int mAnimHint;
+    private final int mAnimType;
     private final int mStartWidth;
     private final int mStartHeight;
     private final int mEndWidth;
@@ -98,6 +99,12 @@
     private SurfaceControl mBackColorSurface;
     /** The leash using to animate screenshot layer. */
     private final SurfaceControl mAnimLeash;
+    /**
+     * The container with background color for {@link #mSurfaceControl}. It is only created if
+     * {@link #mSurfaceControl} may be translucent. E.g. visible wallpaper with alpha < 1 (dimmed).
+     * That prevents flickering of alpha blending.
+     */
+    private SurfaceControl mBackEffectSurface;
 
     // The current active animation to move from the old to the new rotated
     // state.  Which animation is run here will depend on the old and new
@@ -115,7 +122,7 @@
             Transaction t, TransitionInfo.Change change, SurfaceControl rootLeash, int animHint) {
         mContext = context;
         mTransactionPool = pool;
-        mAnimHint = animHint;
+        mAnimType = animHint & ANIMATION_TYPE_MASK;
 
         mSurfaceControl = change.getLeash();
         mStartWidth = change.getStartAbsBounds().width();
@@ -170,11 +177,20 @@
                 }
                 hardwareBuffer.close();
             }
+            if ((animHint & ANIMATION_HINT_HAS_WALLPAPER) != 0) {
+                mBackEffectSurface = new SurfaceControl.Builder()
+                        .setCallsite("ShellRotationAnimation").setParent(rootLeash)
+                        .setEffectLayer().setOpaque(true).setName("BackEffect").build();
+                t.reparent(mSurfaceControl, mBackEffectSurface)
+                        .setColor(mBackEffectSurface,
+                                new float[] {mStartLuma, mStartLuma, mStartLuma})
+                        .show(mBackEffectSurface);
+            }
 
             t.setLayer(mAnimLeash, SCREEN_FREEZE_LAYER_BASE);
             t.show(mAnimLeash);
             // Crop the real content in case it contains a larger child layer, e.g. wallpaper.
-            t.setCrop(mSurfaceControl, new Rect(0, 0, mEndWidth, mEndHeight));
+            t.setCrop(getEnterSurface(), new Rect(0, 0, mEndWidth, mEndHeight));
 
             if (!isCustomRotate()) {
                 mBackColorSurface = new SurfaceControl.Builder()
@@ -199,7 +215,12 @@
     }
 
     private boolean isCustomRotate() {
-        return mAnimHint == ROTATION_ANIMATION_CROSSFADE || mAnimHint == ROTATION_ANIMATION_JUMPCUT;
+        return mAnimType == ROTATION_ANIMATION_CROSSFADE || mAnimType == ROTATION_ANIMATION_JUMPCUT;
+    }
+
+    /** Returns the surface which contains the real content to animate enter. */
+    private SurfaceControl getEnterSurface() {
+        return mBackEffectSurface != null ? mBackEffectSurface : mSurfaceControl;
     }
 
     private void setScreenshotTransform(SurfaceControl.Transaction t) {
@@ -260,7 +281,7 @@
         final boolean customRotate = isCustomRotate();
         if (customRotate) {
             mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
-                    mAnimHint == ROTATION_ANIMATION_JUMPCUT ? R.anim.rotation_animation_jump_exit
+                    mAnimType == ROTATION_ANIMATION_JUMPCUT ? R.anim.rotation_animation_jump_exit
                             : R.anim.rotation_animation_xfade_exit);
             mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
                     R.anim.rotation_animation_enter);
@@ -314,7 +335,11 @@
         } else {
             startDisplayRotation(animations, finishCallback, mainExecutor);
             startScreenshotRotationAnimation(animations, finishCallback, mainExecutor);
-            //startColorAnimation(mTransaction, animationScale);
+            if (mBackEffectSurface != null && mStartLuma > 0.1f) {
+                // Animate from the color of background to black for smooth alpha blending.
+                buildLumaAnimation(animations, mStartLuma, 0f /* endLuma */, mBackEffectSurface,
+                        animationScale, finishCallback, mainExecutor);
+            }
         }
 
         return true;
@@ -322,7 +347,7 @@
 
     private void startDisplayRotation(@NonNull ArrayList<Animator> animations,
             @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) {
-        buildSurfaceAnimation(animations, mRotateEnterAnimation, mSurfaceControl, finishCallback,
+        buildSurfaceAnimation(animations, mRotateEnterAnimation, getEnterSurface(), finishCallback,
                 mTransactionPool, mainExecutor, null /* position */, 0 /* cornerRadius */,
                 null /* clipRect */, false /* isActivity */);
     }
@@ -341,40 +366,17 @@
                 null /* clipRect */, false /* isActivity */);
     }
 
-    private void startColorAnimation(float animationScale, @NonNull ShellExecutor animExecutor) {
-        int colorTransitionMs = mContext.getResources().getInteger(
-                R.integer.config_screen_rotation_color_transition);
-        final float[] rgbTmpFloat = new float[3];
-        final int startColor = Color.rgb(mStartLuma, mStartLuma, mStartLuma);
-        final int endColor = Color.rgb(mEndLuma, mEndLuma, mEndLuma);
-        final long duration = colorTransitionMs * (long) animationScale;
-        final Transaction t = mTransactionPool.acquire();
-
-        final ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
-        // Animation length is already expected to be scaled.
-        va.overrideDurationScale(1.0f);
-        va.setDuration(duration);
-        va.addUpdateListener(animation -> {
-            final long currentPlayTime = Math.min(va.getDuration(), va.getCurrentPlayTime());
-            final float fraction = currentPlayTime / va.getDuration();
-            applyColor(startColor, endColor, rgbTmpFloat, fraction, mBackColorSurface, t);
-        });
-        va.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationCancel(Animator animation) {
-                applyColor(startColor, endColor, rgbTmpFloat, 1f /* fraction */, mBackColorSurface,
-                        t);
-                mTransactionPool.release(t);
-            }
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                applyColor(startColor, endColor, rgbTmpFloat, 1f /* fraction */, mBackColorSurface,
-                        t);
-                mTransactionPool.release(t);
-            }
-        });
-        animExecutor.execute(va::start);
+    private void buildLumaAnimation(@NonNull ArrayList<Animator> animations,
+            float startLuma, float endLuma, SurfaceControl surface, float animationScale,
+            @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) {
+        final long durationMillis = (long) (mContext.getResources().getInteger(
+                R.integer.config_screen_rotation_color_transition) * animationScale);
+        final LumaAnimation animation = new LumaAnimation(durationMillis);
+        // Align the end with the enter animation.
+        animation.setStartOffset(mRotateEnterAnimation.getDuration() - durationMillis);
+        final LumaAnimationAdapter adapter = new LumaAnimationAdapter(surface, startLuma, endLuma);
+        buildSurfaceAnimation(animations, animation, finishCallback, mTransactionPool,
+                mainExecutor, adapter);
     }
 
     public void kill() {
@@ -389,21 +391,47 @@
         if (mBackColorSurface != null && mBackColorSurface.isValid()) {
             t.remove(mBackColorSurface);
         }
+        if (mBackEffectSurface != null && mBackEffectSurface.isValid()) {
+            t.remove(mBackEffectSurface);
+        }
         t.apply();
         mTransactionPool.release(t);
     }
 
-    private static void applyColor(int startColor, int endColor, float[] rgbFloat,
-            float fraction, SurfaceControl surface, SurfaceControl.Transaction t) {
-        final int color = (Integer) ArgbEvaluator.getInstance().evaluate(fraction, startColor,
-                endColor);
-        Color middleColor = Color.valueOf(color);
-        rgbFloat[0] = middleColor.red();
-        rgbFloat[1] = middleColor.green();
-        rgbFloat[2] = middleColor.blue();
-        if (surface.isValid()) {
-            t.setColor(surface, rgbFloat);
+    /** A no-op wrapper to provide animation duration. */
+    private static class LumaAnimation extends Animation {
+        LumaAnimation(long durationMillis) {
+            setDuration(durationMillis);
         }
-        t.apply();
+    }
+
+    private static class LumaAnimationAdapter extends DefaultTransitionHandler.AnimationAdapter {
+        final float[] mColorArray = new float[3];
+        final float mStartLuma;
+        final float mEndLuma;
+        final AccelerateInterpolator mInterpolation;
+
+        LumaAnimationAdapter(@NonNull SurfaceControl leash, float startLuma, float endLuma) {
+            super(leash);
+            mStartLuma = startLuma;
+            mEndLuma = endLuma;
+            // Make the initial progress color lighter if the background is light. That avoids
+            // darker content when fading into the entering surface.
+            final float factor = Math.min(3f, (Math.max(0.5f, mStartLuma) - 0.5f) * 10);
+            Slog.d(TAG, "Luma=" + mStartLuma + " factor=" + factor);
+            mInterpolation = factor > 0.5f ? new AccelerateInterpolator(factor) : null;
+        }
+
+        @Override
+        void applyTransformation(ValueAnimator animator) {
+            final float fraction = mInterpolation != null
+                    ? mInterpolation.getInterpolation(animator.getAnimatedFraction())
+                    : animator.getAnimatedFraction();
+            final float luma = mStartLuma + fraction * (mEndLuma - mStartLuma);
+            mColorArray[0] = luma;
+            mColorArray[1] = luma;
+            mColorArray[2] = luma;
+            mTransaction.setColor(mLeash, mColorArray);
+        }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 11976ae..0151395 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -62,6 +62,7 @@
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
 import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
+import com.android.wm.shell.windowdecor.viewhost.WindowDecorViewHostSupplier;
 
 /**
  * View model for the window decoration with a caption and shadows. Works with
@@ -83,6 +84,7 @@
     private final Transitions mTransitions;
     private final Region mExclusionRegion = Region.obtain();
     private final InputManager mInputManager;
+    private final WindowDecorViewHostSupplier mWindowDecorViewHostSupplier;
     private TaskOperations mTaskOperations;
 
     /**
@@ -120,7 +122,8 @@
             DisplayController displayController,
             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
             SyncTransactionQueue syncQueue,
-            Transitions transitions) {
+            Transitions transitions,
+            WindowDecorViewHostSupplier windowDecorViewHostSupplier) {
         mContext = context;
         mMainExecutor = shellExecutor;
         mMainHandler = mainHandler;
@@ -132,6 +135,7 @@
         mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
         mSyncQueue = syncQueue;
         mTransitions = transitions;
+        mWindowDecorViewHostSupplier = windowDecorViewHostSupplier;
         if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
             mTaskOperations = new TaskOperations(null, mContext, mSyncQueue);
         }
@@ -295,7 +299,8 @@
                         mMainHandler,
                         mBgExecutor,
                         mMainChoreographer,
-                        mSyncQueue);
+                        mSyncQueue,
+                        mWindowDecorViewHostSupplier);
         mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
 
         final FluidResizeTaskPositioner taskPositioner =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 349ee0b..0caa8e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -55,7 +55,9 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
+import com.android.wm.shell.shared.desktopmode.DesktopModeFlags;
 import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
+import com.android.wm.shell.windowdecor.viewhost.WindowDecorViewHostSupplier;
 
 /**
  * Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with
@@ -88,8 +90,10 @@
             Handler handler,
             @ShellBackgroundThread ShellExecutor bgExecutor,
             Choreographer choreographer,
-            SyncTransactionQueue syncQueue) {
-        super(context, userContext, displayController, taskOrganizer, taskInfo, taskSurface);
+            SyncTransactionQueue syncQueue,
+            WindowDecorViewHostSupplier windowDecorViewHostSupplier) {
+        super(context, userContext, displayController, taskOrganizer, taskInfo, taskSurface,
+                windowDecorViewHostSupplier);
         mHandler = handler;
         mBgExecutor = bgExecutor;
         mChoreographer = choreographer;
@@ -237,7 +241,8 @@
             boolean applyStartTransactionOnDraw, boolean setTaskCropAndPosition) {
         final boolean isFreeform =
                 taskInfo.getWindowingMode() == WindowConfiguration.WINDOWING_MODE_FREEFORM;
-        final boolean isDragResizeable = isFreeform && taskInfo.isResizeable;
+        final boolean isDragResizeable = DesktopModeFlags.SCALED_RESIZING.isEnabled(mContext)
+                ? isFreeform : isFreeform && taskInfo.isResizeable;
 
         final WindowDecorLinearLayout oldRootView = mResult.mRootView;
         final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHandleManageWindowsMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHandleManageWindowsMenu.kt
new file mode 100644
index 0000000..13a805a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHandleManageWindowsMenu.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.content.Context
+import android.graphics.Point
+import android.graphics.Rect
+import android.view.WindowManager
+import android.window.TaskSnapshot
+import androidx.compose.ui.graphics.toArgb
+import com.android.wm.shell.shared.desktopmode.ManageWindowsViewContainer
+import com.android.wm.shell.shared.split.SplitScreenConstants
+import com.android.wm.shell.splitscreen.SplitScreenController
+import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
+import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewContainer
+import com.android.wm.shell.windowdecor.common.DecorThemeUtil
+import com.android.wm.shell.windowdecor.extension.isFullscreen
+import com.android.wm.shell.windowdecor.extension.isMultiWindow
+
+/**
+ * Implementation of [ManageWindowsViewContainer] meant to be used in desktop header and app
+ * handle.
+ */
+class DesktopHandleManageWindowsMenu(
+    private val callerTaskInfo: RunningTaskInfo,
+    private val splitScreenController: SplitScreenController,
+    private val captionX: Int,
+    private val captionWidth: Int,
+    private val windowManagerWrapper: WindowManagerWrapper,
+    context: Context,
+    snapshotList: List<Pair<Int, TaskSnapshot>>,
+    onIconClickListener: ((Int) -> Unit),
+    onOutsideClickListener: (() -> Unit)
+) : ManageWindowsViewContainer(
+    context,
+    DecorThemeUtil(context).getColorScheme(callerTaskInfo).background.toArgb()
+) {
+    private var menuViewContainer: AdditionalViewContainer? = null
+
+    init {
+        show(snapshotList, onIconClickListener, onOutsideClickListener)
+    }
+
+    override fun close() {
+        menuViewContainer?.releaseView()
+    }
+
+    private fun calculateMenuPosition(): Point {
+        val position = Point()
+        val nonFreeformX = (captionX + (captionWidth / 2) - (menuView.menuWidth / 2))
+        when {
+            callerTaskInfo.isFreeform -> {
+                val taskBounds = callerTaskInfo.getConfiguration().windowConfiguration.bounds
+                position.set(taskBounds.left, taskBounds.top)
+            }
+            callerTaskInfo.isFullscreen -> {
+                position.set(nonFreeformX, 0)
+            }
+            callerTaskInfo.isMultiWindow -> {
+                val splitPosition = splitScreenController.getSplitPosition(callerTaskInfo.taskId)
+                val leftOrTopStageBounds = Rect()
+                val rightOrBottomStageBounds = Rect()
+                splitScreenController.getStageBounds(leftOrTopStageBounds, rightOrBottomStageBounds)
+                // TODO(b/343561161): This needs to be calculated differently if the task is in
+                //  top/bottom split.
+                when (splitPosition) {
+                    SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT -> {
+                        position.set(leftOrTopStageBounds.width() + nonFreeformX, /* y= */ 0)
+                    }
+
+                    SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT -> {
+                        position.set(nonFreeformX, /* y= */ 0)
+                    }
+                }
+            }
+        }
+        return position
+    }
+
+    override fun addToContainer(menuView: ManageWindowsView) {
+        val menuPosition = calculateMenuPosition()
+        menuViewContainer = AdditionalSystemViewContainer(
+            windowManagerWrapper,
+            callerTaskInfo.taskId,
+            menuPosition.x,
+            menuPosition.y,
+            menuView.menuWidth,
+            menuView.menuHeight,
+            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
+                    WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
+            menuView.rootView
+        )
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt
new file mode 100644
index 0000000..05391a8
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.content.Context
+import android.graphics.PixelFormat
+import android.graphics.Point
+import android.view.SurfaceControl
+import android.view.SurfaceControlViewHost
+import android.view.WindowManager
+import android.view.WindowlessWindowManager
+import android.window.TaskConstants
+import android.window.TaskSnapshot
+import androidx.compose.ui.graphics.toArgb
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.shared.desktopmode.ManageWindowsViewContainer
+import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewContainer
+import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer
+import com.android.wm.shell.windowdecor.common.DecorThemeUtil
+import java.util.function.Supplier
+
+/**
+ * Implementation of [ManageWindowsViewContainer] meant to be used in desktop header and app
+ * handle.
+ */
+class DesktopHeaderManageWindowsMenu(
+    private val callerTaskInfo: RunningTaskInfo,
+    private val displayController: DisplayController,
+    private val rootTdaOrganizer: RootTaskDisplayAreaOrganizer,
+    context: Context,
+    private val surfaceControlBuilderSupplier: Supplier<SurfaceControl.Builder>,
+    private val surfaceControlTransactionSupplier: Supplier<SurfaceControl.Transaction>,
+    snapshotList: List<Pair<Int, TaskSnapshot>>,
+    onIconClickListener: ((Int) -> Unit),
+    onOutsideClickListener: (() -> Unit)
+) : ManageWindowsViewContainer(
+    context,
+    DecorThemeUtil(context).getColorScheme(callerTaskInfo).background.toArgb()
+) {
+    private var menuViewContainer: AdditionalViewContainer? = null
+
+    init {
+        show(snapshotList, onIconClickListener, onOutsideClickListener)
+    }
+
+    override fun close() {
+        menuViewContainer?.releaseView()
+    }
+
+    override fun addToContainer(menuView: ManageWindowsView) {
+        val taskBounds = callerTaskInfo.getConfiguration().windowConfiguration.bounds
+        val menuPosition = Point(taskBounds.left, taskBounds.top)
+        val builder = surfaceControlBuilderSupplier.get()
+        rootTdaOrganizer.attachToDisplayArea(callerTaskInfo.displayId, builder)
+        val leash = builder
+            .setName("Manage Windows Menu")
+            .setContainerLayer()
+            .build()
+        val lp = WindowManager.LayoutParams(
+            menuView.menuWidth, menuView.menuHeight,
+            WindowManager.LayoutParams.TYPE_APPLICATION,
+            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                    or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+                    or WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
+            PixelFormat.TRANSPARENT
+        )
+        val windowManager = WindowlessWindowManager(
+            callerTaskInfo.configuration,
+            leash,
+            null // HostInputToken
+        )
+        val viewHost = SurfaceControlViewHost(
+            context,
+            displayController.getDisplay(callerTaskInfo.displayId), windowManager,
+            "MaximizeMenu"
+        )
+        menuView.let { viewHost.setView(it.rootView, lp) }
+        val t = surfaceControlTransactionSupplier.get()
+        t.setLayer(leash, TaskConstants.TASK_CHILD_LAYER_FLOATING_MENU)
+            .setPosition(leash, menuPosition.x.toFloat(), menuPosition.y.toFloat())
+            .show(leash)
+        t.apply()
+        menuViewContainer = AdditionalViewHostViewContainer(
+            leash, viewHost,
+            surfaceControlTransactionSupplier
+        )
+    }
+}
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 0f8bd28..b311359 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
@@ -39,14 +39,18 @@
 import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR;
 import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
+import static com.android.wm.shell.shared.desktopmode.ManageWindowsViewContainer.MANAGE_WINDOWS_MINIMUM_INSTANCES;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
 
 import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityTaskManager;
+import android.app.IActivityManager;
+import android.app.IActivityTaskManager;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Point;
@@ -76,6 +80,7 @@
 import android.view.SurfaceControl.Transaction;
 import android.view.View;
 import android.widget.Toast;
+import android.window.TaskSnapshot;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
@@ -122,10 +127,14 @@
 import com.android.wm.shell.windowdecor.extension.InsetsStateKt;
 import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
 import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
+import com.android.wm.shell.windowdecor.viewhost.WindowDecorViewHostSupplier;
 
+import kotlin.Pair;
 import kotlin.Unit;
 
 import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Optional;
 import java.util.function.Supplier;
 
@@ -155,6 +164,7 @@
     private final InteractionJankMonitor mInteractionJankMonitor;
     private final MultiInstanceHelper mMultiInstanceHelper;
     private final Optional<DesktopTasksLimiter> mDesktopTasksLimiter;
+    private final WindowDecorViewHostSupplier mWindowDecorViewHostSupplier;
     private boolean mTransitionDragActive;
 
     private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>();
@@ -223,8 +233,8 @@
             AssistContentRequester assistContentRequester,
             MultiInstanceHelper multiInstanceHelper,
             Optional<DesktopTasksLimiter> desktopTasksLimiter,
-            Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler
-    ) {
+            Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler,
+            WindowDecorViewHostSupplier windowDecorViewHostSupplier) {
         this(
                 context,
                 shellExecutor,
@@ -244,6 +254,7 @@
                 genericLinksParser,
                 assistContentRequester,
                 multiInstanceHelper,
+                windowDecorViewHostSupplier,
                 new DesktopModeWindowDecoration.Factory(),
                 new InputMonitorFactory(),
                 SurfaceControl.Transaction::new,
@@ -275,6 +286,7 @@
             AppToWebGenericLinksParser genericLinksParser,
             AssistContentRequester assistContentRequester,
             MultiInstanceHelper multiInstanceHelper,
+            WindowDecorViewHostSupplier windowDecorViewHostSupplier,
             DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory,
             InputMonitorFactory inputMonitorFactory,
             Supplier<SurfaceControl.Transaction> transactionFactory,
@@ -300,6 +312,7 @@
         mMultiInstanceHelper = multiInstanceHelper;
         mShellCommandHandler = shellCommandHandler;
         mWindowManager = windowManager;
+        mWindowDecorViewHostSupplier = windowDecorViewHostSupplier;
         mDesktopModeWindowDecorFactory = desktopModeWindowDecorFactory;
         mInputMonitorFactory = inputMonitorFactory;
         mTransactionFactory = transactionFactory;
@@ -580,6 +593,61 @@
         mDesktopTasksController.openNewWindow(decoration.mTaskInfo);
     }
 
+    private void onManageWindows(DesktopModeWindowDecoration decoration) {
+        if (decoration == null) {
+            return;
+        }
+        decoration.closeHandleMenu();
+        decoration.createManageWindowsMenu(getTaskSnapshots(decoration.mTaskInfo),
+                /* onIconClickListener= */(Integer requestedTaskId) -> {
+                    decoration.closeManageWindowsMenu();
+                    mDesktopTasksController.openInstance(decoration.mTaskInfo, requestedTaskId);
+                    return Unit.INSTANCE;
+                });
+    }
+
+    private ArrayList<Pair<Integer, TaskSnapshot>> getTaskSnapshots(
+            @NonNull RunningTaskInfo callerTaskInfo
+    ) {
+        final ArrayList<Pair<Integer, TaskSnapshot>> snapshotList = new ArrayList<>();
+        final IActivityManager activityManager = ActivityManager.getService();
+        final IActivityTaskManager activityTaskManagerService = ActivityTaskManager.getService();
+        final List<ActivityManager.RecentTaskInfo> recentTasks;
+        try {
+            recentTasks = mActivityTaskManager.getRecentTasks(
+                    Integer.MAX_VALUE,
+                    ActivityManager.RECENT_WITH_EXCLUDED,
+                    activityManager.getCurrentUser().id);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+        final String callerPackageName = callerTaskInfo.baseActivity.getPackageName();
+        for (ActivityManager.RecentTaskInfo info : recentTasks) {
+            if (info.taskId == callerTaskInfo.taskId || info.baseActivity == null) continue;
+            final String infoPackageName = info.baseActivity.getPackageName();
+            if (!infoPackageName.equals(callerPackageName)) {
+                continue;
+            }
+            if (info.baseActivity != null) {
+                if (callerPackageName.equals(infoPackageName)) {
+                    // TODO(b/337903443): Fix this returning null for freeform tasks.
+                    try {
+                        TaskSnapshot screenshot = activityTaskManagerService
+                                .getTaskSnapshot(info.taskId, false);
+                        if (screenshot == null) {
+                            screenshot = activityTaskManagerService
+                                    .takeTaskSnapshot(info.taskId, false);
+                        }
+                        snapshotList.add(new Pair(info.taskId, screenshot));
+                    } catch (RemoteException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+            }
+        }
+        return snapshotList;
+    }
+
     private class DesktopModeTouchEventListener extends GestureDetector.SimpleOnGestureListener
             implements View.OnClickListener, View.OnTouchListener, View.OnLongClickListener,
             View.OnGenericMotionListener, DragDetector.MotionEventHandler {
@@ -636,7 +704,8 @@
             } else if (id == R.id.caption_handle || id == R.id.open_menu_button) {
                 if (!decoration.isHandleMenuActive()) {
                     moveTaskToFront(decoration.mTaskInfo);
-                    decoration.createHandleMenu();
+                    decoration.createHandleMenu(checkNumberOfOtherInstances(decoration.mTaskInfo)
+                                    >= MANAGE_WINDOWS_MINIMUM_INSTANCES);
                 }
             } else if (id == R.id.maximize_window) {
                 // TODO(b/346441962): move click detection logic into the decor's
@@ -1098,8 +1167,22 @@
                         // If we are entering split select, handle will no longer be visible and
                         // should not be receiving any input.
                         if (resultType == TO_SPLIT_LEFT_INDICATOR
-                                || resultType != TO_SPLIT_RIGHT_INDICATOR) {
+                                || resultType == TO_SPLIT_RIGHT_INDICATOR) {
                             relevantDecor.disposeStatusBarInputLayer();
+                            // We should also dispose the other split task's input layer if
+                            // applicable.
+                            final int splitPosition = mSplitScreenController
+                                    .getSplitPosition(relevantDecor.mTaskInfo.taskId);
+                            if (splitPosition != SPLIT_POSITION_UNDEFINED) {
+                                final int oppositePosition =
+                                        splitPosition == SPLIT_POSITION_TOP_OR_LEFT
+                                                ? SPLIT_POSITION_BOTTOM_OR_RIGHT
+                                                : SPLIT_POSITION_TOP_OR_LEFT;
+                                final RunningTaskInfo oppositeTaskInfo =
+                                        mSplitScreenController.getTaskInfo(oppositePosition);
+                                mWindowDecorByTaskId.get(oppositeTaskInfo.taskId)
+                                        .disposeStatusBarInputLayer();
+                            }
                         }
                         mMoveToDesktopAnimator = null;
                         return;
@@ -1284,7 +1367,8 @@
                         mRootTaskDisplayAreaOrganizer,
                         mGenericLinksParser,
                         mAssistContentRequester,
-                        mMultiInstanceHelper);
+                        mMultiInstanceHelper,
+                        mWindowDecorViewHostSupplier);
         mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
 
         final TaskPositioner taskPositioner = mTaskPositionerFactory.create(
@@ -1329,6 +1413,10 @@
             onNewWindow(taskInfo.taskId);
             return Unit.INSTANCE;
         });
+        windowDecoration.setManageWindowsClickListener(() -> {
+            onManageWindows(windowDecoration);
+            return Unit.INSTANCE;
+        });
         windowDecoration.setCaptionListeners(
                 touchEventListener, touchEventListener, touchEventListener, touchEventListener);
         windowDecoration.setExclusionRegionListener(mExclusionRegionListener);
@@ -1420,6 +1508,29 @@
         }
     }
 
+    /**
+     * Gets the number of instances of a task running, not including the specified task itself.
+     */
+    private int checkNumberOfOtherInstances(@NonNull RunningTaskInfo info) {
+        // TODO(b/336289597): Rather than returning number of instances, return a list of valid
+        //  instances, then refer to the list's size and reuse the list for Manage Windows menu.
+        final IActivityTaskManager activityTaskManager = ActivityTaskManager.getService();
+        final IActivityManager activityManager = ActivityManager.getService();
+        try {
+            return activityTaskManager.getRecentTasks(Integer.MAX_VALUE,
+                    ActivityManager.RECENT_WITH_EXCLUDED,
+                    activityManager.getCurrentUserId()).getList().stream().filter(
+                            recentTaskInfo -> (recentTaskInfo.taskId != info.taskId
+                                    && recentTaskInfo.baseActivity != null
+                                    && recentTaskInfo.baseActivity.getPackageName()
+                                    .equals(info.baseActivity.getPackageName())
+                            )
+            ).toList().size();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
     static class InputMonitorFactory {
         InputMonitor create(InputManager inputManager, int displayId) {
             return inputManager.monitorGestureInput("caption-touch", displayId);
@@ -1492,14 +1603,27 @@
                 Transitions transitions,
                 InteractionJankMonitor interactionJankMonitor,
                 Supplier<SurfaceControl.Transaction> transactionFactory) {
-            if (!DesktopModeStatus.isVeiledResizeEnabled()) {
-                return new FluidResizeTaskPositioner(
-                        taskOrganizer, transitions, windowDecoration, displayController,
-                        dragStartListener, transactionFactory);
+            final TaskPositioner taskPositioner = DesktopModeStatus.isVeiledResizeEnabled()
+                    ? new VeiledResizeTaskPositioner(
+                            taskOrganizer,
+                            windowDecoration,
+                            displayController,
+                            dragStartListener,
+                            transitions,
+                            interactionJankMonitor)
+                    : new FluidResizeTaskPositioner(
+                            taskOrganizer,
+                            transitions,
+                            windowDecoration,
+                            displayController,
+                            dragStartListener,
+                            transactionFactory);
+
+            if (DesktopModeFlags.SCALED_RESIZING.isEnabled(windowDecoration.mContext)) {
+                return new FixedAspectRatioTaskPositionerDecorator(windowDecoration,
+                        taskPositioner);
             }
-            return new VeiledResizeTaskPositioner(
-                    taskOrganizer, windowDecoration, displayController,
-                    dragStartListener, transitions, interactionJankMonitor);
+            return taskPositioner;
         }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 142be91..5521c2e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -35,7 +35,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.SuppressLint;
 import android.app.ActivityManager;
 import android.app.WindowConfiguration.WindowingMode;
 import android.app.assist.AssistContent;
@@ -65,6 +64,7 @@
 import android.view.ViewConfiguration;
 import android.view.WindowManager;
 import android.widget.ImageButton;
+import android.window.TaskSnapshot;
 import android.window.WindowContainerTransaction;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -87,15 +87,20 @@
 import com.android.wm.shell.shared.desktopmode.DesktopModeFlags;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
+import com.android.wm.shell.shared.desktopmode.ManageWindowsViewContainer;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
 import com.android.wm.shell.windowdecor.viewholder.AppHandleViewHolder;
 import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
 import com.android.wm.shell.windowdecor.viewholder.WindowDecorationViewHolder;
+import com.android.wm.shell.windowdecor.viewhost.WindowDecorViewHostSupplier;
 
+import kotlin.Pair;
 import kotlin.Unit;
 import kotlin.jvm.functions.Function0;
+import kotlin.jvm.functions.Function1;
 
+import java.util.List;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
 
@@ -131,18 +136,18 @@
     private Function0<Unit> mOnToFullscreenClickListener;
     private Function0<Unit> mOnToSplitscreenClickListener;
     private Function0<Unit> mOnNewWindowClickListener;
+    private Function0<Unit> mOnManageWindowsClickListener;
     private DragPositioningCallback mDragPositioningCallback;
     private DragResizeInputListener mDragResizeListener;
     private DragDetector mDragDetector;
-    private Runnable mCurrentViewHostRunnable = null;
     private RelayoutParams mRelayoutParams = new RelayoutParams();
     private final WindowDecoration.RelayoutResult<WindowDecorLinearLayout> mResult =
             new WindowDecoration.RelayoutResult<>();
-    private final Runnable mViewHostRunnable =
-            () -> updateViewHost(mRelayoutParams, null /* onDrawTransaction */, mResult);
 
     private final Point mPositionInParent = new Point();
     private HandleMenu mHandleMenu;
+    private boolean mMinimumInstancesFound;
+    private ManageWindowsViewContainer mManageWindowsMenu;
 
     private MaximizeMenu mMaximizeMenu;
 
@@ -190,14 +195,16 @@
             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
             AppToWebGenericLinksParser genericLinksParser,
             AssistContentRequester assistContentRequester,
-            MultiInstanceHelper multiInstanceHelper) {
+            MultiInstanceHelper multiInstanceHelper,
+            WindowDecorViewHostSupplier windowDecorViewHostSupplier) {
         this (context, userContext, displayController, splitScreenController, taskOrganizer,
                 taskInfo, taskSurface, handler, bgExecutor, choreographer, syncQueue,
                 rootTaskDisplayAreaOrganizer, genericLinksParser, assistContentRequester,
                 SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
                 WindowContainerTransaction::new, SurfaceControl::new, new WindowManagerWrapper(
                         context.getSystemService(WindowManager.class)),
-                new SurfaceControlViewHostFactory() {}, DefaultMaximizeMenuFactory.INSTANCE,
+                new SurfaceControlViewHostFactory() {}, windowDecorViewHostSupplier,
+                DefaultMaximizeMenuFactory.INSTANCE,
                 DefaultHandleMenuFactory.INSTANCE, multiInstanceHelper);
     }
 
@@ -222,13 +229,14 @@
             Supplier<SurfaceControl> surfaceControlSupplier,
             WindowManagerWrapper windowManagerWrapper,
             SurfaceControlViewHostFactory surfaceControlViewHostFactory,
+            WindowDecorViewHostSupplier windowDecorViewHostSupplier,
             MaximizeMenuFactory maximizeMenuFactory,
             HandleMenuFactory handleMenuFactory,
             MultiInstanceHelper multiInstanceHelper) {
         super(context, userContext, displayController, taskOrganizer, taskInfo, taskSurface,
                 surfaceControlBuilderSupplier, surfaceControlTransactionSupplier,
                 windowContainerTransactionSupplier, surfaceControlSupplier,
-                surfaceControlViewHostFactory);
+                surfaceControlViewHostFactory, windowDecorViewHostSupplier);
         mSplitScreenController = splitScreenController;
         mHandler = handler;
         mBgExecutor = bgExecutor;
@@ -285,6 +293,14 @@
         mOnNewWindowClickListener = listener;
     }
 
+    /**
+     * Registers a listener to be called when the decoration's manage windows action is
+     * triggered.
+     */
+    void setManageWindowsClickListener(Function0<Unit> listener) {
+        mOnManageWindowsClickListener = listener;
+    }
+
     void setCaptionListeners(
             View.OnClickListener onCaptionButtonClickListener,
             View.OnTouchListener onCaptionTouchListener,
@@ -337,73 +353,6 @@
             SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
             boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop) {
         Trace.beginSection("DesktopModeWindowDecoration#relayout");
-        if (taskInfo.isFreeform()) {
-            // The Task is in Freeform mode -> show its header in sync since it's an integral part
-            // of the window itself - a delayed header might cause bad UX.
-            relayoutInSync(taskInfo, startT, finishT, applyStartTransactionOnDraw,
-                    shouldSetTaskPositionAndCrop);
-        } else {
-            // The Task is outside Freeform mode -> allow the handle view to be delayed since the
-            // handle is just a small addition to the window.
-            relayoutWithDelayedViewHost(taskInfo, startT, finishT, applyStartTransactionOnDraw,
-                    shouldSetTaskPositionAndCrop);
-        }
-        Trace.endSection();
-    }
-
-    /** Run the whole relayout phase immediately without delay. */
-    private void relayoutInSync(ActivityManager.RunningTaskInfo taskInfo,
-            SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
-            boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop) {
-        // Clear the current ViewHost runnable as we will update the ViewHost here
-        clearCurrentViewHostRunnable();
-        updateRelayoutParamsAndSurfaces(taskInfo, startT, finishT, applyStartTransactionOnDraw,
-                shouldSetTaskPositionAndCrop);
-        if (mResult.mRootView != null) {
-            updateViewHost(mRelayoutParams, startT, mResult);
-        }
-    }
-
-    /**
-     * Clear the current ViewHost runnable - to ensure it doesn't run once relayout params have been
-     * updated.
-     */
-    private void clearCurrentViewHostRunnable() {
-        if (mCurrentViewHostRunnable != null) {
-            mHandler.removeCallbacks(mCurrentViewHostRunnable);
-            mCurrentViewHostRunnable = null;
-        }
-    }
-
-    /**
-     * Relayout the window decoration but repost some of the work, to unblock the current callstack.
-     */
-    private void relayoutWithDelayedViewHost(ActivityManager.RunningTaskInfo taskInfo,
-            SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
-            boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop) {
-        if (applyStartTransactionOnDraw) {
-            throw new IllegalArgumentException(
-                    "We cannot both sync viewhost ondraw and delay viewhost creation.");
-        }
-        // Clear the current ViewHost runnable as we will update the ViewHost here
-        clearCurrentViewHostRunnable();
-        updateRelayoutParamsAndSurfaces(taskInfo, startT, finishT,
-                false /* applyStartTransactionOnDraw */, shouldSetTaskPositionAndCrop);
-        if (mResult.mRootView == null) {
-            // This means something blocks the window decor from showing, e.g. the task is hidden.
-            // Nothing is set up in this case including the decoration surface.
-            return;
-        }
-        // Store the current runnable so it can be removed if we start a new relayout.
-        mCurrentViewHostRunnable = mViewHostRunnable;
-        mHandler.post(mCurrentViewHostRunnable);
-    }
-
-    @SuppressLint("MissingPermission")
-    private void updateRelayoutParamsAndSurfaces(ActivityManager.RunningTaskInfo taskInfo,
-            SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
-            boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop) {
-        Trace.beginSection("DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces");
 
         if (Flags.enableDesktopWindowingAppToWeb()) {
             setCapturedLink(taskInfo.capturedLink, taskInfo.capturedLinkTimestamp);
@@ -420,8 +369,8 @@
         final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
         final WindowContainerTransaction wct = new WindowContainerTransaction();
 
-        Trace.beginSection("DesktopModeWindowDecoration#relayout-updateViewsAndSurfaces");
-        updateViewsAndSurfaces(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
+        Trace.beginSection("DesktopModeWindowDecoration#relayout-inner");
+        relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
         Trace.endSection();
         // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
 
@@ -433,7 +382,7 @@
             // This means something blocks the window decor from showing, e.g. the task is hidden.
             // Nothing is set up in this case including the decoration surface.
             disposeStatusBarInputLayer();
-            Trace.endSection(); // DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces
+            Trace.endSection(); // DesktopModeWindowDecoration#relayout
             return;
         }
 
@@ -441,12 +390,12 @@
             disposeStatusBarInputLayer();
             mWindowDecorViewHolder = createViewHolder();
         }
-        Trace.beginSection("DesktopModeWindowDecoration#relayout-binding");
 
         final Point position = new Point();
         if (isAppHandle(mWindowDecorViewHolder)) {
             position.set(determineHandlePosition());
         }
+        Trace.beginSection("DesktopModeWindowDecoration#relayout-bindData");
         mWindowDecorViewHolder.bindData(mTaskInfo,
                 position,
                 mResult.mCaptionWidth,
@@ -460,7 +409,7 @@
         }
         updateDragResizeListener(oldDecorationSurface);
         updateMaximizeMenu(startT);
-        Trace.endSection(); // DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces
+        Trace.endSection(); // DesktopModeWindowDecoration#relayout
     }
 
     private boolean isCaptionVisible() {
@@ -506,7 +455,7 @@
     }
 
     private void updateDragResizeListener(SurfaceControl oldDecorationSurface) {
-        if (!isDragResizable(mTaskInfo)) {
+        if (!isDragResizable(mTaskInfo, mContext)) {
             if (!mTaskInfo.positionInParent.equals(mPositionInParent)) {
                 // We still want to track caption bar's exclusion region on a non-resizeable task.
                 updateExclusionRegion();
@@ -548,12 +497,16 @@
         }
     }
 
-    private static boolean isDragResizable(ActivityManager.RunningTaskInfo taskInfo) {
+    private static boolean isDragResizable(ActivityManager.RunningTaskInfo taskInfo,
+            Context context) {
+        if (DesktopModeFlags.SCALED_RESIZING.isEnabled(context)) {
+            return taskInfo.isFreeform();
+        }
         return taskInfo.isFreeform() && taskInfo.isResizeable;
     }
 
     private void updateMaximizeMenu(SurfaceControl.Transaction startT) {
-        if (!isDragResizable(mTaskInfo) || !isMaximizeMenuActive()) {
+        if (!isDragResizable(mTaskInfo, mContext) || !isMaximizeMenuActive()) {
             return;
         }
         if (!mTaskInfo.isVisible()) {
@@ -638,6 +591,10 @@
         relayoutParams.mLayoutResId = captionLayoutId;
         relayoutParams.mCaptionHeightId = getCaptionHeightIdStatic(taskInfo.getWindowingMode());
         relayoutParams.mCaptionWidthId = getCaptionWidthId(relayoutParams.mLayoutResId);
+        // Allow the handle view to be delayed since the handle is just a small addition to the
+        // window, whereas the header cannot be delayed because it is expected to be visible from
+        // the first frame.
+        relayoutParams.mAsyncViewHost = isAppHandle;
 
         if (isAppHeader) {
             if (TaskInfoKt.isTransparentCaptionBarAppearance(taskInfo)) {
@@ -1004,9 +961,10 @@
     /**
      * Updates app info and creates and displays handle menu window.
      */
-    void createHandleMenu() {
+    void createHandleMenu(boolean minimumInstancesFound) {
         // Requests assist content. When content is received, calls {@link #onAssistContentReceived}
         // which sets app info and creates the handle menu.
+        mMinimumInstancesFound = minimumInstancesFound;
         mAssistContentRequester.requestAssistContent(
                 mTaskInfo.taskId, this::onAssistContentReceived);
     }
@@ -1019,8 +977,10 @@
         mWebUri = assistContent == null ? null : assistContent.getWebUri();
         loadAppInfoIfNeeded();
         updateGenericLink();
-
-        // Create and display handle menu
+        final boolean supportsMultiInstance = mMultiInstanceHelper
+                .supportsMultiInstanceSplit(mTaskInfo.baseActivity);
+        final boolean shouldShowManageWindowsButton = supportsMultiInstance
+                && mMinimumInstancesFound;
         mHandleMenu = mHandleMenuFactory.create(
                 this,
                 mWindowManagerWrapper,
@@ -1029,9 +989,8 @@
                 mAppName,
                 mSplitScreenController,
                 DesktopModeStatus.canEnterDesktopMode(mContext),
-                Flags.enableDesktopWindowingMultiInstanceFeatures()
-                        && mMultiInstanceHelper
-                        .supportsMultiInstanceSplit(mTaskInfo.baseActivity),
+                supportsMultiInstance,
+                shouldShowManageWindowsButton,
                 getBrowserLink(),
                 mResult.mCaptionWidth,
                 mResult.mCaptionHeight,
@@ -1047,6 +1006,7 @@
                 /* onToFullscreenClickListener= */ mOnToFullscreenClickListener,
                 /* onToSplitScreenClickListener= */ mOnToSplitscreenClickListener,
                 /* onNewWindowClickListener= */ mOnNewWindowClickListener,
+                /* onManageWindowsClickListener= */ mOnManageWindowsClickListener,
                 /* openInBrowserClickListener= */ (uri) -> {
                     mOpenInBrowserClickListener.accept(uri);
                     onCapturedLinkExpired();
@@ -1061,6 +1021,47 @@
                     return Unit.INSTANCE;
                 }
         );
+        mMinimumInstancesFound = false;
+    }
+
+    void createManageWindowsMenu(@NonNull List<Pair<Integer, TaskSnapshot>> snapshotList,
+            @NonNull Function1<Integer, Unit> onIconClickListener
+    ) {
+        if (mTaskInfo.isFreeform()) {
+            mManageWindowsMenu = new DesktopHeaderManageWindowsMenu(
+                    mTaskInfo,
+                    mDisplayController,
+                    mRootTaskDisplayAreaOrganizer,
+                    mContext,
+                    mSurfaceControlBuilderSupplier,
+                    mSurfaceControlTransactionSupplier,
+                    snapshotList,
+                    onIconClickListener,
+                    /* onOutsideClickListener= */ () -> {
+                        closeManageWindowsMenu();
+                        return Unit.INSTANCE;
+                    }
+                    );
+        } else {
+            mManageWindowsMenu = new DesktopHandleManageWindowsMenu(
+                    mTaskInfo,
+                    mSplitScreenController,
+                    getCaptionX(),
+                    mResult.mCaptionWidth,
+                    mWindowManagerWrapper,
+                    mContext,
+                    snapshotList,
+                    onIconClickListener,
+                    /* onOutsideClickListener= */ () -> {
+                        closeManageWindowsMenu();
+                        return Unit.INSTANCE;
+                    }
+                    );
+        }
+    }
+
+    void closeManageWindowsMenu() {
+        mManageWindowsMenu.close();
     }
 
     private void updateGenericLink() {
@@ -1253,7 +1254,6 @@
         mExclusionRegionListener.onExclusionRegionDismissed(mTaskInfo.taskId);
         disposeResizeVeil();
         disposeStatusBarInputLayer();
-        clearCurrentViewHostRunnable();
         super.close();
     }
 
@@ -1364,7 +1364,8 @@
                 RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
                 AppToWebGenericLinksParser genericLinksParser,
                 AssistContentRequester assistContentRequester,
-                MultiInstanceHelper multiInstanceHelper) {
+                MultiInstanceHelper multiInstanceHelper,
+                WindowDecorViewHostSupplier windowDecorViewHostSupplier) {
             return new DesktopModeWindowDecoration(
                     context,
                     userContext,
@@ -1380,7 +1381,8 @@
                     rootTaskDisplayAreaOrganizer,
                     genericLinksParser,
                     assistContentRequester,
-                    multiInstanceHelper);
+                    multiInstanceHelper,
+                    windowDecorViewHostSupplier);
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
index cb9781e..cad3462 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
@@ -84,22 +84,47 @@
 
         repositionTaskBounds.set(taskBoundsAtDragStart);
 
+        boolean isAspectRatioMaintained = true;
         // Make sure the new resizing destination in any direction falls within the stable bounds.
         if ((ctrlType & CTRL_TYPE_LEFT) != 0) {
             repositionTaskBounds.left = Math.max(repositionTaskBounds.left + (int) delta.x,
                     stableBounds.left);
+            if (repositionTaskBounds.left == stableBounds.left
+                    && repositionTaskBounds.left + (int) delta.x != stableBounds.left) {
+                // If the task edge have been set to the stable bounds and not due to the users
+                // drag, the aspect ratio of the task will not be maintained.
+                isAspectRatioMaintained = false;
+            }
         }
         if ((ctrlType & CTRL_TYPE_RIGHT) != 0) {
             repositionTaskBounds.right = Math.min(repositionTaskBounds.right + (int) delta.x,
                     stableBounds.right);
+            if (repositionTaskBounds.right == stableBounds.right
+                    && repositionTaskBounds.right + (int) delta.x != stableBounds.right) {
+                // If the task edge have been set to the stable bounds and not due to the users
+                // drag, the aspect ratio of the task will not be maintained.
+                isAspectRatioMaintained = false;
+            }
         }
         if ((ctrlType & CTRL_TYPE_TOP) != 0) {
             repositionTaskBounds.top = Math.max(repositionTaskBounds.top + (int) delta.y,
                     stableBounds.top);
+            if (repositionTaskBounds.top == stableBounds.top
+                    && repositionTaskBounds.top + (int) delta.y != stableBounds.top) {
+                // If the task edge have been set to the stable bounds and not due to the users
+                // drag, the aspect ratio of the task will not be maintained.
+                isAspectRatioMaintained = false;
+            }
         }
         if ((ctrlType & CTRL_TYPE_BOTTOM) != 0) {
             repositionTaskBounds.bottom = Math.min(repositionTaskBounds.bottom + (int) delta.y,
                     stableBounds.bottom);
+            if (repositionTaskBounds.bottom == stableBounds.bottom
+                    && repositionTaskBounds.bottom + (int) delta.y != stableBounds.bottom) {
+                // If the task edge have been set to the stable bounds and not due to the users
+                // drag, the aspect ratio of the task will not be maintained.
+                isAspectRatioMaintained = false;
+            }
         }
 
         // If width or height are negative or exceeding the width or height constraints, revert the
@@ -108,11 +133,24 @@
                 windowDecoration)) {
             repositionTaskBounds.right = oldRight;
             repositionTaskBounds.left = oldLeft;
+            isAspectRatioMaintained = false;
         }
         if (isExceedingHeightConstraint(repositionTaskBounds, stableBounds, displayController,
                 windowDecoration)) {
             repositionTaskBounds.top = oldTop;
             repositionTaskBounds.bottom = oldBottom;
+            isAspectRatioMaintained = false;
+        }
+
+        // If the application is unresizeable and any bounds have been set back to their old
+        // location or to a stable bound edge, reset all the bounds to maintain the applications
+        // aspect ratio.
+        if (DesktopModeFlags.SCALED_RESIZING.isEnabled(windowDecoration.mDecorWindowContext)
+                && !isAspectRatioMaintained && !windowDecoration.mTaskInfo.isResizeable) {
+            repositionTaskBounds.top = oldTop;
+            repositionTaskBounds.bottom = oldBottom;
+            repositionTaskBounds.right = oldRight;
+            repositionTaskBounds.left = oldLeft;
         }
 
         // If there are no changes to the bounds after checking new bounds against minimum and
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FixedAspectRatioTaskPositionerDecorator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FixedAspectRatioTaskPositionerDecorator.kt
index e8131a0..3885761 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FixedAspectRatioTaskPositionerDecorator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FixedAspectRatioTaskPositionerDecorator.kt
@@ -16,8 +16,10 @@
 
 package com.android.wm.shell.windowdecor
 
+import android.app.ActivityManager.RunningTaskInfo
 import android.graphics.PointF
 import android.graphics.Rect
+import com.android.internal.annotations.VisibleForTesting
 import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM
 import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_LEFT
 import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT
@@ -51,8 +53,7 @@
             return super.onDragPositioningStart(originalCtrlType, x, y)
         }
 
-        lastRepositionedBounds.set(
-            windowDecoration.mTaskInfo.configuration.windowConfiguration.bounds)
+        lastRepositionedBounds.set(getBounds(windowDecoration.mTaskInfo))
         startingPoint.set(x, y)
         lastValidPoint.set(x, y)
         val startingBoundWidth = lastRepositionedBounds.width()
@@ -255,4 +256,9 @@
     private fun requiresFixedAspectRatio(): Boolean {
         return originalCtrlType.isResizing() && !windowDecoration.mTaskInfo.isResizeable
     }
+
+    @VisibleForTesting
+    fun getBounds(taskInfo: RunningTaskInfo): Rect {
+        return taskInfo.configuration.windowConfiguration.bounds
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
index 748046e..3d00a44 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
@@ -71,6 +71,7 @@
     private val splitScreenController: SplitScreenController,
     private val shouldShowWindowingPill: Boolean,
     private val shouldShowNewWindowButton: Boolean,
+    private val shouldShowManageWindowsButton: Boolean,
     private val openInBrowserLink: Uri?,
     private val captionWidth: Int,
     private val captionHeight: Int,
@@ -119,6 +120,7 @@
         onToFullscreenClickListener: () -> Unit,
         onToSplitScreenClickListener: () -> Unit,
         onNewWindowClickListener: () -> Unit,
+        onManageWindowsClickListener: () -> Unit,
         openInBrowserClickListener: (Uri) -> Unit,
         onCloseMenuClickListener: () -> Unit,
         onOutsideTouchListener: () -> Unit,
@@ -133,6 +135,7 @@
             onToFullscreenClickListener = onToFullscreenClickListener,
             onToSplitScreenClickListener = onToSplitScreenClickListener,
             onNewWindowClickListener = onNewWindowClickListener,
+            onManageWindowsClickListener = onManageWindowsClickListener,
             openInBrowserClickListener = openInBrowserClickListener,
             onCloseMenuClickListener = onCloseMenuClickListener,
             onOutsideTouchListener = onOutsideTouchListener,
@@ -150,6 +153,7 @@
         onToFullscreenClickListener: () -> Unit,
         onToSplitScreenClickListener: () -> Unit,
         onNewWindowClickListener: () -> Unit,
+        onManageWindowsClickListener: () -> Unit,
         openInBrowserClickListener: (Uri) -> Unit,
         onCloseMenuClickListener: () -> Unit,
         onOutsideTouchListener: () -> Unit
@@ -160,13 +164,15 @@
             captionHeight = captionHeight,
             shouldShowWindowingPill = shouldShowWindowingPill,
             shouldShowBrowserPill = shouldShowBrowserPill,
-            shouldShowNewWindowButton = shouldShowNewWindowButton
+            shouldShowNewWindowButton = shouldShowNewWindowButton,
+            shouldShowManageWindowsButton = shouldShowManageWindowsButton
         ).apply {
             bind(taskInfo, appIconBitmap, appName)
             this.onToDesktopClickListener = onToDesktopClickListener
             this.onToFullscreenClickListener = onToFullscreenClickListener
             this.onToSplitScreenClickListener = onToSplitScreenClickListener
             this.onNewWindowClickListener = onNewWindowClickListener
+            this.onManageWindowsClickListener = onManageWindowsClickListener
             this.onOpenInBrowserClickListener = {
                 openInBrowserClickListener.invoke(openInBrowserLink!!)
             }
@@ -372,7 +378,13 @@
                 R.dimen.desktop_mode_handle_menu_new_window_height
             )
         }
-        if (!SHOULD_SHOW_SCREENSHOT_BUTTON && !shouldShowNewWindowButton) {
+        if (!shouldShowManageWindowsButton) {
+            menuHeight -= loadDimensionPixelSize(
+                R.dimen.desktop_mode_handle_menu_manage_windows_height
+            )
+        }
+        if (!SHOULD_SHOW_SCREENSHOT_BUTTON && !shouldShowNewWindowButton
+            && !shouldShowManageWindowsButton) {
             menuHeight -= pillTopMargin
         }
         if (!shouldShowBrowserPill) {
@@ -405,7 +417,8 @@
         captionHeight: Int,
         private val shouldShowWindowingPill: Boolean,
         private val shouldShowBrowserPill: Boolean,
-        private val shouldShowNewWindowButton: Boolean
+        private val shouldShowNewWindowButton: Boolean,
+        private val shouldShowManageWindowsButton: Boolean
     ) {
         val rootView = LayoutInflater.from(context)
             .inflate(R.layout.desktop_mode_window_decor_handle_menu, null /* root */) as View
@@ -430,6 +443,8 @@
         private val moreActionsPill = rootView.requireViewById<View>(R.id.more_actions_pill)
         private val screenshotBtn = moreActionsPill.requireViewById<Button>(R.id.screenshot_button)
         private val newWindowBtn = moreActionsPill.requireViewById<Button>(R.id.new_window_button)
+        private val manageWindowBtn = moreActionsPill
+            .requireViewById<Button>(R.id.manage_windows_button)
 
         // Open in Browser Pill.
         private val openInBrowserPill = rootView.requireViewById<View>(R.id.open_in_browser_pill)
@@ -446,6 +461,7 @@
         var onToFullscreenClickListener: (() -> Unit)? = null
         var onToSplitScreenClickListener: (() -> Unit)? = null
         var onNewWindowClickListener: (() -> Unit)? = null
+        var onManageWindowsClickListener: (() -> Unit)? = null
         var onOpenInBrowserClickListener: (() -> Unit)? = null
         var onCloseMenuClickListener: (() -> Unit)? = null
         var onOutsideTouchListener: (() -> Unit)? = null
@@ -457,6 +473,7 @@
             browserBtn.setOnClickListener { onOpenInBrowserClickListener?.invoke() }
             collapseMenuButton.setOnClickListener { onCloseMenuClickListener?.invoke() }
             newWindowBtn.setOnClickListener { onNewWindowClickListener?.invoke() }
+            manageWindowBtn.setOnClickListener { onManageWindowsClickListener?.invoke() }
 
             rootView.setOnTouchListener { _, event ->
                 if (event.actionMasked == ACTION_OUTSIDE) {
@@ -587,6 +604,7 @@
         private fun bindMoreActionsPill(style: MenuStyle) {
             moreActionsPill.apply {
                 isGone = !shouldShowNewWindowButton && !SHOULD_SHOW_SCREENSHOT_BUTTON
+                        && !shouldShowManageWindowsButton
             }
             screenshotBtn.apply {
                 isGone = !SHOULD_SHOW_SCREENSHOT_BUTTON
@@ -603,6 +621,13 @@
                 setTextColor(style.textColor)
                 compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
             }
+            manageWindowBtn.apply {
+                isGone = !shouldShowManageWindowsButton
+                background.colorFilter =
+                    BlendModeColorFilter(style.backgroundColor, BlendMode.MULTIPLY)
+                setTextColor(style.textColor)
+                compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
+            }
         }
 
         private fun bindOpenInBrowserPill(style: MenuStyle) {
@@ -643,6 +668,7 @@
         splitScreenController: SplitScreenController,
         shouldShowWindowingPill: Boolean,
         shouldShowNewWindowButton: Boolean,
+        shouldShowManageWindowsButton: Boolean,
         openInBrowserLink: Uri?,
         captionWidth: Int,
         captionHeight: Int,
@@ -661,6 +687,7 @@
         splitScreenController: SplitScreenController,
         shouldShowWindowingPill: Boolean,
         shouldShowNewWindowButton: Boolean,
+        shouldShowManageWindowsButton: Boolean,
         openInBrowserLink: Uri?,
         captionWidth: Int,
         captionHeight: Int,
@@ -675,6 +702,7 @@
             splitScreenController,
             shouldShowWindowingPill,
             shouldShowNewWindowButton,
+            shouldShowManageWindowsButton,
             openInBrowserLink,
             captionWidth,
             captionHeight,
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 c1a55b4..3694845 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
@@ -62,6 +62,8 @@
 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 com.android.wm.shell.windowdecor.viewhost.WindowDecorViewHost;
+import com.android.wm.shell.windowdecor.viewhost.WindowDecorViewHostSupplier;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -116,6 +118,7 @@
     final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier;
     final Supplier<WindowContainerTransaction> mWindowContainerTransactionSupplier;
     final SurfaceControlViewHostFactory mSurfaceControlViewHostFactory;
+    @NonNull private final WindowDecorViewHostSupplier mWindowDecorViewHostSupplier;
     private final DisplayController.OnDisplaysChangedListener mOnDisplaysChangedListener =
             new DisplayController.OnDisplaysChangedListener() {
                 @Override
@@ -137,9 +140,7 @@
     Context mDecorWindowContext;
     SurfaceControl mDecorationContainerSurface;
 
-    SurfaceControl mCaptionContainerSurface;
-    private WindowlessWindowManager mCaptionWindowManager;
-    private SurfaceControlViewHost mViewHost;
+    private WindowDecorViewHost mDecorViewHost;
     private Configuration mWindowDecorConfig;
     TaskDragResizer mTaskDragResizer;
     boolean mIsCaptionVisible;
@@ -158,11 +159,13 @@
             DisplayController displayController,
             ShellTaskOrganizer taskOrganizer,
             RunningTaskInfo taskInfo,
-            SurfaceControl taskSurface) {
+            SurfaceControl taskSurface,
+            @NonNull WindowDecorViewHostSupplier windowDecorViewHostSupplier) {
         this(context, userContext, displayController, taskOrganizer, taskInfo, taskSurface,
                 SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
                 WindowContainerTransaction::new, SurfaceControl::new,
-                new SurfaceControlViewHostFactory() {});
+                new SurfaceControlViewHostFactory() {},
+                windowDecorViewHostSupplier);
     }
 
     WindowDecoration(
@@ -176,7 +179,8 @@
             Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
             Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
             Supplier<SurfaceControl> surfaceControlSupplier,
-            SurfaceControlViewHostFactory surfaceControlViewHostFactory) {
+            SurfaceControlViewHostFactory surfaceControlViewHostFactory,
+            @NonNull WindowDecorViewHostSupplier windowDecorViewHostSupplier) {
         mContext = context;
         mUserContext = userContext;
         mDisplayController = displayController;
@@ -187,6 +191,7 @@
         mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier;
         mWindowContainerTransactionSupplier = windowContainerTransactionSupplier;
         mSurfaceControlViewHostFactory = surfaceControlViewHostFactory;
+        mWindowDecorViewHostSupplier = windowDecorViewHostSupplier;
         mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId);
         final InsetsState insetsState = mDisplayController.getInsetsState(mTaskInfo.displayId);
         mIsStatusBarVisible = insetsState != null
@@ -212,15 +217,7 @@
     void relayout(RelayoutParams params, SurfaceControl.Transaction startT,
             SurfaceControl.Transaction finishT, WindowContainerTransaction wct, T rootView,
             RelayoutResult<T> outResult) {
-        updateViewsAndSurfaces(params, startT, finishT, wct, rootView, outResult);
-        if (outResult.mRootView != null) {
-            updateViewHost(params, startT, outResult);
-        }
-    }
-
-    protected void updateViewsAndSurfaces(RelayoutParams params,
-            SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
-            WindowContainerTransaction wct, T rootView, RelayoutResult<T> outResult) {
+        Trace.beginSection("WindowDecoration#relayout");
         outResult.reset();
         if (params.mRunningTaskInfo != null) {
             mTaskInfo = params.mRunningTaskInfo;
@@ -231,17 +228,21 @@
         if (!mTaskInfo.isVisible) {
             releaseViews(wct);
             finishT.hide(mTaskSurface);
+            Trace.endSection(); // WindowDecoration#relayout
             return;
         }
-
+        Trace.beginSection("WindowDecoration#relayout-inflateIfNeeded");
         inflateIfNeeded(params, wct, rootView, oldLayoutResId, outResult);
-        if (outResult.mRootView == null) {
-            // Didn't manage to create a root view, early out.
+        Trace.endSection();
+        final boolean hasCaptionView = outResult.mRootView != null;
+        if (!hasCaptionView) {
+            Trace.endSection(); // WindowDecoration#relayout
             return;
         }
-        rootView = null; // Clear it just in case we use it accidentally
 
+        Trace.beginSection("WindowDecoration#relayout-updateCaptionVisibility");
         updateCaptionVisibility(outResult.mRootView);
+        Trace.endSection();
 
         final Rect taskBounds = mTaskInfo.getConfiguration().windowConfiguration.getBounds();
         outResult.mWidth = taskBounds.width();
@@ -254,10 +255,23 @@
                 ? loadDimensionPixelSize(resources, params.mCaptionWidthId) : taskBounds.width();
         outResult.mCaptionX = (outResult.mWidth - outResult.mCaptionWidth) / 2;
 
+        Trace.beginSection("WindowDecoration#relayout-acquire");
+        if (mDecorViewHost == null) {
+            mDecorViewHost = mWindowDecorViewHostSupplier.acquire(mDecorWindowContext, mDisplay);
+        }
+        Trace.endSection();
+
+        final SurfaceControl captionSurface = mDecorViewHost.getSurfaceControl();
+        Trace.beginSection("WindowDecoration#relayout-updateSurfacesAndInsets");
         updateDecorationContainerSurface(startT, outResult);
-        updateCaptionContainerSurface(startT, outResult);
+        updateCaptionContainerSurface(captionSurface, startT, outResult);
         updateCaptionInsets(params, wct, outResult, taskBounds);
         updateTaskSurface(params, startT, finishT, outResult);
+        Trace.endSection();
+
+        outResult.mRootView.setPadding(0, params.mCaptionTopPadding, 0, 0);
+        updateViewHierarchy(params, outResult, startT);
+        Trace.endSection(); // WindowDecoration#relayout
     }
 
     private void inflateIfNeeded(RelayoutParams params, WindowContainerTransaction wct,
@@ -305,6 +319,32 @@
         return (T) LayoutInflater.from(context).inflate(layoutResId, null);
     }
 
+    private void updateViewHierarchy(@NonNull RelayoutParams params,
+            @NonNull RelayoutResult<T> outResult, @NonNull SurfaceControl.Transaction startT) {
+        Trace.beginSection("WindowDecoration#updateViewHierarchy");
+        final WindowManager.LayoutParams lp =
+                new WindowManager.LayoutParams(
+                        outResult.mCaptionWidth,
+                        outResult.mCaptionHeight,
+                        TYPE_APPLICATION,
+                        FLAG_NOT_FOCUSABLE | FLAG_SPLIT_TOUCH,
+                        PixelFormat.TRANSPARENT);
+        lp.setTitle("Caption of Task=" + mTaskInfo.taskId);
+        lp.setTrustedOverlay();
+        lp.inputFeatures = params.mInputFeatures;
+        if (params.mAsyncViewHost) {
+            if (params.mApplyStartTransactionOnDraw) {
+                throw new IllegalArgumentException(
+                        "We cannot both sync viewhost ondraw and delay viewhost creation.");
+            }
+            mDecorViewHost.updateViewAsync(outResult.mRootView, lp, mTaskInfo.getConfiguration());
+        } else {
+            mDecorViewHost.updateView(outResult.mRootView, lp, mTaskInfo.getConfiguration(),
+                    params.mApplyStartTransactionOnDraw ? startT : null);
+        }
+        Trace.endSection();
+    }
+
     private void updateDecorationContainerSurface(
             SurfaceControl.Transaction startT, RelayoutResult<T> outResult) {
         if (mDecorationContainerSurface == null) {
@@ -325,23 +365,14 @@
                 .show(mDecorationContainerSurface);
     }
 
-    private void updateCaptionContainerSurface(
+    private void updateCaptionContainerSurface(@NonNull SurfaceControl captionSurface,
             SurfaceControl.Transaction startT, RelayoutResult<T> outResult) {
-        if (mCaptionContainerSurface == null) {
-            final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
-            mCaptionContainerSurface = builder
-                    .setName("Caption container of Task=" + mTaskInfo.taskId)
-                    .setContainerLayer()
-                    .setParent(mDecorationContainerSurface)
-                    .setCallsite("WindowDecoration.updateCaptionContainerSurface")
-                    .build();
-        }
-
-        startT.setWindowCrop(mCaptionContainerSurface, outResult.mCaptionWidth,
+        startT.reparent(captionSurface, mDecorationContainerSurface)
+                .setWindowCrop(captionSurface, outResult.mCaptionWidth,
                         outResult.mCaptionHeight)
-                .setPosition(mCaptionContainerSurface, outResult.mCaptionX, 0 /* y */)
-                .setLayer(mCaptionContainerSurface, CAPTION_LAYER_Z_ORDER)
-                .show(mCaptionContainerSurface);
+                .setPosition(captionSurface, outResult.mCaptionX, 0 /* y */)
+                .setLayer(captionSurface, CAPTION_LAYER_Z_ORDER)
+                .show(captionSurface);
     }
 
     private void updateCaptionInsets(RelayoutParams params, WindowContainerTransaction wct,
@@ -435,64 +466,6 @@
         }
     }
 
-    /**
-     * Updates a {@link SurfaceControlViewHost} to connect the window decoration surfaces with our
-     * View hierarchy.
-     *
-     * @param params parameters to use from the last relayout
-     * @param onDrawTransaction a transaction to apply in sync with #onDraw
-     * @param outResult results to use from the last relayout
-     *
-     */
-    protected void updateViewHost(RelayoutParams params,
-            SurfaceControl.Transaction onDrawTransaction, RelayoutResult<T> outResult) {
-        Trace.beginSection("CaptionViewHostLayout");
-        if (mCaptionWindowManager == null) {
-            // Put caption under a container surface because ViewRootImpl sets the destination frame
-            // of windowless window layers and BLASTBufferQueue#update() doesn't support offset.
-            mCaptionWindowManager = new WindowlessWindowManager(
-                    mTaskInfo.getConfiguration(), mCaptionContainerSurface,
-                    null /* hostInputToken */);
-        }
-        mCaptionWindowManager.setConfiguration(mTaskInfo.getConfiguration());
-        final WindowManager.LayoutParams lp =
-                new WindowManager.LayoutParams(
-                        outResult.mCaptionWidth,
-                        outResult.mCaptionHeight,
-                        TYPE_APPLICATION,
-                        FLAG_NOT_FOCUSABLE | FLAG_SPLIT_TOUCH,
-                        PixelFormat.TRANSPARENT);
-        lp.setTitle("Caption of Task=" + mTaskInfo.taskId);
-        lp.setTrustedOverlay();
-        lp.inputFeatures = params.mInputFeatures;
-        if (mViewHost == null) {
-            Trace.beginSection("CaptionViewHostLayout-new");
-            mViewHost = mSurfaceControlViewHostFactory.create(mDecorWindowContext, mDisplay,
-                    mCaptionWindowManager);
-            if (params.mApplyStartTransactionOnDraw) {
-                if (onDrawTransaction == null) {
-                    throw new IllegalArgumentException("Trying to sync a null Transaction");
-                }
-                mViewHost.getRootSurfaceControl().applyTransactionOnDraw(onDrawTransaction);
-            }
-            outResult.mRootView.setPadding(0, params.mCaptionTopPadding, 0, 0);
-            mViewHost.setView(outResult.mRootView, lp);
-            Trace.endSection();
-        } else {
-            Trace.beginSection("CaptionViewHostLayout-relayout");
-            if (params.mApplyStartTransactionOnDraw) {
-                if (onDrawTransaction == null) {
-                    throw new IllegalArgumentException("Trying to sync a null Transaction");
-                }
-                mViewHost.getRootSurfaceControl().applyTransactionOnDraw(onDrawTransaction);
-            }
-            outResult.mRootView.setPadding(0, params.mCaptionTopPadding, 0, 0);
-            mViewHost.relayout(lp);
-            Trace.endSection();
-        }
-        Trace.endSection(); // CaptionViewHostLayout
-    }
-
     private Rect calculateBoundingRect(@NonNull OccludingCaptionElement element,
             int elementWidthPx, @NonNull Rect captionRect) {
         switch (element.mAlignment) {
@@ -580,18 +553,11 @@
     }
 
     void releaseViews(WindowContainerTransaction wct) {
-        if (mViewHost != null) {
-            mViewHost.release();
-            mViewHost = null;
-        }
-
-        mCaptionWindowManager = null;
-
         final SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
         boolean released = false;
-        if (mCaptionContainerSurface != null) {
-            t.remove(mCaptionContainerSurface);
-            mCaptionContainerSurface = null;
+        if (mDecorViewHost != null) {
+            mWindowDecorViewHostSupplier.release(mDecorViewHost, t);
+            mDecorViewHost = null;
             released = true;
         }
 
@@ -742,6 +708,7 @@
 
         boolean mApplyStartTransactionOnDraw;
         boolean mSetTaskPositionAndCrop;
+        boolean mAsyncViewHost;
 
         void reset() {
             mLayoutResId = Resources.ID_NULL;
@@ -758,6 +725,7 @@
 
             mApplyStartTransactionOnDraw = false;
             mSetTaskPositionAndCrop = false;
+            mAsyncViewHost = false;
             mWindowDecorConfig = null;
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHost.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHost.kt
new file mode 100644
index 0000000..139e679
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHost.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.windowdecor.viewhost
+
+import android.content.Context
+import android.content.res.Configuration
+import android.view.Display
+import android.view.SurfaceControl
+import android.view.SurfaceControlViewHost
+import android.view.View
+import android.view.WindowManager
+import android.view.WindowlessWindowManager
+import androidx.tracing.Trace
+import com.android.internal.annotations.VisibleForTesting
+import com.android.wm.shell.shared.annotations.ShellMainThread
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+typealias SurfaceControlViewHostFactory =
+            (Context, Display, WindowlessWindowManager, String) -> SurfaceControlViewHost
+
+/**
+ * A default implementation of [WindowDecorViewHost] backed by a [SurfaceControlViewHost].
+ *
+ * It does not support swapping the root view added to the VRI of the [SurfaceControlViewHost], and
+ * any attempts to do will throw, which means that once a [View] is added using [updateView] or
+ * [updateViewAsync], only its properties and binding may be changed, its children views may be
+ * added, removed or changed and its [WindowManager.LayoutParams] may be changed.
+ * It also supports asynchronously updating the view hierarchy using [updateViewAsync], in which
+ * case the update work will be posted on the [ShellMainThread] with no delay.
+ */
+class DefaultWindowDecorViewHost(
+    private val context: Context,
+    @ShellMainThread private val mainScope: CoroutineScope,
+    private val display: Display,
+    private val surfaceControlViewHostFactory: SurfaceControlViewHostFactory = { c, d, wwm, s ->
+        SurfaceControlViewHost(c, d, wwm, s)
+    }
+) : WindowDecorViewHost {
+
+    private val rootSurface: SurfaceControl = SurfaceControl.Builder()
+            .setName("DefaultWindowDecorViewHost surface")
+            .setContainerLayer()
+            .setCallsite("DefaultWindowDecorViewHost#init")
+            .build()
+
+    private var wwm: WindowlessWindowManager? = null
+    @VisibleForTesting
+    var viewHost: SurfaceControlViewHost? = null
+    private var currentUpdateJob: Job? = null
+
+    override val surfaceControl: SurfaceControl
+        get() = rootSurface
+
+    override fun updateView(
+        view: View,
+        attrs: WindowManager.LayoutParams,
+        configuration: Configuration,
+        onDrawTransaction: SurfaceControl.Transaction?
+    ) {
+        Trace.beginSection("DefaultWindowDecorViewHost#updateView")
+        clearCurrentUpdateJob()
+        updateViewHost(view, attrs, configuration, onDrawTransaction)
+        Trace.endSection()
+    }
+
+    override fun updateViewAsync(
+        view: View,
+        attrs: WindowManager.LayoutParams,
+        configuration: Configuration
+    ) {
+        Trace.beginSection("DefaultWindowDecorViewHost#updateViewAsync")
+        clearCurrentUpdateJob()
+        currentUpdateJob = mainScope.launch {
+            updateViewHost(view, attrs, configuration, onDrawTransaction = null)
+        }
+        Trace.endSection()
+    }
+
+    override fun release(t: SurfaceControl.Transaction) {
+        clearCurrentUpdateJob()
+        viewHost?.release()
+        t.remove(rootSurface)
+    }
+
+    private fun updateViewHost(
+        view: View,
+        attrs: WindowManager.LayoutParams,
+        configuration: Configuration,
+        onDrawTransaction: SurfaceControl.Transaction?
+    ) {
+        Trace.beginSection("DefaultWindowDecorViewHost#updateViewHost")
+        if (wwm == null) {
+            wwm = WindowlessWindowManager(configuration, rootSurface, null)
+        }
+        requireWindowlessWindowManager().setConfiguration(configuration)
+        if (viewHost == null) {
+            viewHost = surfaceControlViewHostFactory.invoke(
+                context,
+                display,
+                requireWindowlessWindowManager(),
+                "DefaultWindowDecorViewHost#updateViewHost"
+            )
+        }
+        onDrawTransaction?.let {
+            requireViewHost().rootSurfaceControl.applyTransactionOnDraw(it)
+        }
+        if (requireViewHost().view == null) {
+            Trace.beginSection("DefaultWindowDecorViewHost#updateViewHost-setView")
+            requireViewHost().setView(view, attrs)
+            Trace.endSection()
+        } else {
+            check(requireViewHost().view == view) { "Changing view is not allowed" }
+            Trace.beginSection("DefaultWindowDecorViewHost#updateViewHost-relayout")
+            requireViewHost().relayout(attrs)
+            Trace.endSection()
+        }
+        Trace.endSection()
+    }
+
+    private fun clearCurrentUpdateJob() {
+        currentUpdateJob?.cancel()
+        currentUpdateJob = null
+    }
+
+    private fun requireWindowlessWindowManager(): WindowlessWindowManager {
+        return wwm ?: error("Expected non-null windowless window manager")
+    }
+
+    private fun requireViewHost(): SurfaceControlViewHost {
+        return viewHost ?: error("Expected non-null view host")
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHostSupplier.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHostSupplier.kt
new file mode 100644
index 0000000..9997e8f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHostSupplier.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.windowdecor.viewhost
+
+import android.content.Context
+import android.view.Display
+import android.view.SurfaceControl
+import com.android.wm.shell.shared.annotations.ShellMainThread
+import kotlinx.coroutines.CoroutineScope
+
+/**
+ * A supplier of [DefaultWindowDecorViewHost]s. It creates a new one every time one is requested.
+ */
+class DefaultWindowDecorViewHostSupplier(
+    @ShellMainThread private val mainScope: CoroutineScope,
+) : WindowDecorViewHostSupplier<DefaultWindowDecorViewHost> {
+
+    override fun acquire(context: Context, display: Display): DefaultWindowDecorViewHost {
+        return DefaultWindowDecorViewHost(context, mainScope, display)
+    }
+
+    override fun release(viewHost: DefaultWindowDecorViewHost, t: SurfaceControl.Transaction) {
+        viewHost.release(t)
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/WindowDecorViewHost.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/WindowDecorViewHost.kt
new file mode 100644
index 0000000..3fbaea8
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/WindowDecorViewHost.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.windowdecor.viewhost
+
+import android.content.res.Configuration
+import android.view.SurfaceControl
+import android.view.View
+import android.view.WindowManager
+import com.android.wm.shell.windowdecor.WindowDecoration
+
+/**
+ * An interface for a utility that hosts a [WindowDecoration]'s [View] hierarchy under a
+ * [SurfaceControl].
+ */
+interface WindowDecorViewHost {
+    /** The surface where the underlying [View] hierarchy is being rendered. */
+    val surfaceControl: SurfaceControl
+
+    /** Synchronously update the view hierarchy of this view host. */
+    fun updateView(
+        view: View,
+        attrs: WindowManager.LayoutParams,
+        configuration: Configuration,
+        onDrawTransaction: SurfaceControl.Transaction?
+    )
+
+    /** Asynchronously update the view hierarchy of this view host. */
+    fun updateViewAsync(
+        view: View,
+        attrs: WindowManager.LayoutParams,
+        configuration: Configuration
+    )
+
+    /** Releases the underlying [View] hierarchy and removes the backing [SurfaceControl]. */
+    fun release(t: SurfaceControl.Transaction)
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/WindowDecorViewHostSupplier.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/WindowDecorViewHostSupplier.kt
new file mode 100644
index 0000000..0e23584
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/WindowDecorViewHostSupplier.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.windowdecor.viewhost
+
+import android.content.Context
+import android.view.Display
+import android.view.SurfaceControl
+
+/**
+ * An interface for a supplier of [WindowDecorViewHost]s.
+ */
+interface WindowDecorViewHostSupplier<T : WindowDecorViewHost> {
+    /** Acquire a [WindowDecorViewHost]. */
+    fun acquire(context: Context, display: Display): T
+
+    /**
+     * Release a [WindowDecorViewHost] when it is no longer used.
+     *
+     * @param viewHost the [WindowDecorViewHost] to release
+     * @param t a transaction that may be used to remove any underlying backing [SurfaceControl]
+     *          that are hosting this [WindowDecorViewHost]. The supplier is not expected to apply
+     *          the transaction. It should be applied by the owner of this supplier.
+     */
+    fun release(viewHost: T, t: SurfaceControl.Transaction)
+}
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 5b02837..497d0e5 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
@@ -18,6 +18,8 @@
 import android.window.WindowContainerTransaction
 import androidx.test.filters.SmallTest
 import com.android.dx.mockito.inline.extended.ExtendedMockito
+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.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.ShellTestCase
@@ -448,6 +450,42 @@
         )
     }
 
+    @Test
+    fun startDragToDesktop_aborted_logsDragHoldCancelled() {
+        val transition = startDragToDesktopTransition(defaultHandler, createTask(), dragAnimator)
+
+        defaultHandler.onTransitionConsumed(transition, aborted = true, mock())
+
+        verify(mockInteractionJankMonitor).cancel(eq(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD))
+        verify(mockInteractionJankMonitor, times(0)).cancel(
+            eq(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE))
+    }
+
+    @Test
+    fun mergeEndDragToDesktop_aborted_logsDragReleaseCancelled() {
+        val task = createTask()
+        val startTransition = startDrag(defaultHandler, task)
+        val endTransition = mock<IBinder>()
+        defaultHandler.onTaskResizeAnimationListener = mock()
+        defaultHandler.mergeAnimation(
+            transition = endTransition,
+            info = createTransitionInfo(
+                type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
+                draggedTask = task
+            ),
+            t = mock<SurfaceControl.Transaction>(),
+            mergeTarget = startTransition,
+            finishCallback = mock<Transitions.TransitionFinishCallback>()
+        )
+
+        defaultHandler.onTransitionConsumed(endTransition, aborted = true, mock())
+
+        verify(mockInteractionJankMonitor)
+            .cancel(eq(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE))
+        verify(mockInteractionJankMonitor, times(0))
+            .cancel(eq(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD))
+    }
+
     private fun startDrag(
         handler: DragToDesktopTransitionHandler,
         task: RunningTaskInfo = createTask(),
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/SplitDragPolicyTest.java
similarity index 92%
rename from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/SplitDragPolicyTest.java
index 645b296..46b60499 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/SplitDragPolicyTest.java
@@ -30,11 +30,11 @@
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
-import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_FULLSCREEN;
-import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_BOTTOM;
-import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_LEFT;
-import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT;
-import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_TOP;
+import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_FULLSCREEN;
+import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_SPLIT_BOTTOM;
+import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_SPLIT_LEFT;
+import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_SPLIT_RIGHT;
+import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_SPLIT_TOP;
 
 import static junit.framework.Assert.assertTrue;
 import static junit.framework.Assert.fail;
@@ -72,7 +72,7 @@
 import com.android.internal.logging.InstanceId;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.draganddrop.DragAndDropPolicy.Target;
+import com.android.wm.shell.draganddrop.SplitDragPolicy.Target;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 
 import org.junit.After;
@@ -92,7 +92,7 @@
  */
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-public class DragAndDropPolicyTest extends ShellTestCase {
+public class SplitDragPolicyTest extends ShellTestCase {
 
     @Mock
     private Context mContext;
@@ -104,7 +104,7 @@
     @Mock
     private SplitScreenController mSplitScreenStarter;
     @Mock
-    private DragAndDropPolicy.Starter mFullscreenStarter;
+    private SplitDragPolicy.Starter mFullscreenStarter;
 
     @Mock
     private InstanceId mLoggerSessionId;
@@ -112,7 +112,7 @@
     private DisplayLayout mLandscapeDisplayLayout;
     private DisplayLayout mPortraitDisplayLayout;
     private Insets mInsets;
-    private DragAndDropPolicy mPolicy;
+    private SplitDragPolicy mPolicy;
 
     private ClipData mActivityClipData;
     private PendingIntent mLaunchableIntentPendingIntent;
@@ -150,7 +150,7 @@
         mPortraitDisplayLayout = new DisplayLayout(info2, res, false, false);
         mInsets = Insets.of(0, 0, 0, 0);
 
-        mPolicy = spy(new DragAndDropPolicy(mContext, mSplitScreenStarter, mFullscreenStarter));
+        mPolicy = spy(new SplitDragPolicy(mContext, mSplitScreenStarter, mFullscreenStarter));
         mActivityClipData = createAppClipData(MIMETYPE_APPLICATION_ACTIVITY);
         mLaunchableIntentPendingIntent = mock(PendingIntent.class);
         when(mLaunchableIntentPendingIntent.getCreatorUserHandle())
@@ -289,7 +289,7 @@
         ArrayList<Target> targets = assertExactTargetTypes(
                 mPolicy.getTargets(mInsets), TYPE_FULLSCREEN);
 
-        mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), null /* hideTaskToken */);
+        mPolicy.onDropped(filterTargetByType(targets, TYPE_FULLSCREEN), null /* hideTaskToken */);
         verify(mFullscreenStarter).startIntent(any(), anyInt(), any(),
                 eq(SPLIT_POSITION_UNDEFINED), any(), any());
     }
@@ -304,12 +304,12 @@
         ArrayList<Target> targets = assertExactTargetTypes(
                 mPolicy.getTargets(mInsets), TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
 
-        mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_LEFT), null /* hideTaskToken */);
+        mPolicy.onDropped(filterTargetByType(targets, TYPE_SPLIT_LEFT), null /* hideTaskToken */);
         verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(),
                 eq(SPLIT_POSITION_TOP_OR_LEFT), any(), any());
         reset(mSplitScreenStarter);
 
-        mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT), null /* hideTaskToken */);
+        mPolicy.onDropped(filterTargetByType(targets, TYPE_SPLIT_RIGHT), null /* hideTaskToken */);
         verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(),
                 eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any(), any());
     }
@@ -324,12 +324,12 @@
         ArrayList<Target> targets = assertExactTargetTypes(
                 mPolicy.getTargets(mInsets), TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM);
 
-        mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_TOP), null /* hideTaskToken */);
+        mPolicy.onDropped(filterTargetByType(targets, TYPE_SPLIT_TOP), null /* hideTaskToken */);
         verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(),
                 eq(SPLIT_POSITION_TOP_OR_LEFT), any(), any());
         reset(mSplitScreenStarter);
 
-        mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM),
+        mPolicy.onDropped(filterTargetByType(targets, TYPE_SPLIT_BOTTOM),
                 null /* hideTaskToken */);
         verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(),
                 eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any(), any());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 6ddb678..f3944d5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -256,7 +256,7 @@
         when(mMockPipDisplayLayoutState.getDisplayLayout()).thenReturn(mMockDisplayLayout1);
         when(mMockDisplayController.getDisplayLayout(displayId)).thenReturn(mMockDisplayLayout2);
 
-        when(mMockPipTaskOrganizer.isInPip()).thenReturn(true);
+        when(mMockPipTransitionState.hasEnteredPip()).thenReturn(true);
         mPipController.mDisplaysChangedListener.onDisplayConfigurationChanged(
                 displayId, new Configuration());
 
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 2c805e8..a17d08d 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
@@ -98,6 +98,7 @@
 import com.android.wm.shell.transition.Transitions
 import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeKeyguardChangeListener
 import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener
+import com.android.wm.shell.windowdecor.viewhost.WindowDecorViewHostSupplier
 import java.util.Optional
 import java.util.function.Consumer
 import java.util.function.Supplier
@@ -181,6 +182,7 @@
     @Mock private lateinit var mockTaskPositionerFactory:
             DesktopModeWindowDecorViewModel.TaskPositionerFactory
     @Mock private lateinit var mockTaskPositioner: TaskPositioner
+    @Mock private lateinit var mockWindowDecorViewHostSupplier: WindowDecorViewHostSupplier<*>
     private lateinit var spyContext: TestableContext
 
     private val transactionFactory = Supplier<SurfaceControl.Transaction> {
@@ -230,6 +232,7 @@
                 mockGenericLinksParser,
                 mockAssistContentRequester,
                 mockMultiInstanceHelper,
+                mockWindowDecorViewHostSupplier,
                 mockDesktopModeWindowDecorFactory,
                 mockInputMonitorFactory,
                 transactionFactory,
@@ -1197,7 +1200,7 @@
         whenever(
             mockDesktopModeWindowDecorFactory.create(
                 any(), any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(),
-                any(), any(), any(), any())
+                any(), any(), any(), any(), any())
         ).thenReturn(decoration)
         decoration.mTaskInfo = task
         whenever(decoration.isFocused).thenReturn(task.isFocused)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index b9e542a0..69efdb8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -97,6 +97,8 @@
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams;
+import com.android.wm.shell.windowdecor.viewhost.WindowDecorViewHost;
+import com.android.wm.shell.windowdecor.viewhost.WindowDecorViewHostSupplier;
 
 import kotlin.Unit;
 import kotlin.jvm.functions.Function0;
@@ -105,6 +107,7 @@
 import org.junit.After;
 import org.junit.Before;
 import org.junit.BeforeClass;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -162,6 +165,10 @@
     @Mock
     private WindowDecoration.SurfaceControlViewHostFactory mMockSurfaceControlViewHostFactory;
     @Mock
+    private WindowDecorViewHostSupplier mMockWindowDecorViewHostSupplier;
+    @Mock
+    private WindowDecorViewHost mMockWindowDecorViewHost;
+    @Mock
     private TypedArray mMockRoundedCornersRadiusArray;
     @Mock
     private TestTouchEventListener mMockTouchEventListener;
@@ -222,6 +229,8 @@
         mTestableContext = new TestableContext(mContext);
         mTestableContext.ensureTestableResources();
         mContext.setMockPackageManager(mMockPackageManager);
+        when(mMockMultiInstanceHelper.supportsMultiInstanceSplit(any()))
+                .thenReturn(false);
         when(mMockPackageManager.getApplicationLabel(any())).thenReturn("applicationLabel");
         final ActivityInfo activityInfo = new ActivityInfo();
         activityInfo.applicationInfo = new ApplicationInfo();
@@ -229,10 +238,13 @@
         final Display defaultDisplay = mock(Display.class);
         doReturn(defaultDisplay).when(mMockDisplayController).getDisplay(Display.DEFAULT_DISPLAY);
         doReturn(mInsetsState).when(mMockDisplayController).getInsetsState(anyInt());
-        when(mMockHandleMenuFactory.create(any(), any(), anyInt(), any(), any(),
-                any(), anyBoolean(), anyBoolean(), any(), anyInt(), anyInt(), anyInt()))
+        when(mMockHandleMenuFactory.create(any(), any(), anyInt(), any(), any(), any(),
+                anyBoolean(), anyBoolean(), anyBoolean(), any(), anyInt(), anyInt(), anyInt()))
                 .thenReturn(mMockHandleMenu);
         when(mMockMultiInstanceHelper.supportsMultiInstanceSplit(any())).thenReturn(false);
+        when(mMockWindowDecorViewHostSupplier.acquire(any(), eq(defaultDisplay)))
+                .thenReturn(mMockWindowDecorViewHost);
+        when(mMockWindowDecorViewHost.getSurfaceControl()).thenReturn(mock(SurfaceControl.class));
     }
 
     @After
@@ -504,6 +516,42 @@
     }
 
     @Test
+    public void updateRelayoutParams_handle_requestsAsyncViewHostRendering() {
+        final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+        // Make the task fullscreen so that its decoration is an App Handle.
+        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        final RelayoutParams relayoutParams = new RelayoutParams();
+
+        DesktopModeWindowDecoration.updateRelayoutParams(
+                relayoutParams,
+                mTestableContext,
+                taskInfo,
+                /* applyStartTransactionOnDraw= */ true,
+                /* shouldSetTaskPositionAndCrop */ false);
+
+        // App Handles don't need to be rendered in sync with the task animation, per UX.
+        assertThat(relayoutParams.mAsyncViewHost).isTrue();
+    }
+
+    @Test
+    public void updateRelayoutParams_header_requestsSyncViewHostRendering() {
+        final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+        // Make the task freeform so that its decoration is an App Header.
+        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        final RelayoutParams relayoutParams = new RelayoutParams();
+
+        DesktopModeWindowDecoration.updateRelayoutParams(
+                relayoutParams,
+                mTestableContext,
+                taskInfo,
+                /* applyStartTransactionOnDraw= */ true,
+                /* shouldSetTaskPositionAndCrop */ false);
+
+        // App Headers must be rendered in sync with the task animation, so it cannot be delayed.
+        assertThat(relayoutParams.mAsyncViewHost).isFalse();
+    }
+
+    @Test
     public void relayout_fullscreenTask_appliesTransactionImmediately() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
         final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
@@ -516,6 +564,7 @@
     }
 
     @Test
+    @Ignore("TODO(b/367235906): Due to MONITOR_INPUT permission error")
     public void relayout_freeformTask_appliesTransactionOnDraw() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
         final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
@@ -526,74 +575,7 @@
         spyWindowDecor.relayout(taskInfo);
 
         verify(mMockTransaction, never()).apply();
-        verify(mMockRootSurfaceControl).applyTransactionOnDraw(mMockTransaction);
-    }
-
-    @Test
-    public void relayout_fullscreenTask_doesNotCreateViewHostImmediately() {
-        final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
-        final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
-        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
-
-        spyWindowDecor.relayout(taskInfo);
-
-        verify(mMockSurfaceControlViewHostFactory, never()).create(any(), any(), any());
-    }
-
-    @Test
-    public void relayout_fullscreenTask_postsViewHostCreation() {
-        final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
-        final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
-        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
-
-        ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
-        spyWindowDecor.relayout(taskInfo);
-
-        verify(mMockHandler).post(runnableArgument.capture());
-        runnableArgument.getValue().run();
-        verify(mMockSurfaceControlViewHostFactory).create(any(), any(), any());
-    }
-
-    @Test
-    public void relayout_freeformTask_createsViewHostImmediately() {
-        final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
-        final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
-        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
-        // Make non-resizable to avoid dealing with input-permissions (MONITOR_INPUT)
-        taskInfo.isResizeable = false;
-
-        spyWindowDecor.relayout(taskInfo);
-
-        verify(mMockSurfaceControlViewHostFactory).create(any(), any(), any());
-        verify(mMockHandler, never()).post(any());
-    }
-
-    @Test
-    public void relayout_removesExistingHandlerCallback() {
-        final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
-        final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
-        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
-        ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
-        spyWindowDecor.relayout(taskInfo);
-        verify(mMockHandler).post(runnableArgument.capture());
-
-        spyWindowDecor.relayout(taskInfo);
-
-        verify(mMockHandler).removeCallbacks(runnableArgument.getValue());
-    }
-
-    @Test
-    public void close_removesExistingHandlerCallback() {
-        final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
-        final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
-        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
-        ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
-        spyWindowDecor.relayout(taskInfo);
-        verify(mMockHandler).post(runnableArgument.capture());
-
-        spyWindowDecor.close();
-
-        verify(mMockHandler).removeCallbacks(runnableArgument.getValue());
+        verify(mMockWindowDecorViewHost).updateView(any(), any(), any(), eq(mMockTransaction));
     }
 
     @Test
@@ -766,6 +748,7 @@
                 any(),
                 any(),
                 any(),
+                any(),
                 openInBrowserCaptor.capture(),
                 any(),
                 any()
@@ -793,6 +776,7 @@
                 any(),
                 any(),
                 any(),
+                any(),
                 openInBrowserCaptor.capture(),
                 any(),
                 any()
@@ -843,6 +827,7 @@
                 any(),
                 any(),
                 any(),
+                any(),
                 closeClickListener.capture(),
                 any()
         );
@@ -854,8 +839,10 @@
     }
 
     private void verifyHandleMenuCreated(@Nullable Uri uri) {
+
         verify(mMockHandleMenuFactory).create(any(), any(), anyInt(), any(), any(),
-                any(), anyBoolean(), anyBoolean(), eq(uri), anyInt(), anyInt(), anyInt());
+                any(), anyBoolean(), anyBoolean(), anyBoolean(), eq(uri), anyInt(),
+                anyInt(), anyInt());
     }
 
     private void createMaximizeMenu(DesktopModeWindowDecoration decoration, MaximizeMenu menu) {
@@ -924,7 +911,8 @@
                 mMockGenericLinksParser, mMockAssistContentRequester, SurfaceControl.Builder::new,
                 mMockTransactionSupplier, WindowContainerTransaction::new, SurfaceControl::new,
                 new WindowManagerWrapper(mMockWindowManager), mMockSurfaceControlViewHostFactory,
-                maximizeMenuFactory, mMockHandleMenuFactory, mMockMultiInstanceHelper);
+                mMockWindowDecorViewHostSupplier, maximizeMenuFactory, mMockHandleMenuFactory,
+                mMockMultiInstanceHelper);
         windowDecor.setCaptionListeners(mMockTouchEventListener, mMockTouchEventListener,
                 mMockTouchEventListener, mMockTouchEventListener);
         windowDecor.setExclusionRegionListener(mMockExclusionRegionListener);
@@ -953,7 +941,7 @@
     }
 
     private void createHandleMenu(@NonNull DesktopModeWindowDecoration decor) {
-        decor.createHandleMenu();
+        decor.createHandleMenu(false);
         // Call DesktopModeWindowDecoration#onAssistContentReceived because decor waits to receive
         // {@link AssistContent} before creating the menu
         decor.onAssistContentReceived(mAssistContent);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
index 1f33ae6..24f6bec 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
@@ -39,6 +39,7 @@
 import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT
 import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP
 import com.google.common.truth.Truth.assertThat
+import junit.framework.Assert.assertFalse
 import junit.framework.Assert.assertTrue
 import org.junit.After
 import org.junit.Before
@@ -105,6 +106,7 @@
         initializeTaskInfo()
         mockWindowDecoration.mDisplay = mockDisplay
         mockWindowDecoration.mDecorWindowContext = mockContext
+        mockWindowDecoration.mTaskInfo.isResizeable = true
         whenever(mockContext.getResources()).thenReturn(mockResources)
         whenever(mockWindowDecoration.mDecorWindowContext.resources).thenReturn(mockResources)
         whenever(mockResources.getDimensionPixelSize(R.dimen.desktop_mode_minimum_window_width))
@@ -164,6 +166,60 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_SCALED_RESIZING)
+    fun testChangeBounds_unresizeableApp_heightLessThanMin_resetToStartingBounds() {
+        mockWindowDecoration.mTaskInfo.isResizeable = false
+        val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.top.toFloat())
+        val repositionTaskBounds = Rect(STARTING_BOUNDS)
+
+        // Resize to width of 95px and height of 5px with min width of 10px
+        val newX = STARTING_BOUNDS.right.toFloat() - 5
+        val newY = STARTING_BOUNDS.top.toFloat() + 95
+        val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
+
+        assertFalse(
+            DragPositioningCallbackUtility.changeBounds(
+                CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
+                repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, mockDisplayController,
+                mockWindowDecoration
+            )
+        )
+
+        assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
+        assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top)
+        assertThat(repositionTaskBounds.right).isEqualTo(STARTING_BOUNDS.right)
+        assertThat(repositionTaskBounds.bottom).isEqualTo(STARTING_BOUNDS.bottom)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_SCALED_RESIZING)
+    fun testChangeBounds_unresizeableApp_widthLessThanMin_resetToStartingBounds() {
+        mockWindowDecoration.mTaskInfo.isResizeable = false
+        val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.top.toFloat())
+        val repositionTaskBounds = Rect(STARTING_BOUNDS)
+
+        // Resize to height of 95px and width of 5px with min width of 10px
+        val newX = STARTING_BOUNDS.right.toFloat() - 95
+        val newY = STARTING_BOUNDS.top.toFloat() + 5
+        val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
+
+        assertFalse(
+            DragPositioningCallbackUtility.changeBounds(
+                CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
+                repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, mockDisplayController,
+                mockWindowDecoration
+            )
+        )
+
+
+        assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
+        assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top)
+        assertThat(repositionTaskBounds.right).isEqualTo(STARTING_BOUNDS.right)
+        assertThat(repositionTaskBounds.bottom).isEqualTo(STARTING_BOUNDS.bottom)
+    }
+
+
+    @Test
     fun testChangeBoundsDoesNotChangeHeightWhenNegative() {
         val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.top.toFloat())
         val repositionTaskBounds = Rect(STARTING_BOUNDS)
@@ -317,6 +373,34 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_SCALED_RESIZING)
+    fun testChangeBounds_unresizeableApp_beyondStableBounds_resetToStartingBounds() {
+        mockWindowDecoration.mTaskInfo.isResizeable = false
+        val startingPoint = PointF(
+            STARTING_BOUNDS.right.toFloat(),
+            STARTING_BOUNDS.bottom.toFloat()
+        )
+        val repositionTaskBounds = Rect(STARTING_BOUNDS)
+
+        // Resize to beyond stable bounds.
+        val newX = STARTING_BOUNDS.right.toFloat() + STABLE_BOUNDS.width()
+        val newY = STARTING_BOUNDS.bottom.toFloat() + STABLE_BOUNDS.height()
+
+        val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
+        assertFalse(
+            DragPositioningCallbackUtility.changeBounds(
+                CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
+                repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, mockDisplayController,
+                mockWindowDecoration
+            )
+        )
+        assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
+        assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top)
+        assertThat(repositionTaskBounds.right).isEqualTo(STARTING_BOUNDS.right)
+        assertThat(repositionTaskBounds.bottom).isEqualTo(STARTING_BOUNDS.bottom)
+    }
+
+    @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS)
     fun taskMinWidthHeightUndefined_changeBoundsInDesktopModeLessThanMin_shouldNotChangeBounds() {
         doReturn(true).`when` { DesktopModeStatus.canEnterDesktopMode(mockContext) }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FixedAspectRatioTaskPositionerDecoratorTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FixedAspectRatioTaskPositionerDecoratorTests.kt
new file mode 100644
index 0000000..ce17c1d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FixedAspectRatioTaskPositionerDecoratorTests.kt
@@ -0,0 +1,636 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor
+
+import android.app.ActivityManager
+import android.graphics.PointF
+import android.graphics.Rect
+import android.util.MathUtils.abs
+import android.util.MathUtils.max
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM
+import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_LEFT
+import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT
+import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP
+import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFINED
+import com.android.wm.shell.windowdecor.DragPositioningCallback.CtrlType
+import com.google.common.truth.Truth.assertThat
+import com.google.testing.junit.testparameterinjector.TestParameter
+import com.google.testing.junit.testparameterinjector.TestParameterInjector
+import kotlin.math.min
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.never
+
+/**
+ * Tests for the [FixedAspectRatioTaskPositionerDecorator], written in parameterized form to check
+ * decorators behaviour for different variations of drag actions.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:FixedAspectRatioTaskPositionerDecoratorTests
+ */
+@SmallTest
+@RunWith(TestParameterInjector::class)
+class FixedAspectRatioTaskPositionerDecoratorTests : ShellTestCase(){
+    @Mock
+    private lateinit var mockDesktopWindowDecoration: DesktopModeWindowDecoration
+    @Mock
+    private lateinit var mockTaskPositioner: VeiledResizeTaskPositioner
+
+    private lateinit var decoratedTaskPositioner: FixedAspectRatioTaskPositionerDecorator
+
+    @Before
+    fun setUp() {
+        mockDesktopWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply {
+            isResizeable = false
+            configuration.windowConfiguration.setBounds(PORTRAIT_BOUNDS)
+        }
+        doReturn(PORTRAIT_BOUNDS).`when`(mockTaskPositioner).onDragPositioningStart(
+            any(), any(), any())
+        doReturn(Rect()).`when`(mockTaskPositioner).onDragPositioningMove(any(), any())
+        doReturn(Rect()).`when`(mockTaskPositioner).onDragPositioningEnd(any(), any())
+        decoratedTaskPositioner = spy(
+            FixedAspectRatioTaskPositionerDecorator(
+            mockDesktopWindowDecoration, mockTaskPositioner)
+        )
+    }
+
+    @Test
+    fun testOnDragPositioningStart_noAdjustment(
+        @TestParameter testCase: ResizeableOrNotResizingTestCases
+    ) {
+        val originalX = 0f
+        val originalY = 0f
+        mockDesktopWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply {
+            isResizeable = testCase.isResizeable
+        }
+
+        decoratedTaskPositioner.onDragPositioningStart(testCase.ctrlType, originalX, originalY)
+
+        val capturedValues = getLatestOnStartArguments()
+        assertThat(capturedValues.ctrlType).isEqualTo(testCase.ctrlType)
+        assertThat(capturedValues.x).isEqualTo(originalX)
+        assertThat(capturedValues.y).isEqualTo(originalY)
+    }
+
+    @Test
+    fun testOnDragPositioningStart_cornerResize_noAdjustment(
+        @TestParameter testCase: CornerResizeStartTestCases
+    ) {
+        val originalX = 0f
+        val originalY = 0f
+
+        decoratedTaskPositioner.onDragPositioningStart(testCase.ctrlType, originalX, originalY)
+
+        val capturedValues = getLatestOnStartArguments()
+        assertThat(capturedValues.ctrlType).isEqualTo(testCase.ctrlType)
+        assertThat(capturedValues.x).isEqualTo(originalX)
+        assertThat(capturedValues.y).isEqualTo(originalY)
+    }
+
+    @Test
+    fun testOnDragPositioningStart_edgeResize_ctrlTypeAdjusted(
+        @TestParameter testCase: EdgeResizeStartTestCases, @TestParameter orientation: Orientation
+    ) {
+        val startingBounds = getAndMockBounds(orientation)
+        val startingPoint = getEdgeStartingPoint(
+            testCase.ctrlType, testCase.additionalEdgeCtrlType, startingBounds)
+
+        decoratedTaskPositioner.onDragPositioningStart(
+            testCase.ctrlType, startingPoint.x, startingPoint.y)
+
+        val adjustedCtrlType = testCase.ctrlType + testCase.additionalEdgeCtrlType
+        val capturedValues = getLatestOnStartArguments()
+        assertThat(capturedValues.ctrlType).isEqualTo(adjustedCtrlType)
+        assertThat(capturedValues.x).isEqualTo(startingPoint.x)
+        assertThat(capturedValues.y).isEqualTo(startingPoint.y)
+    }
+
+    @Test
+    fun testOnDragPositioningMove_noAdjustment(
+        @TestParameter testCase: ResizeableOrNotResizingTestCases
+    ) {
+        val originalX = 0f
+        val originalY = 0f
+        decoratedTaskPositioner.onDragPositioningStart(testCase.ctrlType, originalX, originalX)
+        mockDesktopWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply {
+            isResizeable = testCase.isResizeable
+        }
+
+        decoratedTaskPositioner.onDragPositioningMove(
+            originalX + SMALL_DELTA, originalY + SMALL_DELTA)
+
+        val capturedValues = getLatestOnMoveArguments()
+        assertThat(capturedValues.x).isEqualTo(originalX + SMALL_DELTA)
+        assertThat(capturedValues.y).isEqualTo(originalY + SMALL_DELTA)
+    }
+
+    @Test
+    fun testOnDragPositioningMove_cornerResize_invalidRegion_noResize(
+        @TestParameter testCase: InvalidCornerResizeTestCases,
+        @TestParameter orientation: Orientation
+    ) {
+        val startingBounds = getAndMockBounds(orientation)
+        val startingPoint = getCornerStartingPoint(testCase.ctrlType, startingBounds)
+
+        decoratedTaskPositioner.onDragPositioningStart(
+            testCase.ctrlType, startingPoint.x, startingPoint.y)
+
+        val updatedBounds = decoratedTaskPositioner.onDragPositioningMove(
+            startingPoint.x + testCase.dragDelta.x,
+            startingPoint.y + testCase.dragDelta.y)
+
+        verify(mockTaskPositioner, never()).onDragPositioningMove(any(), any())
+        assertThat(updatedBounds).isEqualTo(startingBounds)
+    }
+
+
+    @Test
+    fun testOnDragPositioningMove_cornerResize_validRegion_resizeToAdjustedCoordinates(
+        @TestParameter testCase: ValidCornerResizeTestCases,
+        @TestParameter orientation: Orientation
+    ) {
+        val startingBounds = getAndMockBounds(orientation)
+        val startingPoint = getCornerStartingPoint(testCase.ctrlType, startingBounds)
+
+        decoratedTaskPositioner.onDragPositioningStart(
+            testCase.ctrlType, startingPoint.x, startingPoint.y)
+
+        decoratedTaskPositioner.onDragPositioningMove(
+            startingPoint.x + testCase.dragDelta.x, startingPoint.y + testCase.dragDelta.y)
+
+        val adjustedDragDelta = calculateAdjustedDelta(
+            testCase.ctrlType, testCase.dragDelta, orientation)
+        val capturedValues = getLatestOnMoveArguments()
+        val absChangeX = abs(capturedValues.x - startingPoint.x)
+        val absChangeY = abs(capturedValues.y - startingPoint.y)
+        val resultAspectRatio = max(absChangeX, absChangeY) / min(absChangeX, absChangeY)
+        assertThat(capturedValues.x).isEqualTo(startingPoint.x + adjustedDragDelta.x)
+        assertThat(capturedValues.y).isEqualTo(startingPoint.y + adjustedDragDelta.y)
+        assertThat(resultAspectRatio).isEqualTo(STARTING_ASPECT_RATIO)
+    }
+
+    @Test
+    fun testOnDragPositioningMove_edgeResize_resizeToAdjustedCoordinates(
+        @TestParameter testCase: EdgeResizeTestCases,
+        @TestParameter orientation: Orientation
+    ) {
+        val startingBounds = getAndMockBounds(orientation)
+        val startingPoint = getEdgeStartingPoint(
+            testCase.ctrlType, testCase.additionalEdgeCtrlType, startingBounds)
+
+        decoratedTaskPositioner.onDragPositioningStart(
+            testCase.ctrlType, startingPoint.x, startingPoint.y)
+
+        decoratedTaskPositioner.onDragPositioningMove(
+            startingPoint.x + testCase.dragDelta.x,
+            startingPoint.y + testCase.dragDelta.y)
+
+        val adjustedDragDelta = calculateAdjustedDelta(
+            testCase.ctrlType + testCase.additionalEdgeCtrlType,
+            testCase.dragDelta,
+            orientation)
+        val capturedValues = getLatestOnMoveArguments()
+        val absChangeX = abs(capturedValues.x - startingPoint.x)
+        val absChangeY = abs(capturedValues.y - startingPoint.y)
+        val resultAspectRatio = max(absChangeX, absChangeY) / min(absChangeX, absChangeY)
+        assertThat(capturedValues.x).isEqualTo(startingPoint.x + adjustedDragDelta.x)
+        assertThat(capturedValues.y).isEqualTo(startingPoint.y + adjustedDragDelta.y)
+        assertThat(resultAspectRatio).isEqualTo(STARTING_ASPECT_RATIO)
+    }
+
+    @Test
+    fun testOnDragPositioningEnd_noAdjustment(
+        @TestParameter testCase: ResizeableOrNotResizingTestCases
+    ) {
+        val originalX = 0f
+        val originalY = 0f
+        decoratedTaskPositioner.onDragPositioningStart(testCase.ctrlType, originalX, originalX)
+        mockDesktopWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply {
+            isResizeable = testCase.isResizeable
+        }
+
+        decoratedTaskPositioner.onDragPositioningEnd(
+            originalX + SMALL_DELTA, originalY + SMALL_DELTA)
+
+        val capturedValues = getLatestOnEndArguments()
+        assertThat(capturedValues.x).isEqualTo(originalX + SMALL_DELTA)
+        assertThat(capturedValues.y).isEqualTo(originalY + SMALL_DELTA)
+    }
+
+    @Test
+    fun testOnDragPositioningEnd_cornerResize_invalidRegion_endsAtPreviousValidPoint(
+        @TestParameter testCase: InvalidCornerResizeTestCases,
+        @TestParameter orientation: Orientation
+    ) {
+        val startingBounds = getAndMockBounds(orientation)
+        val startingPoint = getCornerStartingPoint(testCase.ctrlType, startingBounds)
+
+        decoratedTaskPositioner.onDragPositioningStart(
+            testCase.ctrlType, startingPoint.x, startingPoint.y)
+
+        decoratedTaskPositioner.onDragPositioningEnd(
+            startingPoint.x + testCase.dragDelta.x,
+            startingPoint.y + testCase.dragDelta.y)
+
+        val capturedValues = getLatestOnEndArguments()
+        assertThat(capturedValues.x).isEqualTo(startingPoint.x)
+        assertThat(capturedValues.y).isEqualTo(startingPoint.y)
+    }
+
+    @Test
+    fun testOnDragPositioningEnd_cornerResize_validRegion_endAtAdjustedCoordinates(
+        @TestParameter testCase: ValidCornerResizeTestCases,
+        @TestParameter orientation: Orientation
+    ) {
+        val startingBounds = getAndMockBounds(orientation)
+        val startingPoint = getCornerStartingPoint(testCase.ctrlType, startingBounds)
+
+        decoratedTaskPositioner.onDragPositioningStart(
+            testCase.ctrlType, startingPoint.x, startingPoint.y)
+
+        decoratedTaskPositioner.onDragPositioningEnd(
+            startingPoint.x + testCase.dragDelta.x, startingPoint.y + testCase.dragDelta.y)
+
+        val adjustedDragDelta = calculateAdjustedDelta(
+            testCase.ctrlType, testCase.dragDelta, orientation)
+        val capturedValues = getLatestOnEndArguments()
+        val absChangeX = abs(capturedValues.x - startingPoint.x)
+        val absChangeY = abs(capturedValues.y - startingPoint.y)
+        val resultAspectRatio = max(absChangeX, absChangeY) / min(absChangeX, absChangeY)
+        assertThat(capturedValues.x).isEqualTo(startingPoint.x + adjustedDragDelta.x)
+        assertThat(capturedValues.y).isEqualTo(startingPoint.y + adjustedDragDelta.y)
+        assertThat(resultAspectRatio).isEqualTo(STARTING_ASPECT_RATIO)
+    }
+
+    @Test
+    fun testOnDragPositioningEnd_edgeResize_endAtAdjustedCoordinates(
+        @TestParameter testCase: EdgeResizeTestCases,
+        @TestParameter orientation: Orientation
+    ) {
+        val startingBounds = getAndMockBounds(orientation)
+        val startingPoint = getEdgeStartingPoint(
+            testCase.ctrlType, testCase.additionalEdgeCtrlType, startingBounds)
+
+        decoratedTaskPositioner.onDragPositioningStart(
+            testCase.ctrlType, startingPoint.x, startingPoint.y)
+
+        decoratedTaskPositioner.onDragPositioningEnd(
+            startingPoint.x + testCase.dragDelta.x,
+            startingPoint.y + testCase.dragDelta.y)
+
+        val adjustedDragDelta = calculateAdjustedDelta(
+            testCase.ctrlType + testCase.additionalEdgeCtrlType,
+            testCase.dragDelta,
+            orientation)
+        val capturedValues = getLatestOnEndArguments()
+        val absChangeX = abs(capturedValues.x - startingPoint.x)
+        val absChangeY = abs(capturedValues.y - startingPoint.y)
+        val resultAspectRatio = max(absChangeX, absChangeY) / min(absChangeX, absChangeY)
+        assertThat(capturedValues.x).isEqualTo(startingPoint.x + adjustedDragDelta.x)
+        assertThat(capturedValues.y).isEqualTo(startingPoint.y + adjustedDragDelta.y)
+        assertThat(resultAspectRatio).isEqualTo(STARTING_ASPECT_RATIO)
+    }
+
+    /**
+     * Returns the most recent arguments passed to the `.onPositioningStart()` of the
+     * [mockTaskPositioner].
+     */
+    private fun getLatestOnStartArguments(): CtrlCoordinateCapture {
+        val captorCtrlType = argumentCaptor<Int>()
+        val captorCoordinates = argumentCaptor<Float>()
+        verify(mockTaskPositioner).onDragPositioningStart(
+            captorCtrlType.capture(), captorCoordinates.capture(), captorCoordinates.capture())
+
+        return CtrlCoordinateCapture(captorCtrlType.firstValue, captorCoordinates.firstValue,
+            captorCoordinates.secondValue)
+    }
+
+    /**
+     * Returns the most recent arguments passed to the `.onPositioningMove()` of the
+     * [mockTaskPositioner].
+     */
+    private fun getLatestOnMoveArguments(): PointF {
+        val captorCoordinates = argumentCaptor<Float>()
+        verify(mockTaskPositioner).onDragPositioningMove(
+            captorCoordinates.capture(), captorCoordinates.capture())
+
+        return PointF(captorCoordinates.firstValue, captorCoordinates.secondValue)
+    }
+
+    /**
+     * Returns the most recent arguments passed to the `.onPositioningEnd()` of the
+     * [mockTaskPositioner].
+     */
+    private fun getLatestOnEndArguments(): PointF {
+        val captorCoordinates = argumentCaptor<Float>()
+        verify(mockTaskPositioner).onDragPositioningEnd(
+            captorCoordinates.capture(), captorCoordinates.capture())
+
+        return PointF(captorCoordinates.firstValue, captorCoordinates.secondValue)
+    }
+
+    /**
+     * Mocks the app bounds to correspond with a given orientation and returns the mocked bounds.
+     */
+    private fun getAndMockBounds(orientation: Orientation): Rect {
+        val mockBounds = if (orientation.isPortrait) PORTRAIT_BOUNDS else LANDSCAPE_BOUNDS
+        doReturn(mockBounds).`when`(mockTaskPositioner).onDragPositioningStart(
+            any(), any(), any())
+        doReturn(mockBounds).`when`(decoratedTaskPositioner).getBounds(any())
+        return mockBounds
+    }
+
+    /**
+     * Calculates the corner point a given drag action should start from, based on the [ctrlType],
+     * given the [startingBounds].
+     */
+    private fun getCornerStartingPoint(@CtrlType ctrlType: Int, startingBounds: Rect): PointF {
+        return when (ctrlType) {
+            CTRL_TYPE_BOTTOM + CTRL_TYPE_RIGHT ->
+                PointF(startingBounds.right.toFloat(), startingBounds.bottom.toFloat())
+
+            CTRL_TYPE_BOTTOM + CTRL_TYPE_LEFT ->
+                PointF(startingBounds.left.toFloat(), startingBounds.bottom.toFloat())
+
+            CTRL_TYPE_TOP + CTRL_TYPE_RIGHT ->
+                PointF(startingBounds.right.toFloat(), startingBounds.top.toFloat())
+            // CTRL_TYPE_TOP + CTRL_TYPE_LEFT
+            else ->
+                PointF(startingBounds.left.toFloat(), startingBounds.top.toFloat())
+        }
+    }
+
+    /**
+     * Calculates the point along an edge the edge resize should start from, based on the starting
+     * edge ([edgeCtrlType]) and the additional edge we expect to resize ([additionalEdgeCtrlType]),
+     * given the [startingBounds].
+     */
+    private fun getEdgeStartingPoint(
+        @CtrlType edgeCtrlType: Int, @CtrlType additionalEdgeCtrlType: Int, startingBounds: Rect
+    ): PointF {
+        val simulatedCorner = getCornerStartingPoint(
+            edgeCtrlType + additionalEdgeCtrlType, startingBounds)
+        when (additionalEdgeCtrlType) {
+            CTRL_TYPE_TOP -> {
+                simulatedCorner.offset(0f, -SMALL_DELTA)
+                return simulatedCorner
+            }
+            CTRL_TYPE_BOTTOM -> {
+                simulatedCorner.offset(0f, SMALL_DELTA)
+                return simulatedCorner
+            }
+            CTRL_TYPE_LEFT -> {
+                simulatedCorner.offset(SMALL_DELTA, 0f)
+                return simulatedCorner
+            }
+            // CTRL_TYPE_RIGHT
+            else -> {
+                simulatedCorner.offset(-SMALL_DELTA, 0f)
+                return simulatedCorner
+            }
+        }
+    }
+
+    /**
+     * Calculates the adjustments to the drag delta we expect for a given action and orientation.
+     */
+    private fun calculateAdjustedDelta(
+        @CtrlType ctrlType: Int, delta: PointF, orientation: Orientation
+    ): PointF {
+        if ((abs(delta.x) < abs(delta.y) && delta.x != 0f) || delta.y == 0f) {
+            // Only respect x delta if it's less than y delta but non-zero (i.e there is a change
+            // in x to be applied), or if the y delta is zero (i.e there is no change in y to be
+            // applied).
+            val adjustedY = if (orientation.isPortrait)
+                delta.x * STARTING_ASPECT_RATIO else
+                delta.x / STARTING_ASPECT_RATIO
+            if (ctrlType.isBottomRightOrTopLeftCorner()) {
+                return PointF(delta.x, adjustedY)
+            }
+            return PointF(delta.x, -adjustedY)
+        }
+        // Respect y delta.
+        val adjustedX = if (orientation.isPortrait)
+            delta.y / STARTING_ASPECT_RATIO else
+            delta.y * STARTING_ASPECT_RATIO
+        if (ctrlType.isBottomRightOrTopLeftCorner()) {
+            return PointF(adjustedX, delta.y)
+        }
+        return PointF(-adjustedX, delta.y)
+    }
+
+    private fun @receiver:CtrlType Int.isBottomRightOrTopLeftCorner(): Boolean {
+        return this == CTRL_TYPE_BOTTOM + CTRL_TYPE_RIGHT || this == CTRL_TYPE_TOP + CTRL_TYPE_LEFT
+    }
+
+    private inner class CtrlCoordinateCapture(ctrl: Int, xValue: Float, yValue: Float) {
+        var ctrlType = ctrl
+        var x = xValue
+        var y = yValue
+    }
+
+    companion object {
+        private val PORTRAIT_BOUNDS = Rect(100, 100, 200, 400)
+        private val LANDSCAPE_BOUNDS = Rect(100, 100, 400, 200)
+        private val STARTING_ASPECT_RATIO = PORTRAIT_BOUNDS.height() / PORTRAIT_BOUNDS.width()
+        private const val LARGE_DELTA = 50f
+        private const val SMALL_DELTA = 30f
+
+        enum class Orientation(
+            val isPortrait: Boolean
+        ) {
+            PORTRAIT (true),
+            LANDSCAPE (false)
+        }
+
+        enum class ResizeableOrNotResizingTestCases(
+            val ctrlType: Int,
+            val isResizeable: Boolean
+        ) {
+            NotResizing (CTRL_TYPE_UNDEFINED, false),
+            Resizeable (CTRL_TYPE_RIGHT, true)
+        }
+
+        /**
+         * Tests cases for the start of a corner resize.
+         * @param ctrlType the control type of the corner the resize is initiated on.
+         */
+        enum class CornerResizeStartTestCases(
+            val ctrlType: Int
+        ) {
+            BottomRightCorner (CTRL_TYPE_BOTTOM + CTRL_TYPE_RIGHT),
+            BottomLeftCorner (CTRL_TYPE_BOTTOM + CTRL_TYPE_LEFT),
+            TopRightCorner (CTRL_TYPE_TOP + CTRL_TYPE_RIGHT),
+            TopLeftCorner (CTRL_TYPE_TOP + CTRL_TYPE_LEFT)
+        }
+
+        /**
+         * Tests cases for the moving and ending of a invalid corner resize. Where the compass point
+         * (e.g `SouthEast`) represents the direction of the drag.
+         * @param ctrlType the control type of the corner the resize is initiated on.
+         * @param dragDelta the delta of the attempted drag action, from the [ctrlType]'s
+         * corresponding corner point. Represented as a combination a different signed small and
+         * large deltas which correspond to the direction/angle of drag.
+         */
+        enum class InvalidCornerResizeTestCases(
+            val ctrlType: Int,
+            val dragDelta: PointF
+        ) {
+            BottomRightCornerNorthEastDrag (
+                CTRL_TYPE_BOTTOM + CTRL_TYPE_RIGHT,
+                PointF(LARGE_DELTA, -LARGE_DELTA)),
+            BottomRightCornerSouthWestDrag (
+                CTRL_TYPE_BOTTOM + CTRL_TYPE_RIGHT,
+                PointF(-LARGE_DELTA, LARGE_DELTA)),
+            TopLeftCornerNorthEastDrag (
+                CTRL_TYPE_TOP + CTRL_TYPE_LEFT,
+                PointF(LARGE_DELTA, -LARGE_DELTA)),
+            TopLeftCornerSouthWestDrag (
+                CTRL_TYPE_TOP + CTRL_TYPE_LEFT,
+                PointF(-LARGE_DELTA, LARGE_DELTA)),
+            BottomLeftCornerSouthEastDrag (
+                CTRL_TYPE_BOTTOM + CTRL_TYPE_LEFT,
+                PointF(LARGE_DELTA, LARGE_DELTA)),
+            BottomLeftCornerNorthWestDrag (
+                CTRL_TYPE_BOTTOM + CTRL_TYPE_LEFT,
+                PointF(-LARGE_DELTA, -LARGE_DELTA)),
+            TopRightCornerSouthEastDrag (
+                CTRL_TYPE_TOP + CTRL_TYPE_RIGHT,
+                PointF(LARGE_DELTA, LARGE_DELTA)),
+            TopRightCornerNorthWestDrag (
+                CTRL_TYPE_TOP + CTRL_TYPE_RIGHT,
+                PointF(-LARGE_DELTA, -LARGE_DELTA)),
+        }
+
+        /**
+         * Tests cases for the moving and ending of a valid corner resize. Where the compass point
+         * (e.g `SouthEast`) represents the direction of the drag, followed by the expected
+         * behaviour in that direction (i.e `RespectY` means the y delta will be respected whereas
+         * `RespectX` means the x delta will be respected).
+         * @param ctrlType the control type of the corner the resize is initiated on.
+         * @param dragDelta the delta of the attempted drag action, from the [ctrlType]'s
+         * corresponding corner point. Represented as a combination a different signed small and
+         * large deltas which correspond to the direction/angle of drag.
+         */
+        enum class ValidCornerResizeTestCases(
+            val ctrlType: Int,
+            val dragDelta: PointF,
+        ) {
+            BottomRightCornerSouthEastDragRespectY (
+                CTRL_TYPE_BOTTOM + CTRL_TYPE_RIGHT,
+                PointF(+LARGE_DELTA, SMALL_DELTA)),
+            BottomRightCornerSouthEastDragRespectX (
+                CTRL_TYPE_BOTTOM + CTRL_TYPE_RIGHT,
+                PointF(SMALL_DELTA, LARGE_DELTA)),
+            BottomRightCornerNorthWestDragRespectY (
+                CTRL_TYPE_BOTTOM + CTRL_TYPE_RIGHT,
+                PointF(-LARGE_DELTA, -SMALL_DELTA)),
+            BottomRightCornerNorthWestDragRespectX (
+                CTRL_TYPE_BOTTOM + CTRL_TYPE_RIGHT,
+                PointF(-SMALL_DELTA, -LARGE_DELTA)),
+            TopLeftCornerSouthEastDragRespectY (
+                CTRL_TYPE_TOP + CTRL_TYPE_LEFT,
+                PointF(LARGE_DELTA, SMALL_DELTA)),
+            TopLeftCornerSouthEastDragRespectX (
+                CTRL_TYPE_TOP + CTRL_TYPE_LEFT,
+                PointF(SMALL_DELTA, LARGE_DELTA)),
+            TopLeftCornerNorthWestDragRespectY (
+                CTRL_TYPE_TOP + CTRL_TYPE_LEFT,
+                PointF(-LARGE_DELTA, -SMALL_DELTA)),
+            TopLeftCornerNorthWestDragRespectX (
+                CTRL_TYPE_TOP + CTRL_TYPE_LEFT,
+                PointF(-SMALL_DELTA, -LARGE_DELTA)),
+            BottomLeftCornerSouthWestDragRespectY (
+                CTRL_TYPE_BOTTOM + CTRL_TYPE_LEFT,
+                PointF(-LARGE_DELTA, SMALL_DELTA)),
+            BottomLeftCornerSouthWestDragRespectX (
+                CTRL_TYPE_BOTTOM + CTRL_TYPE_LEFT,
+                PointF(-SMALL_DELTA, LARGE_DELTA)),
+            BottomLeftCornerNorthEastDragRespectY (
+                CTRL_TYPE_BOTTOM + CTRL_TYPE_LEFT,
+                PointF(LARGE_DELTA, -SMALL_DELTA)),
+            BottomLeftCornerNorthEastDragRespectX (
+                CTRL_TYPE_BOTTOM + CTRL_TYPE_LEFT,
+                PointF(SMALL_DELTA, -LARGE_DELTA)),
+            TopRightCornerSouthWestDragRespectY (
+                CTRL_TYPE_TOP + CTRL_TYPE_RIGHT,
+                PointF(-LARGE_DELTA, SMALL_DELTA)),
+            TopRightCornerSouthWestDragRespectX (
+                CTRL_TYPE_TOP + CTRL_TYPE_RIGHT,
+                PointF(-SMALL_DELTA, LARGE_DELTA)),
+            TopRightCornerNorthEastDragRespectY (
+                CTRL_TYPE_TOP + CTRL_TYPE_RIGHT,
+                PointF(LARGE_DELTA, -SMALL_DELTA)),
+            TopRightCornerNorthEastDragRespectX (
+                CTRL_TYPE_TOP + CTRL_TYPE_RIGHT,
+                PointF(+SMALL_DELTA, -LARGE_DELTA))
+        }
+
+        /**
+         * Tests cases for the start of an edge resize.
+         * @param ctrlType the control type of the edge the resize is initiated on.
+         * @param additionalEdgeCtrlType the expected additional edge to be included in the ctrl
+         * type.
+         */
+        enum class EdgeResizeStartTestCases(
+            val ctrlType: Int,
+            val additionalEdgeCtrlType: Int
+        ) {
+            BottomOfLeftEdgeResize (CTRL_TYPE_LEFT, CTRL_TYPE_BOTTOM),
+            TopOfLeftEdgeResize (CTRL_TYPE_LEFT, CTRL_TYPE_TOP),
+            BottomOfRightEdgeResize (CTRL_TYPE_RIGHT, CTRL_TYPE_BOTTOM),
+            TopOfRightEdgeResize (CTRL_TYPE_RIGHT, CTRL_TYPE_TOP),
+            RightOfTopEdgeResize (CTRL_TYPE_TOP, CTRL_TYPE_RIGHT),
+            LeftOfTopEdgeResize (CTRL_TYPE_TOP, CTRL_TYPE_LEFT),
+            RightOfBottomEdgeResize (CTRL_TYPE_BOTTOM, CTRL_TYPE_RIGHT),
+            LeftOfBottomEdgeResize (CTRL_TYPE_BOTTOM, CTRL_TYPE_LEFT)
+        }
+
+        /**
+         * Tests cases for the moving and ending of an edge resize.
+         * @param ctrlType the control type of the edge the resize is initiated on.
+         * @param additionalEdgeCtrlType the expected additional edge to be included in the ctrl
+         * type.
+         * @param dragDelta the delta of the attempted drag action, from the [ctrlType]'s
+         * corresponding edge point. Represented as a combination a different signed small and
+         * large deltas which correspond to the direction/angle of drag.
+         */
+        enum class EdgeResizeTestCases(
+            val ctrlType: Int,
+            val additionalEdgeCtrlType: Int,
+            val dragDelta: PointF
+        ) {
+            BottomOfLeftEdgeResize (CTRL_TYPE_LEFT, CTRL_TYPE_BOTTOM, PointF(-SMALL_DELTA, 0f)),
+            TopOfLeftEdgeResize (CTRL_TYPE_LEFT, CTRL_TYPE_TOP, PointF(-SMALL_DELTA, 0f)),
+            BottomOfRightEdgeResize (CTRL_TYPE_RIGHT, CTRL_TYPE_BOTTOM, PointF(SMALL_DELTA, 0f)),
+            TopOfRightEdgeResize (CTRL_TYPE_RIGHT, CTRL_TYPE_TOP, PointF(SMALL_DELTA, 0f)),
+            RightOfTopEdgeResize (CTRL_TYPE_TOP, CTRL_TYPE_RIGHT, PointF(0f, -SMALL_DELTA)),
+            LeftOfTopEdgeResize (CTRL_TYPE_TOP, CTRL_TYPE_LEFT, PointF(0f, -SMALL_DELTA)),
+            RightOfBottomEdgeResize (CTRL_TYPE_BOTTOM, CTRL_TYPE_RIGHT, PointF(0f, SMALL_DELTA)),
+            LeftOfBottomEdgeResize (CTRL_TYPE_BOTTOM, CTRL_TYPE_LEFT, PointF(0f, SMALL_DELTA))
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
index 3a3e965..7543fed 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
@@ -121,6 +121,7 @@
             displayId = DISPLAY_ID
             configuration.windowConfiguration.setBounds(STARTING_BOUNDS)
             configuration.windowConfiguration.displayRotation = ROTATION_90
+            isResizeable = true
         }
         `when`(mockWindowDecoration.calculateValidDragArea()).thenReturn(VALID_DRAG_AREA)
         mockWindowDecoration.mDisplay = mockDisplay
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
index 100abbb..a845231 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
@@ -236,11 +236,11 @@
         val handleMenu = HandleMenu(mockDesktopWindowDecoration,
             WindowManagerWrapper(mockWindowManager),
             layoutId, appIcon, appName, splitScreenController, shouldShowWindowingPill = true,
-            shouldShowNewWindowButton = true,
+            shouldShowNewWindowButton = true, shouldShowManageWindowsButton = false,
             null /* openInBrowserLink */, captionWidth = HANDLE_WIDTH, captionHeight = 50,
             captionX = captionX
         )
-        handleMenu.show(mock(), mock(), mock(), mock(), mock(), mock(), mock())
+        handleMenu.show(mock(), mock(), mock(), mock(), mock(), mock(), mock(), mock())
         return handleMenu
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
index 6ae16ed..7784af6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
@@ -141,6 +141,7 @@
             displayId = DISPLAY_ID
             configuration.windowConfiguration.setBounds(STARTING_BOUNDS)
             configuration.windowConfiguration.displayRotation = ROTATION_90
+            isResizeable = true
         }
         `when`(mockDesktopWindowDecoration.calculateValidDragArea()).thenReturn(VALID_DRAG_AREA)
         mockDesktopWindowDecoration.mDisplay = mockDisplay
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 2e117ac..7252b32 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
@@ -85,6 +85,8 @@
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.tests.R;
 import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewContainer;
+import com.android.wm.shell.windowdecor.viewhost.WindowDecorViewHost;
+import com.android.wm.shell.windowdecor.viewhost.WindowDecorViewHostSupplier;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -128,6 +130,10 @@
     @Mock
     private SurfaceControlViewHost mMockSurfaceControlViewHost;
     @Mock
+    private WindowDecorViewHostSupplier mMockWindowDecorViewHostSupplier;
+    @Mock
+    private WindowDecorViewHost mMockWindowDecorViewHost;
+    @Mock
     private AttachedSurfaceControl mMockRootSurfaceControl;
     @Mock
     private TestView mMockView;
@@ -167,6 +173,9 @@
         when(mMockSurfaceControlViewHost.getRootSurfaceControl())
                 .thenReturn(mMockRootSurfaceControl);
         when(mMockView.findViewById(anyInt())).thenReturn(mMockView);
+        when(mMockWindowDecorViewHostSupplier.acquire(any(), any()))
+                .thenReturn(mMockWindowDecorViewHost);
+        when(mMockWindowDecorViewHost.getSurfaceControl()).thenReturn(mock(SurfaceControl.class));
 
         // Add status bar inset so that WindowDecoration does not think task is in immersive mode
         mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, statusBars()).setVisible(true);
@@ -230,10 +239,6 @@
         final SurfaceControl.Builder decorContainerSurfaceBuilder =
                 createMockSurfaceControlBuilder(decorContainerSurface);
         mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
-        final SurfaceControl captionContainerSurface = mock(SurfaceControl.class);
-        final SurfaceControl.Builder captionContainerSurfaceBuilder =
-                createMockSurfaceControlBuilder(captionContainerSurface);
-        mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder);
 
         final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
                 .setDisplayId(Display.DEFAULT_DISPLAY)
@@ -254,18 +259,18 @@
         verify(mMockSurfaceControlStartT).setTrustedOverlay(decorContainerSurface, true);
         verify(mMockSurfaceControlStartT).setWindowCrop(decorContainerSurface, 300, 100);
 
-        verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
-        verify(captionContainerSurfaceBuilder).setContainerLayer();
+        final SurfaceControl captionContainerSurface = mMockWindowDecorViewHost.getSurfaceControl();
+        verify(mMockSurfaceControlStartT).reparent(captionContainerSurface, decorContainerSurface);
         verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64);
         verify(mMockSurfaceControlStartT).show(captionContainerSurface);
 
-        verify(mMockSurfaceControlViewHostFactory).create(any(), eq(defaultDisplay), any());
-
-        verify(mMockSurfaceControlViewHost)
-                .setView(same(mMockView),
-                        argThat(lp -> lp.height == 64
-                                && lp.width == 300
-                                && (lp.flags & LayoutParams.FLAG_NOT_FOCUSABLE) != 0));
+        verify(mMockWindowDecorViewHost).updateView(
+                same(mMockView),
+                argThat(lp -> lp.height == 64
+                        && lp.width == 300
+                        && (lp.flags & LayoutParams.FLAG_NOT_FOCUSABLE) != 0),
+                eq(taskInfo.configuration),
+                eq(null) /* onDrawTransaction */);
         verify(mMockView).setTaskFocusState(true);
         verify(mMockWindowContainerTransaction).addInsetsSource(
                 eq(taskInfo.token),
@@ -296,10 +301,6 @@
         final SurfaceControl.Builder decorContainerSurfaceBuilder =
                 createMockSurfaceControlBuilder(decorContainerSurface);
         mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
-        final SurfaceControl captionContainerSurface = mock(SurfaceControl.class);
-        final SurfaceControl.Builder captionContainerSurfaceBuilder =
-                createMockSurfaceControlBuilder(captionContainerSurface);
-        mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder);
 
         final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
         mMockSurfaceControlTransactions.add(t);
@@ -322,7 +323,7 @@
 
         windowDecor.relayout(taskInfo);
 
-        verify(mMockSurfaceControlViewHost, never()).release();
+        verify(mMockWindowDecorViewHost, never()).release(any());
         verify(t, never()).apply();
         verify(mMockWindowContainerTransaction, never())
                 .removeInsetsSource(eq(taskInfo.token), any(), anyInt(), anyInt());
@@ -332,9 +333,8 @@
         taskInfo.isVisible = false;
         windowDecor.relayout(taskInfo);
 
-        final InOrder releaseOrder = inOrder(t2, mMockSurfaceControlViewHost);
-        releaseOrder.verify(mMockSurfaceControlViewHost).release();
-        releaseOrder.verify(t2).remove(captionContainerSurface);
+        final InOrder releaseOrder = inOrder(t2, mMockWindowDecorViewHostSupplier);
+        releaseOrder.verify(mMockWindowDecorViewHostSupplier).release(mMockWindowDecorViewHost, t2);
         releaseOrder.verify(t2).remove(decorContainerSurface);
         releaseOrder.verify(t2).apply();
         // Expect to remove two insets sources, the caption insets and the mandatory gesture insets.
@@ -382,8 +382,8 @@
         verify(mMockDisplayController).removeDisplayWindowListener(same(listener));
 
         assertThat(mRelayoutResult.mRootView).isSameInstanceAs(mMockView);
-        verify(mMockSurfaceControlViewHostFactory).create(any(), eq(mockDisplay), any());
-        verify(mMockSurfaceControlViewHost).setView(same(mMockView), any());
+        verify(mMockWindowDecorViewHostSupplier).acquire(any(), eq(mockDisplay));
+        verify(mMockWindowDecorViewHost).updateView(same(mMockView), any(), any(), any());
     }
 
     @Test
@@ -396,10 +396,6 @@
         final SurfaceControl.Builder decorContainerSurfaceBuilder =
                 createMockSurfaceControlBuilder(decorContainerSurface);
         mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
-        final SurfaceControl captionContainerSurface = mock(SurfaceControl.class);
-        final SurfaceControl.Builder captionContainerSurfaceBuilder =
-                createMockSurfaceControlBuilder(captionContainerSurface);
-        mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder);
 
         final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
         mMockSurfaceControlTransactions.add(t);
@@ -436,8 +432,7 @@
                 windowDecor.mDecorWindowContext.getResources(), mRelayoutParams.mCaptionHeightId);
         verify(mMockSurfaceControlAddWindowT).setWindowCrop(additionalWindowSurface, width, height);
         verify(mMockSurfaceControlAddWindowT).show(additionalWindowSurface);
-        verify(mMockSurfaceControlViewHostFactory, Mockito.times(2))
-                .create(any(), eq(defaultDisplay), any());
+        verify(mMockSurfaceControlViewHostFactory).create(any(), eq(defaultDisplay), any());
     }
 
     @Test
@@ -450,10 +445,6 @@
         final SurfaceControl.Builder decorContainerSurfaceBuilder =
                 createMockSurfaceControlBuilder(decorContainerSurface);
         mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
-        final SurfaceControl captionContainerSurface = mock(SurfaceControl.class);
-        final SurfaceControl.Builder captionContainerSurfaceBuilder =
-                createMockSurfaceControlBuilder(captionContainerSurface);
-        mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder);
 
         final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
         mMockSurfaceControlTransactions.add(t);
@@ -473,8 +464,8 @@
 
         windowDecor.relayout(taskInfo);
 
-        verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
-        verify(captionContainerSurfaceBuilder).setContainerLayer();
+        final SurfaceControl captionContainerSurface = mMockWindowDecorViewHost.getSurfaceControl();
+        verify(mMockSurfaceControlStartT).reparent(captionContainerSurface, decorContainerSurface);
         // Width of the captionContainerSurface should match the width of TASK_BOUNDS
         verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64);
         verify(mMockSurfaceControlStartT).show(captionContainerSurface);
@@ -490,10 +481,6 @@
         final SurfaceControl.Builder decorContainerSurfaceBuilder =
                 createMockSurfaceControlBuilder(decorContainerSurface);
         mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
-        final SurfaceControl captionContainerSurface = mock(SurfaceControl.class);
-        final SurfaceControl.Builder captionContainerSurfaceBuilder =
-                createMockSurfaceControlBuilder(captionContainerSurface);
-        mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder);
 
         final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
         mMockSurfaceControlTransactions.add(t);
@@ -511,9 +498,11 @@
         taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
         final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
 
-        windowDecor.relayout(taskInfo, true /* applyStartTransactionOnDraw */);
+        mRelayoutParams.mApplyStartTransactionOnDraw = true;
+        windowDecor.relayout(taskInfo);
 
-        verify(mMockRootSurfaceControl).applyTransactionOnDraw(mMockSurfaceControlStartT);
+        verify(mMockWindowDecorViewHost).updateView(any(), any(), any(),
+                eq(mMockSurfaceControlStartT));
     }
 
     @Test
@@ -867,37 +856,52 @@
     }
 
     @Test
-    public void updateViewHost_applyTransactionOnDrawIsTrue_surfaceControlIsUpdated() {
+    public void relayout_applyTransactionOnDrawIsTrue_updatesViewWithDrawTransaction() {
         final TestWindowDecoration windowDecor = createWindowDecoration(
-                new TestRunningTaskInfoBuilder().build());
+                new TestRunningTaskInfoBuilder()
+                        .setVisible(true)
+                        .setWindowingMode(WINDOWING_MODE_FREEFORM)
+                        .build());
         mRelayoutParams.mApplyStartTransactionOnDraw = true;
         mRelayoutResult.mRootView = mMockView;
 
-        windowDecor.updateViewHost(mRelayoutParams, mMockSurfaceControlStartT, mRelayoutResult);
+        windowDecor.relayout(windowDecor.mTaskInfo);
 
-        verify(mMockRootSurfaceControl).applyTransactionOnDraw(mMockSurfaceControlStartT);
+        verify(mMockWindowDecorViewHost)
+                .updateView(eq(mRelayoutResult.mRootView), any(),
+                        eq(windowDecor.mTaskInfo.configuration), eq(mMockSurfaceControlStartT));
     }
 
     @Test
-    public void updateViewHost_nullDrawTransaction_applyTransactionOnDrawIsTrue_throwsException() {
+    public void relayout_applyTransactionOnDrawIsTrue_asyncViewHostRendering_throwsException() {
         final TestWindowDecoration windowDecor = createWindowDecoration(
-                new TestRunningTaskInfoBuilder().build());
+                new TestRunningTaskInfoBuilder()
+                        .setVisible(true)
+                        .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+                        .build());
         mRelayoutParams.mApplyStartTransactionOnDraw = true;
+        mRelayoutParams.mAsyncViewHost = true;
         mRelayoutResult.mRootView = mMockView;
 
         assertThrows(IllegalArgumentException.class,
-                () -> windowDecor.updateViewHost(
-                        mRelayoutParams, null /* onDrawTransaction */, mRelayoutResult));
+                () -> windowDecor.relayout(windowDecor.mTaskInfo));
     }
 
     @Test
-    public void updateViewHost_nullDrawTransaction_applyTransactionOnDrawIsFalse_doesNotThrow() {
+    public void relayout_asyncViewHostRendering() {
         final TestWindowDecoration windowDecor = createWindowDecoration(
-                new TestRunningTaskInfoBuilder().build());
-        mRelayoutParams.mApplyStartTransactionOnDraw = false;
+                new TestRunningTaskInfoBuilder()
+                        .setVisible(true)
+                        .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+                        .build());
+        mRelayoutParams.mAsyncViewHost = true;
         mRelayoutResult.mRootView = mMockView;
 
-        windowDecor.updateViewHost(mRelayoutParams, null /* onDrawTransaction */, mRelayoutResult);
+        windowDecor.relayout(windowDecor.mTaskInfo);
+
+        verify(mMockWindowDecorViewHost)
+                .updateViewAsync(eq(mRelayoutResult.mRootView), any(),
+                        eq(windowDecor.mTaskInfo.configuration));
     }
 
     @Test
@@ -997,7 +1001,8 @@
                 new MockObjectSupplier<>(mMockSurfaceControlTransactions,
                         () -> mock(SurfaceControl.Transaction.class)),
                 () -> mMockWindowContainerTransaction, () -> mMockTaskSurface,
-                mMockSurfaceControlViewHostFactory);
+                mMockSurfaceControlViewHostFactory,
+                mMockWindowDecorViewHostSupplier);
     }
 
     private class MockObjectSupplier<T> implements Supplier<T> {
@@ -1037,16 +1042,20 @@
                 Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
                 Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
                 Supplier<SurfaceControl> surfaceControlSupplier,
-                SurfaceControlViewHostFactory surfaceControlViewHostFactory) {
+                SurfaceControlViewHostFactory surfaceControlViewHostFactory,
+                @NonNull WindowDecorViewHostSupplier windowDecorViewHostSupplier) {
             super(context, userContext, displayController, taskOrganizer, taskInfo, taskSurface,
                     surfaceControlBuilderSupplier, surfaceControlTransactionSupplier,
                     windowContainerTransactionSupplier, surfaceControlSupplier,
-                    surfaceControlViewHostFactory);
+                    surfaceControlViewHostFactory, windowDecorViewHostSupplier);
         }
 
         @Override
         void relayout(ActivityManager.RunningTaskInfo taskInfo) {
-            relayout(taskInfo, false /* applyStartTransactionOnDraw */);
+            mRelayoutParams.mRunningTaskInfo = taskInfo;
+            mRelayoutParams.mLayoutResId = R.layout.caption_layout;
+            relayout(mRelayoutParams, mMockSurfaceControlStartT, mMockSurfaceControlFinishT,
+                    mMockWindowContainerTransaction, mMockView, mRelayoutResult);
         }
 
         @Override
@@ -1067,15 +1076,6 @@
             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);
-        }
-
         private AdditionalViewContainer addTestViewContainer() {
             final Resources resources = mDecorWindowContext.getResources();
             final int width = loadDimensionPixelSize(resources, mCaptionMenuWidthId);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHostTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHostTest.kt
new file mode 100644
index 0000000..1b2ce9e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHostTest.kt
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.viewhost
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.SurfaceControl
+import android.view.SurfaceControlViewHost
+import android.view.View
+import android.view.WindowManager
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertThrows
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
+
+
+/**
+ * Tests for [DefaultWindowDecorViewHost].
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:DefaultWindowDecorViewHostTest
+ */
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+class DefaultWindowDecorViewHostTest : ShellTestCase() {
+
+    @Test
+    fun updateView_layoutInViewHost() = runTest {
+        val windowDecorViewHost = createDefaultViewHost()
+        val view = View(context)
+
+        windowDecorViewHost.updateView(
+            view = view,
+            attrs = WindowManager.LayoutParams(100, 100),
+            configuration = context.resources.configuration,
+            onDrawTransaction = null
+        )
+
+        assertThat(windowDecorViewHost.viewHost).isNotNull()
+        assertThat(windowDecorViewHost.viewHost!!.view).isEqualTo(view)
+    }
+
+    @Test
+    fun updateView_alreadyLaidOut_relayouts() = runTest {
+        val windowDecorViewHost = createDefaultViewHost()
+        val view = View(context)
+        windowDecorViewHost.updateView(
+            view = view,
+            attrs = WindowManager.LayoutParams(100, 100),
+            configuration = context.resources.configuration,
+            onDrawTransaction = null
+        )
+
+        val otherParams = WindowManager.LayoutParams(200, 200)
+        windowDecorViewHost.updateView(
+            view = view,
+            attrs = otherParams,
+            configuration = context.resources.configuration,
+            onDrawTransaction = null
+        )
+
+        assertThat(windowDecorViewHost.viewHost!!.view).isEqualTo(view)
+        assertThat(windowDecorViewHost.viewHost!!.view!!.layoutParams.width)
+            .isEqualTo(otherParams.width)
+    }
+
+    @Test
+    fun updateView_replacingView_throws() = runTest {
+        val windowDecorViewHost = createDefaultViewHost()
+        val view = View(context)
+        windowDecorViewHost.updateView(
+            view = view,
+            attrs = WindowManager.LayoutParams(100, 100),
+            configuration = context.resources.configuration,
+            onDrawTransaction = null
+        )
+
+        val otherView = View(context)
+        assertThrows(Exception::class.java) {
+            windowDecorViewHost.updateView(
+                view = otherView,
+                attrs = WindowManager.LayoutParams(100, 100),
+                configuration = context.resources.configuration,
+                onDrawTransaction = null
+            )
+        }
+    }
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    @Test
+    fun updateView_clearsPendingAsyncJob() = runTest {
+        val windowDecorViewHost = createDefaultViewHost()
+        val asyncView = View(context)
+        val syncView = View(context)
+        val asyncAttrs = WindowManager.LayoutParams(100, 100)
+        val syncAttrs = WindowManager.LayoutParams(200, 200)
+
+        windowDecorViewHost.updateViewAsync(
+            view = asyncView,
+            attrs = asyncAttrs,
+            configuration = context.resources.configuration,
+        )
+
+        // No view host yet, since the coroutine hasn't run.
+        assertThat(windowDecorViewHost.viewHost).isNull()
+
+        windowDecorViewHost.updateView(
+            view = syncView,
+            attrs = syncAttrs,
+            configuration = context.resources.configuration,
+            onDrawTransaction = null
+        )
+
+        // Would run coroutine if it hadn't been cancelled.
+        advanceUntilIdle()
+
+        assertThat(windowDecorViewHost.viewHost).isNotNull()
+        assertThat(windowDecorViewHost.viewHost!!.view).isNotNull()
+        // View host view/attrs should match the ones from the sync call, plus, since the
+        // sync/async were made with different views, if the job hadn't been cancelled there
+        // would've been an exception thrown as replacing views isn't allowed.
+        assertThat(windowDecorViewHost.viewHost!!.view).isEqualTo(syncView)
+        assertThat(windowDecorViewHost.viewHost!!.view!!.layoutParams.width)
+            .isEqualTo(syncAttrs.width)
+    }
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    @Test
+    fun updateViewAsync() = runTest {
+        val windowDecorViewHost = createDefaultViewHost()
+        val view = View(context)
+        val attrs = WindowManager.LayoutParams(100, 100)
+
+        windowDecorViewHost.updateViewAsync(
+            view = view,
+            attrs = attrs,
+            configuration = context.resources.configuration,
+        )
+
+        assertThat(windowDecorViewHost.viewHost).isNull()
+
+        advanceUntilIdle()
+
+        assertThat(windowDecorViewHost.viewHost).isNotNull()
+    }
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    @Test
+    fun updateViewAsync_clearsPendingAsyncJob() = runTest {
+        val windowDecorViewHost = createDefaultViewHost()
+
+        val view = View(context)
+        windowDecorViewHost.updateViewAsync(
+            view = view,
+            attrs = WindowManager.LayoutParams(100, 100),
+            configuration = context.resources.configuration,
+        )
+        val otherView = View(context)
+        windowDecorViewHost.updateViewAsync(
+            view = otherView,
+            attrs = WindowManager.LayoutParams(100, 100),
+            configuration = context.resources.configuration,
+        )
+
+        advanceUntilIdle()
+
+        assertThat(windowDecorViewHost.viewHost).isNotNull()
+        assertThat(windowDecorViewHost.viewHost!!.view).isNotNull()
+        assertThat(windowDecorViewHost.viewHost!!.view).isEqualTo(otherView)
+    }
+
+    @Test
+    fun release() = runTest {
+        val windowDecorViewHost = createDefaultViewHost()
+
+        val view = View(context)
+        windowDecorViewHost.updateView(
+            view = view,
+            attrs = WindowManager.LayoutParams(100, 100),
+            configuration = context.resources.configuration,
+            onDrawTransaction = null
+        )
+
+        val t = mock(SurfaceControl.Transaction::class.java)
+        windowDecorViewHost.release(t)
+
+        verify(windowDecorViewHost.viewHost!!).release()
+        verify(t).remove(windowDecorViewHost.surfaceControl)
+    }
+
+    private fun CoroutineScope.createDefaultViewHost() = DefaultWindowDecorViewHost(
+        context = context,
+        mainScope = this,
+        display = context.display,
+        surfaceControlViewHostFactory = { c, d, wwm, s ->
+            spy(SurfaceControlViewHost(c, d, wwm, s))
+        }
+    )
+}
\ No newline at end of file
diff --git a/libs/appfunctions/OWNERS b/libs/appfunctions/OWNERS
new file mode 100644
index 0000000..c093675
--- /dev/null
+++ b/libs/appfunctions/OWNERS
@@ -0,0 +1,3 @@
+set noparent
+
+include /core/java/android/app/appfunctions/OWNERS
diff --git a/libs/hwui/apex/android_bitmap.cpp b/libs/hwui/apex/android_bitmap.cpp
index c80a9b4..000f109 100644
--- a/libs/hwui/apex/android_bitmap.cpp
+++ b/libs/hwui/apex/android_bitmap.cpp
@@ -14,21 +14,21 @@
  * limitations under the License.
  */
 
-#include <log/log.h>
-
-#include "android/graphics/bitmap.h"
-#include "TypeCast.h"
-#include "GraphicsJNI.h"
-
+#include <Gainmap.h>
 #include <GraphicsJNI.h>
-#include <hwui/Bitmap.h>
 #include <SkBitmap.h>
 #include <SkColorSpace.h>
 #include <SkImageInfo.h>
 #include <SkRefCnt.h>
 #include <SkStream.h>
+#include <hwui/Bitmap.h>
+#include <log/log.h>
 #include <utils/Color.h>
 
+#include "GraphicsJNI.h"
+#include "TypeCast.h"
+#include "android/graphics/bitmap.h"
+
 using namespace android;
 
 ABitmap* ABitmap_acquireBitmapFromJava(JNIEnv* env, jobject bitmapObj) {
@@ -215,6 +215,14 @@
 int ABitmap_compress(const AndroidBitmapInfo* info, ADataSpace dataSpace, const void* pixels,
                      AndroidBitmapCompressFormat inFormat, int32_t quality, void* userContext,
                      AndroidBitmap_CompressWriteFunc fn) {
+    return ABitmap_compressWithGainmap(info, dataSpace, pixels, nullptr, -1.f, inFormat, quality,
+                                       userContext, fn);
+}
+
+int ABitmap_compressWithGainmap(const AndroidBitmapInfo* info, ADataSpace dataSpace,
+                                const void* pixels, const void* gainmapPixels, float hdrSdrRatio,
+                                AndroidBitmapCompressFormat inFormat, int32_t quality,
+                                void* userContext, AndroidBitmap_CompressWriteFunc fn) {
     Bitmap::JavaCompressFormat format;
     switch (inFormat) {
         case ANDROID_BITMAP_COMPRESS_FORMAT_JPEG:
@@ -275,7 +283,7 @@
         // besides ADATASPACE_UNKNOWN as an error?
         cs = nullptr;
     } else {
-        cs = uirenderer::DataSpaceToColorSpace((android_dataspace) dataSpace);
+        cs = uirenderer::DataSpaceToColorSpace((android_dataspace)dataSpace);
         // DataSpaceToColorSpace treats UNKNOWN as SRGB, but compress forces the
         // client to specify SRGB if that is what they want.
         if (!cs || dataSpace == ADATASPACE_UNKNOWN) {
@@ -292,16 +300,70 @@
 
     auto imageInfo =
             SkImageInfo::Make(info->width, info->height, colorType, alphaType, std::move(cs));
-    SkBitmap bitmap;
-    // We are not going to modify the pixels, but installPixels expects them to
-    // not be const, since for all it knows we might want to draw to the SkBitmap.
-    if (!bitmap.installPixels(imageInfo, const_cast<void*>(pixels), info->stride)) {
-        return ANDROID_BITMAP_RESULT_BAD_PARAMETER;
+
+    // Validate the image info
+    {
+        SkBitmap tempBitmap;
+        if (!tempBitmap.installPixels(imageInfo, const_cast<void*>(pixels), info->stride)) {
+            return ANDROID_BITMAP_RESULT_BAD_PARAMETER;
+        }
+    }
+
+    SkPixelRef pixelRef =
+            SkPixelRef(info->width, info->height, const_cast<void*>(pixels), info->stride);
+
+    auto bitmap = Bitmap::createFrom(imageInfo, pixelRef);
+
+    if (gainmapPixels) {
+        auto gainmap = sp<uirenderer::Gainmap>::make();
+        gainmap->info.fGainmapRatioMin = {
+                1.f,
+                1.f,
+                1.f,
+                1.f,
+        };
+        gainmap->info.fGainmapRatioMax = {
+                hdrSdrRatio,
+                hdrSdrRatio,
+                hdrSdrRatio,
+                1.f,
+        };
+        gainmap->info.fGainmapGamma = {
+                1.f,
+                1.f,
+                1.f,
+                1.f,
+        };
+
+        static constexpr auto kDefaultEpsilon = 1.f / 64.f;
+        gainmap->info.fEpsilonSdr = {
+                kDefaultEpsilon,
+                kDefaultEpsilon,
+                kDefaultEpsilon,
+                1.f,
+        };
+        gainmap->info.fEpsilonHdr = {
+                kDefaultEpsilon,
+                kDefaultEpsilon,
+                kDefaultEpsilon,
+                1.f,
+        };
+        gainmap->info.fDisplayRatioSdr = 1.f;
+        gainmap->info.fDisplayRatioHdr = hdrSdrRatio;
+
+        SkPixelRef gainmapPixelRef = SkPixelRef(info->width, info->height,
+                                                const_cast<void*>(gainmapPixels), info->stride);
+        auto gainmapBitmap = Bitmap::createFrom(imageInfo, gainmapPixelRef);
+        gainmap->bitmap = std::move(gainmapBitmap);
+        bitmap->setGainmap(std::move(gainmap));
     }
 
     CompressWriter stream(userContext, fn);
-    return Bitmap::compress(bitmap, format, quality, &stream) ? ANDROID_BITMAP_RESULT_SUCCESS
-                                                              : ANDROID_BITMAP_RESULT_JNI_EXCEPTION;
+
+    return bitmap->compress(format, quality, &stream)
+
+                   ? ANDROID_BITMAP_RESULT_SUCCESS
+                   : ANDROID_BITMAP_RESULT_JNI_EXCEPTION;
 }
 
 AHardwareBuffer* ABitmap_getHardwareBuffer(ABitmap* bitmapHandle) {
diff --git a/libs/hwui/apex/include/android/graphics/bitmap.h b/libs/hwui/apex/include/android/graphics/bitmap.h
index 8c4b439d..6f65e9e 100644
--- a/libs/hwui/apex/include/android/graphics/bitmap.h
+++ b/libs/hwui/apex/include/android/graphics/bitmap.h
@@ -65,6 +65,13 @@
 ANDROID_API int ABitmap_compress(const AndroidBitmapInfo* info, ADataSpace dataSpace, const void* pixels,
                      AndroidBitmapCompressFormat format, int32_t quality, void* userContext,
                      AndroidBitmap_CompressWriteFunc);
+// If gainmapPixels is null, then no gainmap is encoded, and hdrSdrRatio is
+// unused
+ANDROID_API int ABitmap_compressWithGainmap(const AndroidBitmapInfo* info, ADataSpace dataSpace,
+                                            const void* pixels, const void* gainmapPixels,
+                                            float hdrSdrRatio, AndroidBitmapCompressFormat format,
+                                            int32_t quality, void* userContext,
+                                            AndroidBitmap_CompressWriteFunc);
 /**
  *  Retrieve the native object associated with a HARDWARE Bitmap.
  *
diff --git a/libs/hwui/libhwui.map.txt b/libs/hwui/libhwui.map.txt
index d03ceb4..2414299 100644
--- a/libs/hwui/libhwui.map.txt
+++ b/libs/hwui/libhwui.map.txt
@@ -13,6 +13,7 @@
     ABitmapConfig_getFormatFromConfig;
     ABitmapConfig_getConfigFromFormat;
     ABitmap_compress;
+    ABitmap_compressWithGainmap;
     ABitmap_getHardwareBuffer;
     ACanvas_isSupportedPixelFormat;
     ACanvas_getNativeHandleFromJava;
diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig
index bbd91fc..dcf5c5b 100644
--- a/location/java/android/location/flags/location.aconfig
+++ b/location/java/android/location/flags/location.aconfig
@@ -116,4 +116,7 @@
     description: "Flag for enabling NI SUPL message injection by carrier config"
     bug: "242105192"
     is_fixed_read_only: true
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
 }
diff --git a/media/TEST_MAPPING b/media/TEST_MAPPING
index 6ac9695..e52e0b1 100644
--- a/media/TEST_MAPPING
+++ b/media/TEST_MAPPING
@@ -30,24 +30,14 @@
       "file_patterns": ["(?i)drm|crypto"]
     },
     {
-      "name": "CtsMediaDrmFrameworkTestCases",
-      "options" : [
-        {
-          "include-annotation": "android.platform.test.annotations.Presubmit"
-        }
-      ],
+      "name": "CtsMediaDrmFrameworkTestCases_Presubmit",
       "file_patterns": ["(?i)drm|crypto"]
     },
     {
       "file_patterns": [
         "[^/]*(LoudnessCodec)[^/]*\\.java"
       ],
-      "name": "LoudnessCodecApiTest",
-      "options": [
-        {
-          "include-annotation": "android.platform.test.annotations.Presubmit"
-        }
-      ]
+      "name": "LoudnessCodecApiTest_Presubmit"
     }
   ]
 }
diff --git a/media/java/android/media/SoundPool.java b/media/java/android/media/SoundPool.java
index f33a744..7b9ff23 100644
--- a/media/java/android/media/SoundPool.java
+++ b/media/java/android/media/SoundPool.java
@@ -26,6 +26,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.ParcelFileDescriptor;
+import android.os.Trace;
 import android.util.AndroidRuntimeException;
 import android.util.Log;
 
@@ -314,8 +315,13 @@
     public final int play(int soundID, float leftVolume, float rightVolume,
             int priority, int loop, float rate) {
         // FIXME: b/174876164 implement device id for soundpool
-        baseStart(0);
-        return _play(soundID, leftVolume, rightVolume, priority, loop, rate, getPlayerIId());
+        try {
+            Trace.traceBegin(Trace.TRACE_TAG_AUDIO, "SoundPool.play");
+            baseStart(0);
+            return _play(soundID, leftVolume, rightVolume, priority, loop, rate, getPlayerIId());
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_AUDIO);
+        }
     }
 
     /**
diff --git a/media/java/android/media/projection/OWNERS b/media/java/android/media/projection/OWNERS
index 880ec8f..2b72744 100644
--- a/media/java/android/media/projection/OWNERS
+++ b/media/java/android/media/projection/OWNERS
@@ -1,7 +1,7 @@
 # Bug component: 1345447
 
-michaelwr@google.com
-santoscordon@google.com
-chaviw@google.com
-nmusgrave@google.com
+marvinramin@google.com
 dakinola@google.com
+vaniadesmonda@google.com
+caen@google.com
+santoscordon@google.com
\ No newline at end of file
diff --git a/media/java/android/media/projection/TEST_MAPPING b/media/java/android/media/projection/TEST_MAPPING
index 7aa9118..b33097c 100644
--- a/media/java/android/media/projection/TEST_MAPPING
+++ b/media/java/android/media/projection/TEST_MAPPING
@@ -1,15 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "MediaProjectionTests",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        }
-      ]
+      "name": "MediaProjectionTests"
     }
   ]
 }
diff --git a/media/jni/soundpool/SoundPool.cpp b/media/jni/soundpool/SoundPool.cpp
index 9f32a83..4c0b8d0 100644
--- a/media/jni/soundpool/SoundPool.cpp
+++ b/media/jni/soundpool/SoundPool.cpp
@@ -21,6 +21,9 @@
 #include <algorithm>
 #include <thread>
 
+#define ATRACE_TAG ATRACE_TAG_AUDIO
+#include <utils/Trace.h>
+
 #include "SoundPool.h"
 
 namespace android
@@ -135,8 +138,10 @@
         return 0;
     }
 
+    ATRACE_BEGIN("SoundPool::play (native)");
     const int32_t streamID = mStreamManager.queueForPlay(
             sound, soundID, leftVolume, rightVolume, priority, loop, rate, playerIId);
+    ATRACE_END();
     ALOGV("%s returned %d", __func__, streamID);
 
     return streamID;
diff --git a/mime/java-res/android.mime.types b/mime/java-res/android.mime.types
index 5cf807d..17cdee4 100644
--- a/mime/java-res/android.mime.types
+++ b/mime/java-res/android.mime.types
@@ -132,6 +132,7 @@
 # Optional additions that should not override any previous mapping.
 
 ?application/x-wifi-config ?xml
+?multipart/related mht
 
 # Special cases where Android has a strong opinion about mappings, so we
 # define them very last and make them override in both directions (no "?").
diff --git a/native/android/TEST_MAPPING b/native/android/TEST_MAPPING
index 7c71098..be84574 100644
--- a/native/android/TEST_MAPPING
+++ b/native/android/TEST_MAPPING
@@ -14,12 +14,7 @@
        "file_patterns": ["permission_manager.cpp"]
     },
     {
-       "name": "CtsOsTestCases",
-       "options": [
-           {
-              "include-filter": "android.os.cts.PerformanceHintManagerTest"
-           }
-       ],
+       "name": "CtsOsTestCases_cts_performancehintmanagertest",
        "file_patterns": ["performance_hint.cpp"]
     }
   ],
diff --git a/native/webview/TEST_MAPPING b/native/webview/TEST_MAPPING
index 07f4383..3858059 100644
--- a/native/webview/TEST_MAPPING
+++ b/native/webview/TEST_MAPPING
@@ -1,28 +1,13 @@
 {
   "presubmit": [
     {
-      "name": "CtsWebkitTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsWebkitTestCases"
     },
     {
-      "name": "CtsSdkSandboxWebkitTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsSdkSandboxWebkitTestCases"
     },
     {
-      "name": "CtsHostsideWebViewTests",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsHostsideWebViewTests"
     },
     {
       "name": "GtsWebViewTestCases",
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index 5b6b6c0..e7cb76c 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -205,6 +205,7 @@
     method public int getSelectionModeForCategory(String);
     method public boolean isDefaultServiceForAid(android.content.ComponentName, String);
     method public boolean isDefaultServiceForCategory(android.content.ComponentName, String);
+    method @FlaggedApi("android.nfc.enable_card_emulation_euicc") public boolean isEuiccSupported();
     method public boolean registerAidsForService(android.content.ComponentName, String, java.util.List<java.lang.String>);
     method @FlaggedApi("android.nfc.nfc_read_polling_loop") public boolean registerPollingLoopFilterForService(@NonNull android.content.ComponentName, @NonNull String, boolean);
     method @FlaggedApi("android.nfc.nfc_read_polling_loop") public boolean registerPollingLoopPatternFilterForService(@NonNull android.content.ComponentName, @NonNull String, boolean);
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index cc5ff81..bc8a7af 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -98,7 +98,6 @@
   public final class CardEmulation {
     method @FlaggedApi("android.permission.flags.wallet_role_enabled") @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public static android.content.ComponentName getPreferredPaymentService(@NonNull android.content.Context);
     method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<android.nfc.cardemulation.ApduServiceInfo> getServices(@NonNull String, int);
-    method @FlaggedApi("android.nfc.enable_card_emulation_euicc") public boolean isEuiccSupported();
     method @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public void overrideRoutingTable(@NonNull android.app.Activity, int, int);
     method @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public void recoverRoutingTable(@NonNull android.app.Activity);
   }
diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java
index a72a896..83ad32c 100644
--- a/nfc/java/android/nfc/cardemulation/CardEmulation.java
+++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java
@@ -992,9 +992,7 @@
      * Is EUICC supported as a Secure Element EE which supports off host card emulation.
      *
      * @return true if the device supports EUICC for off host card emulation, false otherwise.
-     * @hide
      */
-    @SystemApi
     @FlaggedApi(android.nfc.Flags.FLAG_ENABLE_CARD_EMULATION_EUICC)
     public boolean isEuiccSupported() {
         return callServiceReturn(() -> sService.isEuiccSupported(), false);
diff --git a/packages/PrintSpooler/TEST_MAPPING b/packages/PrintSpooler/TEST_MAPPING
index ad3b44f..f8bf8a0 100644
--- a/packages/PrintSpooler/TEST_MAPPING
+++ b/packages/PrintSpooler/TEST_MAPPING
@@ -1,12 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "CtsPrintTestCases",
-      "options": [
-        {
-          "include-annotation": "android.platform.test.annotations.Presubmit"
-        }
-      ]
+      "name": "CtsPrintTestCases_Presubmit"
     }
   ],
   "postsubmit": [
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index b997c35..0cb85d8 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -42,8 +42,6 @@
         "SettingsLibIllustrationPreference",
         "SettingsLibLayoutPreference",
         "SettingsLibMainSwitchPreference",
-        "SettingsLibMetadata",
-        "SettingsLibPreference",
         "SettingsLibProfileSelector",
         "SettingsLibProgressBar",
         "SettingsLibRestrictedLockUtils",
diff --git a/packages/SettingsLib/Graph/Android.bp b/packages/SettingsLib/Graph/Android.bp
index e2ed1e4..163b689 100644
--- a/packages/SettingsLib/Graph/Android.bp
+++ b/packages/SettingsLib/Graph/Android.bp
@@ -4,7 +4,7 @@
 
 filegroup {
     name: "SettingsLibGraph-srcs",
-    srcs: ["src/**/*"],
+    srcs: ["src/**/*.kt"],
 }
 
 android_library {
@@ -14,8 +14,24 @@
     ],
     srcs: [":SettingsLibGraph-srcs"],
     static_libs: [
+        "SettingsLibGraph-proto-lite",
+        "SettingsLibIpc",
+        "SettingsLibMetadata",
+        "SettingsLibPreference",
         "androidx.annotation_annotation",
+        "androidx.fragment_fragment",
         "androidx.preference_preference",
     ],
     kotlincflags: ["-Xjvm-default=all"],
 }
+
+java_library {
+    name: "SettingsLibGraph-proto-lite",
+    srcs: ["graph.proto"],
+    proto: {
+        type: "lite",
+        canonical_path_from_root: false,
+    },
+    sdk_version: "core_current",
+    static_libs: ["libprotobuf-java-lite"],
+}
diff --git a/packages/SettingsLib/Graph/graph.proto b/packages/SettingsLib/Graph/graph.proto
new file mode 100644
index 0000000..e93d756
--- /dev/null
+++ b/packages/SettingsLib/Graph/graph.proto
@@ -0,0 +1,156 @@
+syntax = "proto3";
+
+package com.android.settingslib.graph;
+
+option java_package = "com.android.settingslib.graph.proto";
+option java_multiple_files = true;
+
+// Proto represents preference graph.
+message PreferenceGraphProto {
+  // Preference screens appear in the graph.
+  // Key: preference key of the PreferenceScreen. Value: PreferenceScreen.
+  map<string, PreferenceScreenProto> screens = 1;
+  // Roots of the graph.
+  // Each element is a preference key of the PreferenceScreen.
+  repeated string roots = 2;
+  // Activities appear in the graph.
+  // Key: activity class. Value: preference key of associated PreferenceScreen.
+  map<string, string> activity_screens = 3;
+}
+
+// Proto of PreferenceScreen.
+message PreferenceScreenProto {
+  // Intent to show the PreferenceScreen.
+  optional IntentProto intent = 1;
+  // Root of the PreferenceScreen hierarchy.
+  optional PreferenceGroupProto root = 2;
+  // If the preference screen provides complete hierarchy by source code.
+  optional bool complete_hierarchy = 3;
+}
+
+// Proto of PreferenceGroup.
+message PreferenceGroupProto {
+  // Self information of PreferenceGroup.
+  optional PreferenceProto preference = 1;
+  // A list of children.
+  repeated PreferenceOrGroupProto preferences = 2;
+}
+
+// Proto represents either PreferenceProto or PreferenceGroupProto.
+message PreferenceOrGroupProto {
+  oneof kind {
+    // It is a Preference.
+    PreferenceProto preference = 1;
+    // It is a PreferenceGroup.
+    PreferenceGroupProto group = 2;
+  }
+}
+
+// Proto of Preference.
+message PreferenceProto {
+  // Key of the preference.
+  optional string key = 1;
+  // Title of the preference.
+  optional TextProto title = 2;
+  // Summary of the preference.
+  optional TextProto summary = 3;
+  // Icon of the preference.
+  optional int32 icon = 4;
+  // Additional keywords for indexing.
+  optional int32 keywords = 5;
+  // Extras of the preference.
+  optional BundleProto extras = 6;
+  // Whether the preference is indexable.
+  optional bool indexable = 7;
+  // Whether the preference is enabled.
+  optional bool enabled = 8;
+  // Whether the preference is available/visible.
+  optional bool available = 9;
+  // Whether the preference is persistent.
+  optional bool persistent = 10;
+  // Whether the preference is restricted by managed configurations.
+  optional bool restricted = 11;
+  // Target of the preference action.
+  optional ActionTarget action_target = 12;
+  // Preference value (if present, it means `persistent` is true).
+  optional PreferenceValueProto value = 13;
+
+  // Target of an Intent
+  message ActionTarget {
+    oneof kind {
+      // Resolved key of the preference screen located in current app.
+      // This is resolved from android:fragment or activity of current app.
+      string key = 1;
+      // Unresolvable Intent that is either an unrecognized activity of current
+      // app or activity belongs to other app.
+      IntentProto intent = 2;
+    }
+  }
+}
+
+// Proto of string or string resource id.
+message TextProto {
+  oneof text {
+    int32 resource_id = 1;
+    string string = 2;
+  }
+}
+
+// Proto of preference value.
+message PreferenceValueProto {
+  oneof value {
+    bool boolean_value = 1;
+  }
+}
+
+// Proto of android.content.Intent
+message IntentProto {
+  // The action of the Intent.
+  optional string action = 1;
+
+  // The data attribute of the Intent, expressed as a URI.
+  optional string data = 2;
+
+  // The package attribute of the Intent, which may be set to force the
+  // detection of a particular application package that can handle the event.
+  optional string pkg = 3;
+
+  // The component attribute of the Intent, which may be set to force the
+  // detection of a particular component (app). If present, this must be a
+  // package name followed by a '/' and then followed by the class name.
+  optional string component = 4;
+
+  // Flags controlling how intent is handled. The value must be bitwise OR of
+  // intent flag constants defined by Android.
+  // http://developer.android.com/reference/android/content/Intent.html#setFlags(int)
+  optional int32 flags = 5;
+
+  // Extended data from the intent.
+  optional BundleProto extras = 6;
+
+  // The MIME type of the Intent (e.g. "text/plain").
+  //
+  // For more information, see
+  // https://developer.android.com/reference/android/content/Intent#setType(java.lang.String).
+  optional string mime_type = 7;
+}
+
+// Proto of android.os.Bundle
+message BundleProto {
+  // Bundle data.
+  map<string, BundleValue> values = 1;
+
+  message BundleValue {
+    // Bundle data value for the associated key name.
+    // Can be extended to support other types of bundled data.
+    oneof value {
+      string string_value = 1;
+      bytes bytes_value = 2;
+      int32 int_value = 3;
+      int64 long_value = 4;
+      bool boolean_value = 5;
+      double double_value = 6;
+      BundleProto bundle_value = 7;
+    }
+  }
+}
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt
new file mode 100644
index 0000000..04c2968
--- /dev/null
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.graph
+
+import android.app.Application
+import android.os.Bundle
+import com.android.settingslib.graph.proto.PreferenceGraphProto
+import com.android.settingslib.ipc.ApiHandler
+import com.android.settingslib.ipc.MessageCodec
+import java.util.Locale
+
+/** API to get preference graph. */
+abstract class GetPreferenceGraphApiHandler(private val activityClasses: Set<String>) :
+    ApiHandler<GetPreferenceGraphRequest, PreferenceGraphProto> {
+
+    override val requestCodec: MessageCodec<GetPreferenceGraphRequest>
+        get() = GetPreferenceGraphRequestCodec
+
+    override val responseCodec: MessageCodec<PreferenceGraphProto>
+        get() = PreferenceGraphProtoCodec
+
+    override suspend fun invoke(
+        application: Application,
+        myUid: Int,
+        callingUid: Int,
+        request: GetPreferenceGraphRequest,
+    ): PreferenceGraphProto {
+        val builderRequest =
+            if (request.activityClasses.isEmpty()) {
+                GetPreferenceGraphRequest(activityClasses, request.visitedScreens, request.locale)
+            } else {
+                request
+            }
+        return PreferenceGraphBuilder.of(application, builderRequest).build()
+    }
+}
+
+/**
+ * Request of [GetPreferenceGraphApiHandler].
+ *
+ * @param activityClasses activities of the preference graph
+ * @param visitedScreens keys of the visited preference screen
+ * @param locale locale of the preference graph
+ */
+data class GetPreferenceGraphRequest
+@JvmOverloads
+constructor(
+    val activityClasses: Set<String> = setOf(),
+    val visitedScreens: Set<String> = setOf(),
+    val locale: Locale? = null,
+    val includeValue: Boolean = true,
+)
+
+object GetPreferenceGraphRequestCodec : MessageCodec<GetPreferenceGraphRequest> {
+    override fun encode(data: GetPreferenceGraphRequest): Bundle =
+        Bundle(3).apply {
+            putStringArray(KEY_ACTIVITIES, data.activityClasses.toTypedArray())
+            putStringArray(KEY_PREF_KEYS, data.visitedScreens.toTypedArray())
+            putString(KEY_LOCALE, data.locale?.toLanguageTag())
+        }
+
+    override fun decode(data: Bundle): GetPreferenceGraphRequest {
+        val activities = data.getStringArray(KEY_ACTIVITIES) ?: arrayOf()
+        val visitedScreens = data.getStringArray(KEY_PREF_KEYS) ?: arrayOf()
+        fun String?.toLocale() = if (this != null) Locale.forLanguageTag(this) else null
+        return GetPreferenceGraphRequest(
+            activities.toSet(),
+            visitedScreens.toSet(),
+            data.getString(KEY_LOCALE).toLocale(),
+        )
+    }
+
+    private const val KEY_ACTIVITIES = "activities"
+    private const val KEY_PREF_KEYS = "keys"
+    private const val KEY_LOCALE = "locale"
+}
+
+object PreferenceGraphProtoCodec : MessageCodec<PreferenceGraphProto> {
+    override fun encode(data: PreferenceGraphProto): Bundle =
+        Bundle(1).apply { putByteArray(KEY_GRAPH, data.toByteArray()) }
+
+    override fun decode(data: Bundle): PreferenceGraphProto =
+        PreferenceGraphProto.parseFrom(data.getByteArray(KEY_GRAPH)!!)
+
+    private const val KEY_GRAPH = "graph"
+}
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
new file mode 100644
index 0000000..8c5d877
--- /dev/null
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
@@ -0,0 +1,445 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress("DEPRECATION")
+
+package com.android.settingslib.graph
+
+import android.annotation.SuppressLint
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.content.res.Configuration
+import android.os.Build
+import android.os.Bundle
+import android.preference.PreferenceActivity
+import android.util.Log
+import androidx.fragment.app.Fragment
+import androidx.preference.Preference
+import androidx.preference.PreferenceGroup
+import androidx.preference.PreferenceScreen
+import androidx.preference.TwoStatePreference
+import com.android.settingslib.graph.proto.PreferenceGraphProto
+import com.android.settingslib.graph.proto.PreferenceGroupProto
+import com.android.settingslib.graph.proto.PreferenceProto
+import com.android.settingslib.graph.proto.PreferenceProto.ActionTarget
+import com.android.settingslib.graph.proto.PreferenceScreenProto
+import com.android.settingslib.graph.proto.TextProto
+import com.android.settingslib.metadata.BooleanValue
+import com.android.settingslib.metadata.PersistentPreference
+import com.android.settingslib.metadata.PreferenceAvailabilityProvider
+import com.android.settingslib.metadata.PreferenceHierarchy
+import com.android.settingslib.metadata.PreferenceHierarchyNode
+import com.android.settingslib.metadata.PreferenceMetadata
+import com.android.settingslib.metadata.PreferenceRestrictionProvider
+import com.android.settingslib.metadata.PreferenceScreenBindingKeyProvider
+import com.android.settingslib.metadata.PreferenceScreenMetadata
+import com.android.settingslib.metadata.PreferenceScreenRegistry
+import com.android.settingslib.metadata.PreferenceSummaryProvider
+import com.android.settingslib.metadata.PreferenceTitleProvider
+import com.android.settingslib.preference.PreferenceScreenFactory
+import com.android.settingslib.preference.PreferenceScreenProvider
+import java.util.Locale
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+
+private const val TAG = "PreferenceGraphBuilder"
+
+/**
+ * Builder of preference graph.
+ *
+ * Only activity in current application is supported. To create preference graph across
+ * applications, use [crawlPreferenceGraph].
+ */
+class PreferenceGraphBuilder
+private constructor(private val context: Context, private val request: GetPreferenceGraphRequest) {
+    private val preferenceScreenFactory by lazy {
+        PreferenceScreenFactory(context.ofLocale(request.locale))
+    }
+    private val builder by lazy { PreferenceGraphProto.newBuilder() }
+    private val visitedScreens = mutableSetOf<String>().apply { addAll(request.visitedScreens) }
+    private val includeValue = request.includeValue
+
+    private suspend fun init() {
+        for (activityClass in request.activityClasses) {
+            add(activityClass)
+        }
+    }
+
+    fun build() = builder.build()
+
+    /** Adds an activity to the graph. */
+    suspend fun <T> add(activityClass: Class<T>) where T : Activity, T : PreferenceScreenProvider =
+        addPreferenceScreenProvider(activityClass)
+
+    /**
+     * Adds an activity to the graph.
+     *
+     * Reflection is used to create the instance. To avoid security vulnerability, the code ensures
+     * given [activityClassName] must be declared as an <activity> entry in AndroidManifest.xml.
+     */
+    suspend fun add(activityClassName: String) {
+        try {
+            val intent = Intent()
+            intent.setClassName(context, activityClassName)
+            if (context.packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) ==
+                null) {
+                Log.e(TAG, "$activityClassName is not activity")
+                return
+            }
+            val activityClass = context.classLoader.loadClass(activityClassName)
+            if (addPreferenceScreenKeyProvider(activityClass)) return
+            if (PreferenceScreenProvider::class.java.isAssignableFrom(activityClass)) {
+                addPreferenceScreenProvider(activityClass)
+            } else {
+                Log.w(TAG, "$activityClass does not implement PreferenceScreenProvider")
+            }
+        } catch (e: Exception) {
+            Log.e(TAG, "Fail to add $activityClassName", e)
+        }
+    }
+
+    private suspend fun addPreferenceScreenKeyProvider(activityClass: Class<*>): Boolean {
+        if (!PreferenceScreenBindingKeyProvider::class.java.isAssignableFrom(activityClass)) {
+            return false
+        }
+        val key = getPreferenceScreenKey { activityClass.newInstance() } ?: return false
+        if (addPreferenceScreenFromRegistry(key, activityClass)) {
+            builder.addRoots(key)
+            return true
+        }
+        return false
+    }
+
+    private suspend fun getPreferenceScreenKey(newInstance: () -> Any): String? =
+        withContext(Dispatchers.Main) {
+            try {
+                val instance = newInstance()
+                if (instance is PreferenceScreenBindingKeyProvider) {
+                    return@withContext instance.getPreferenceScreenBindingKey(context)
+                } else {
+                    Log.w(TAG, "$instance is not PreferenceScreenKeyProvider")
+                }
+            } catch (e: Exception) {
+                Log.e(TAG, "getPreferenceScreenKey failed", e)
+            }
+            null
+        }
+
+    private suspend fun addPreferenceScreenFromRegistry(
+        key: String,
+        activityClass: Class<*>,
+    ): Boolean {
+        val metadata = PreferenceScreenRegistry[key] ?: return false
+        if (!metadata.hasCompleteHierarchy()) return false
+        return addPreferenceScreenMetadata(metadata, activityClass)
+    }
+
+    private suspend fun addPreferenceScreenMetadata(
+        metadata: PreferenceScreenMetadata,
+        activityClass: Class<*>,
+    ): Boolean =
+        addPreferenceScreen(metadata.key, activityClass) {
+            preferenceScreenProto {
+                completeHierarchy = true
+                root = metadata.getPreferenceHierarchy(context).toProto(activityClass, true)
+            }
+        }
+
+    private suspend fun addPreferenceScreenProvider(activityClass: Class<*>) {
+        Log.d(TAG, "add $activityClass")
+        createPreferenceScreen { activityClass.newInstance() }
+            ?.let {
+                addPreferenceScreen(Intent(context, activityClass), activityClass, it)
+                builder.addRoots(it.key)
+            }
+    }
+
+    /**
+     * Creates [PreferenceScreen].
+     *
+     * Androidx Activity/Fragment instance must be created in main thread, otherwise an exception is
+     * raised.
+     */
+    private suspend fun createPreferenceScreen(newInstance: () -> Any): PreferenceScreen? =
+        withContext(Dispatchers.Main) {
+            try {
+                val instance = newInstance()
+                Log.d(TAG, "createPreferenceScreen $instance")
+                if (instance is PreferenceScreenProvider) {
+                    return@withContext instance.createPreferenceScreen(preferenceScreenFactory)
+                } else {
+                    Log.w(TAG, "$instance is not PreferenceScreenProvider")
+                }
+            } catch (e: Exception) {
+                Log.e(TAG, "createPreferenceScreen failed", e)
+            }
+            return@withContext null
+        }
+
+    private suspend fun addPreferenceScreen(
+        intent: Intent,
+        activityClass: Class<*>,
+        preferenceScreen: PreferenceScreen?,
+    ) {
+        val key = preferenceScreen?.key
+        if (key.isNullOrEmpty()) {
+            Log.e(TAG, "$activityClass \"$preferenceScreen\" has no key")
+            return
+        }
+        @Suppress("CheckReturnValue")
+        addPreferenceScreen(key, activityClass) { preferenceScreen.toProto(intent, activityClass) }
+    }
+
+    private suspend fun addPreferenceScreen(
+        key: String,
+        activityClass: Class<*>,
+        preferenceScreenProvider: suspend () -> PreferenceScreenProto,
+    ): Boolean {
+        if (!visitedScreens.add(key)) {
+            Log.w(TAG, "$activityClass $key visited")
+            return false
+        }
+        val activityClassName = activityClass.name
+        val associatedKey = builder.getActivityScreensOrDefault(activityClassName, null)
+        if (associatedKey == null) {
+            builder.putActivityScreens(activityClassName, key)
+        } else if (associatedKey != key) {
+            Log.w(TAG, "Dup $activityClassName association, old: $associatedKey, new: $key")
+        }
+        builder.putScreens(key, preferenceScreenProvider())
+        return true
+    }
+
+    private suspend fun PreferenceScreen.toProto(
+        intent: Intent,
+        activityClass: Class<*>,
+    ): PreferenceScreenProto = preferenceScreenProto {
+        this.intent = intent.toProto()
+        root = (this@toProto as PreferenceGroup).toProto(activityClass)
+    }
+
+    private suspend fun PreferenceGroup.toProto(activityClass: Class<*>): PreferenceGroupProto =
+        preferenceGroupProto {
+            preference = (this@toProto as Preference).toProto(activityClass)
+            for (index in 0 until preferenceCount) {
+                val child = getPreference(index)
+                addPreferences(
+                    preferenceOrGroupProto {
+                        if (child is PreferenceGroup) {
+                            group = child.toProto(activityClass)
+                        } else {
+                            preference = child.toProto(activityClass)
+                        }
+                    })
+            }
+        }
+
+    private suspend fun Preference.toProto(activityClass: Class<*>): PreferenceProto =
+        preferenceProto {
+            this@toProto.key?.let { key = it }
+            this@toProto.title?.let { title = textProto { string = it.toString() } }
+            this@toProto.summary?.let { summary = textProto { string = it.toString() } }
+            val preferenceExtras = peekExtras()
+            preferenceExtras?.let { extras = it.toProto() }
+            enabled = isEnabled
+            available = isVisible
+            persistent = isPersistent
+            if (includeValue && isPersistent && this@toProto is TwoStatePreference) {
+                value = preferenceValueProto { booleanValue = this@toProto.isChecked }
+            }
+            this@toProto.fragment.toActionTarget(activityClass, preferenceExtras)?.let {
+                actionTarget = it
+                return@preferenceProto
+            }
+            this@toProto.intent?.let { actionTarget = it.toActionTarget() }
+        }
+
+    private suspend fun PreferenceHierarchy.toProto(
+        activityClass: Class<*>,
+        isRoot: Boolean,
+    ): PreferenceGroupProto = preferenceGroupProto {
+        preference = toProto(this@toProto, activityClass, isRoot)
+        forEachAsync {
+            addPreferences(
+                preferenceOrGroupProto {
+                    if (it is PreferenceHierarchy) {
+                        group = it.toProto(activityClass, false)
+                    } else {
+                        preference = toProto(it, activityClass, false)
+                    }
+                })
+        }
+    }
+
+    private suspend fun toProto(
+        node: PreferenceHierarchyNode,
+        activityClass: Class<*>,
+        isRoot: Boolean,
+    ) = preferenceProto {
+        val metadata = node.metadata
+        key = metadata.key
+        metadata.getTitleTextProto(isRoot)?.let { title = it }
+        if (metadata.summary != 0) {
+            summary = textProto { resourceId = metadata.summary }
+        } else {
+            (metadata as? PreferenceSummaryProvider)?.getSummary(context)?.let {
+                summary = textProto { string = it.toString() }
+            }
+        }
+        if (metadata.icon != 0) icon = metadata.icon
+        if (metadata.keywords != 0) keywords = metadata.keywords
+        val preferenceExtras = metadata.extras(context)
+        preferenceExtras?.let { extras = it.toProto() }
+        indexable = metadata.isIndexable(context)
+        enabled = metadata.isEnabled(context)
+        if (metadata is PreferenceAvailabilityProvider) {
+            available = metadata.isAvailable(context)
+        }
+        if (metadata is PreferenceRestrictionProvider) {
+            restricted = metadata.isRestricted(context)
+        }
+        persistent = metadata.isPersistent(context)
+        if (includeValue &&
+            persistent &&
+            metadata is BooleanValue &&
+            metadata is PersistentPreference<*>) {
+            metadata.storage(context).getValue(metadata.key, Boolean::class.javaObjectType)?.let {
+                value = preferenceValueProto { booleanValue = it }
+            }
+        }
+        if (metadata is PreferenceScreenMetadata) {
+            if (metadata.hasCompleteHierarchy()) {
+                @Suppress("CheckReturnValue") addPreferenceScreenMetadata(metadata, activityClass)
+            } else {
+                metadata.fragmentClass()?.toActionTarget(activityClass, preferenceExtras)?.let {
+                    actionTarget = it
+                }
+            }
+        }
+        metadata.intent(context)?.let { actionTarget = it.toActionTarget() }
+    }
+
+    private fun PreferenceMetadata.getTitleTextProto(isRoot: Boolean): TextProto? {
+        if (isRoot && this is PreferenceScreenMetadata) {
+            val titleRes = screenTitle
+            if (titleRes != 0) {
+                return textProto { resourceId = titleRes }
+            } else {
+                getScreenTitle(context)?.let {
+                    return textProto { string = it.toString() }
+                }
+            }
+        } else {
+            val titleRes = title
+            if (titleRes != 0) {
+                return textProto { resourceId = titleRes }
+            }
+        }
+        return (this as? PreferenceTitleProvider)?.getTitle(context)?.let {
+            textProto { string = it.toString() }
+        }
+    }
+
+    private suspend fun String?.toActionTarget(
+        activityClass: Class<*>,
+        extras: Bundle?,
+    ): ActionTarget? {
+        if (this.isNullOrEmpty()) return null
+        try {
+            val fragmentClass = context.classLoader.loadClass(this)
+            if (Fragment::class.java.isAssignableFrom(fragmentClass)) {
+                @Suppress("UNCHECKED_CAST")
+                return (fragmentClass as Class<out Fragment>).toActionTarget(activityClass, extras)
+            }
+        } catch (e: Exception) {
+            Log.e(TAG, "Cannot loadClass $this", e)
+        }
+        return null
+    }
+
+    private suspend fun Class<out Fragment>.toActionTarget(
+        activityClass: Class<*>,
+        extras: Bundle?,
+    ): ActionTarget {
+        val startIntent = Intent(context, activityClass)
+        startIntent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT, name)
+        extras?.let { startIntent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS, it) }
+        if (!PreferenceScreenProvider::class.java.isAssignableFrom(this) &&
+            !PreferenceScreenBindingKeyProvider::class.java.isAssignableFrom(this)) {
+            return actionTargetProto { intent = startIntent.toProto() }
+        }
+        val fragment =
+            withContext(Dispatchers.Main) {
+                return@withContext try {
+                    newInstance().apply { arguments = extras }
+                } catch (e: Exception) {
+                    Log.e(TAG, "Fail to instantiate fragment ${this@toActionTarget}", e)
+                    null
+                }
+            }
+        if (fragment is PreferenceScreenBindingKeyProvider) {
+            val screenKey = fragment.getPreferenceScreenBindingKey(context)
+            if (screenKey != null && addPreferenceScreenFromRegistry(screenKey, activityClass)) {
+                return actionTargetProto { key = screenKey }
+            }
+        }
+        if (fragment is PreferenceScreenProvider) {
+            val screen = fragment.createPreferenceScreen(preferenceScreenFactory)
+            if (screen != null) {
+                addPreferenceScreen(startIntent, activityClass, screen)
+                return actionTargetProto { key = screen.key }
+            }
+        }
+        return actionTargetProto { intent = startIntent.toProto() }
+    }
+
+    private suspend fun Intent.toActionTarget(): ActionTarget {
+        if (component?.packageName == "") {
+            setClassName(context, component!!.className)
+        }
+        resolveActivity(context.packageManager)?.let {
+            if (it.packageName == context.packageName) {
+                add(it.className)
+            }
+        }
+        return actionTargetProto { intent = toProto() }
+    }
+
+    companion object {
+        suspend fun of(context: Context, request: GetPreferenceGraphRequest) =
+            PreferenceGraphBuilder(context, request).also { it.init() }
+    }
+}
+
+@SuppressLint("AppBundleLocaleChanges")
+internal fun Context.ofLocale(locale: Locale?): Context {
+    if (locale == null) return this
+    val baseConfig: Configuration = resources.configuration
+    val baseLocale =
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+            baseConfig.locales[0]
+        } else {
+            baseConfig.locale
+        }
+    if (locale == baseLocale) {
+        return this
+    }
+    val newConfig = Configuration(baseConfig)
+    newConfig.setLocale(locale)
+    return createConfigurationContext(newConfig)
+}
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceScreenManager.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceScreenManager.kt
deleted file mode 100644
index 9231f40..0000000
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceScreenManager.kt
+++ /dev/null
@@ -1,70 +0,0 @@
-package com.android.settingslib.graph
-
-import androidx.annotation.StringRes
-import androidx.annotation.XmlRes
-import androidx.preference.Preference
-import androidx.preference.PreferenceManager
-import androidx.preference.PreferenceScreen
-
-/** Manager to create and initialize preference screen. */
-class PreferenceScreenManager(private val preferenceManager: PreferenceManager) {
-    private val context = preferenceManager.context
-    // the map will preserve order
-    private val updaters = mutableMapOf<String, PreferenceUpdater>()
-    private val screenUpdaters = mutableListOf<PreferenceScreenUpdater>()
-
-    /** Creates an empty [PreferenceScreen]. */
-    fun createPreferenceScreen(): PreferenceScreen =
-        preferenceManager.createPreferenceScreen(context)
-
-    /** Creates [PreferenceScreen] from resource. */
-    fun createPreferenceScreen(@XmlRes xmlRes: Int): PreferenceScreen =
-        preferenceManager.inflateFromResource(context, xmlRes, null)
-
-    /** Adds updater for given preference. */
-    fun addPreferenceUpdater(@StringRes key: Int, updater: PreferenceUpdater) =
-        addPreferenceUpdater(context.getString(key), updater)
-
-    /** Adds updater for given preference. */
-    fun addPreferenceUpdater(
-        key: String,
-        updater: PreferenceUpdater,
-    ): PreferenceScreenManager {
-        updaters.put(key, updater)?.let { if (it != updater) throw IllegalArgumentException() }
-        return this
-    }
-
-    /** Adds updater for preference screen. */
-    fun addPreferenceScreenUpdater(updater: PreferenceScreenUpdater): PreferenceScreenManager {
-        screenUpdaters.add(updater)
-        return this
-    }
-
-    /** Adds a list of updaters for preference screen. */
-    fun addPreferenceScreenUpdater(
-        vararg updaters: PreferenceScreenUpdater,
-    ): PreferenceScreenManager {
-        screenUpdaters.addAll(updaters)
-        return this
-    }
-
-    /** Updates preference screen with registered updaters. */
-    fun updatePreferenceScreen(preferenceScreen: PreferenceScreen) {
-        for ((key, updater) in updaters) {
-            preferenceScreen.findPreference<Preference>(key)?.let { updater.updatePreference(it) }
-        }
-        for (updater in screenUpdaters) {
-            updater.updatePreferenceScreen(preferenceScreen)
-        }
-    }
-}
-
-/** Updater of [Preference]. */
-interface PreferenceUpdater {
-    fun updatePreference(preference: Preference)
-}
-
-/** Updater of [PreferenceScreen]. */
-interface PreferenceScreenUpdater {
-    fun updatePreferenceScreen(preferenceScreen: PreferenceScreen)
-}
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceScreenProvider.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceScreenProvider.kt
deleted file mode 100644
index 9e4c1f6..0000000
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceScreenProvider.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.android.settingslib.graph
-
-import android.content.Context
-import androidx.preference.PreferenceScreen
-
-/**
- * Interface to provide [PreferenceScreen].
- *
- * It is expected to be implemented by Activity/Fragment and the implementation needs to use
- * [Context] APIs (e.g. `getContext()`, `getActivity()`) with caution: preference screen creation
- * could happen in background service, where the Activity/Fragment lifecycle callbacks (`onCreate`,
- * `onDestroy`, etc.) are not invoked.
- */
-interface PreferenceScreenProvider {
-
-    /**
-     * Creates [PreferenceScreen].
-     *
-     * Preference screen creation could happen in background service. The implementation MUST use
-     * given [context] instead of APIs like `getContext()`, `getActivity()`, etc.
-     */
-    fun createPreferenceScreen(
-        context: Context,
-        preferenceScreenManager: PreferenceScreenManager,
-    ): PreferenceScreen?
-}
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoConverters.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoConverters.kt
new file mode 100644
index 0000000..d9b9590
--- /dev/null
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoConverters.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.graph
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import com.android.settingslib.graph.proto.BundleProto
+import com.android.settingslib.graph.proto.BundleProto.BundleValue
+import com.android.settingslib.graph.proto.IntentProto
+import com.android.settingslib.graph.proto.TextProto
+import com.google.protobuf.ByteString
+
+fun TextProto.getText(context: Context): String? =
+    when {
+        hasResourceId() -> context.getString(resourceId)
+        hasString() -> string
+        else -> null
+    }
+
+fun Intent.toProto(): IntentProto = intentProto {
+    this@toProto.action?.let { action = it }
+    this@toProto.dataString?.let { data = it }
+    this@toProto.`package`?.let { pkg = it }
+    this@toProto.component?.let { component = it.flattenToShortString() }
+    this@toProto.flags.let { if (it != 0) flags = it }
+    this@toProto.extras?.let { extras = it.toProto() }
+    this@toProto.type?.let { mimeType = it }
+}
+
+fun Bundle.toProto(): BundleProto = bundleProto {
+    fun toProto(value: Any): BundleValue = bundleValueProto {
+        when (value) {
+            is String -> stringValue = value
+            is ByteArray -> bytesValue = ByteString.copyFrom(value)
+            is Int -> intValue = value
+            is Long -> longValue = value
+            is Boolean -> booleanValue = value
+            is Double -> doubleValue = value
+            is Bundle -> bundleValue = value.toProto()
+            else -> throw IllegalArgumentException("Unknown type: ${value.javaClass} $value")
+        }
+    }
+
+    for (key in keySet()) {
+        @Suppress("DEPRECATION") get(key)?.let { putValues(key, toProto(it)) }
+    }
+}
+
+fun BundleValue.stringify(): String =
+    when {
+        hasBooleanValue() -> "$valueCase"
+        hasBytesValue() -> "$bytesValue"
+        hasIntValue() -> "$intValue"
+        hasLongValue() -> "$longValue"
+        hasStringValue() -> stringValue
+        hasDoubleValue() -> "$doubleValue"
+        hasBundleValue() -> "$bundleValue"
+        else -> "Unknown"
+    }
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt
new file mode 100644
index 0000000..d7dae77
--- /dev/null
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.graph
+
+import com.android.settingslib.graph.proto.BundleProto
+import com.android.settingslib.graph.proto.BundleProto.BundleValue
+import com.android.settingslib.graph.proto.IntentProto
+import com.android.settingslib.graph.proto.PreferenceGroupProto
+import com.android.settingslib.graph.proto.PreferenceOrGroupProto
+import com.android.settingslib.graph.proto.PreferenceProto
+import com.android.settingslib.graph.proto.PreferenceProto.ActionTarget
+import com.android.settingslib.graph.proto.PreferenceScreenProto
+import com.android.settingslib.graph.proto.PreferenceValueProto
+import com.android.settingslib.graph.proto.TextProto
+
+/** Returns root or null. */
+val PreferenceScreenProto.rootOrNull
+    get() = if (hasRoot()) root else null
+
+/** Kotlin DSL-style builder for [PreferenceScreenProto]. */
+@JvmSynthetic
+inline fun preferenceScreenProto(init: PreferenceScreenProto.Builder.() -> Unit) =
+    PreferenceScreenProto.newBuilder().also(init).build()
+
+/** Returns preference or null. */
+val PreferenceOrGroupProto.preferenceOrNull
+    get() = if (hasPreference()) preference else null
+
+/** Returns group or null. */
+val PreferenceOrGroupProto.groupOrNull
+    get() = if (hasGroup()) group else null
+
+/** Kotlin DSL-style builder for [PreferenceOrGroupProto]. */
+@JvmSynthetic
+inline fun preferenceOrGroupProto(init: PreferenceOrGroupProto.Builder.() -> Unit) =
+    PreferenceOrGroupProto.newBuilder().also(init).build()
+
+/** Returns preference or null. */
+val PreferenceGroupProto.preferenceOrNull
+    get() = if (hasPreference()) preference else null
+
+/** Kotlin DSL-style builder for [PreferenceGroupProto]. */
+@JvmSynthetic
+inline fun preferenceGroupProto(init: PreferenceGroupProto.Builder.() -> Unit) =
+    PreferenceGroupProto.newBuilder().also(init).build()
+
+/** Returns title or null. */
+val PreferenceProto.titleOrNull
+    get() = if (hasTitle()) title else null
+
+/** Returns summary or null. */
+val PreferenceProto.summaryOrNull
+    get() = if (hasSummary()) summary else null
+
+/** Returns actionTarget or null. */
+val PreferenceProto.actionTargetOrNull
+    get() = if (hasActionTarget()) actionTarget else null
+
+/** Kotlin DSL-style builder for [PreferenceProto]. */
+@JvmSynthetic
+inline fun preferenceProto(init: PreferenceProto.Builder.() -> Unit) =
+    PreferenceProto.newBuilder().also(init).build()
+
+/** Returns intent or null. */
+val ActionTarget.intentOrNull
+    get() = if (hasIntent()) intent else null
+
+/** Kotlin DSL-style builder for [ActionTarget]. */
+@JvmSynthetic
+inline fun actionTargetProto(init: ActionTarget.Builder.() -> Unit) =
+    ActionTarget.newBuilder().also(init).build()
+
+/** Kotlin DSL-style builder for [PreferenceValueProto]. */
+@JvmSynthetic
+inline fun preferenceValueProto(init: PreferenceValueProto.Builder.() -> Unit) =
+    PreferenceValueProto.newBuilder().also(init).build()
+
+/** Kotlin DSL-style builder for [TextProto]. */
+@JvmSynthetic
+inline fun textProto(init: TextProto.Builder.() -> Unit) = TextProto.newBuilder().also(init).build()
+
+/** Kotlin DSL-style builder for [IntentProto]. */
+@JvmSynthetic
+inline fun intentProto(init: IntentProto.Builder.() -> Unit) =
+    IntentProto.newBuilder().also(init).build()
+
+/** Kotlin DSL-style builder for [BundleProto]. */
+@JvmSynthetic
+inline fun bundleProto(init: BundleProto.Builder.() -> Unit) =
+    BundleProto.newBuilder().also(init).build()
+
+/** Kotlin DSL-style builder for [BundleValue]. */
+@JvmSynthetic
+inline fun bundleValueProto(init: BundleValue.Builder.() -> Unit) =
+    BundleValue.newBuilder().also(init).build()
diff --git a/packages/SettingsLib/Ipc/Android.bp b/packages/SettingsLib/Ipc/Android.bp
new file mode 100644
index 0000000..2c7209a
--- /dev/null
+++ b/packages/SettingsLib/Ipc/Android.bp
@@ -0,0 +1,34 @@
+package {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+filegroup {
+    name: "SettingsLibIpc-srcs",
+    srcs: ["src/**/*.kt"],
+}
+
+android_library {
+    name: "SettingsLibIpc",
+    defaults: [
+        "SettingsLintDefaults",
+    ],
+    srcs: [":SettingsLibIpc-srcs"],
+    static_libs: [
+        "androidx.collection_collection",
+        "guava",
+        "kotlinx-coroutines-android",
+    ],
+    kotlincflags: ["-Xjvm-default=all"],
+}
+
+android_library {
+    name: "SettingsLibIpc-testutils",
+    srcs: ["testutils/**/*.kt"],
+    static_libs: [
+        "Robolectric_all-target_upstream",
+        "SettingsLibIpc",
+        "androidx.test.core",
+        "flag-junit",
+        "kotlinx-coroutines-android",
+    ],
+}
diff --git a/packages/SettingsLib/Ipc/AndroidManifest.xml b/packages/SettingsLib/Ipc/AndroidManifest.xml
new file mode 100644
index 0000000..fc48a7d
--- /dev/null
+++ b/packages/SettingsLib/Ipc/AndroidManifest.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.settingslib.ipc">
+
+    <uses-sdk android:minSdkVersion="21" />
+</manifest>
diff --git a/packages/SettingsLib/Ipc/README.md b/packages/SettingsLib/Ipc/README.md
new file mode 100644
index 0000000..ea2c3a1b5
--- /dev/null
+++ b/packages/SettingsLib/Ipc/README.md
@@ -0,0 +1,116 @@
+# Service IPC library
+
+This library provides a kind of IPC (inter-process communication) framework
+based on Android
+[bound service](https://developer.android.com/develop/background-work/services/bound-services)
+with [Messenger](https://developer.android.com/reference/android/os/Messenger).
+
+Following benefits are offered by the library to improve and simplify IPC
+development:
+
+-   Enforce permission check for every API implementation to avoid security
+    vulnerability.
+-   Allow modular API development for better code maintenance (no more huge
+    Service class).
+-   Prevent common mistakes, e.g. Service context leaking, ServiceConnection
+    management.
+
+## Overview
+
+In this manner of IPC,
+[Service](https://developer.android.com/reference/android/app/Service) works
+with [Handler](https://developer.android.com/reference/android/os/Handler) to
+deal with different types of
+[Message](https://developer.android.com/reference/android/os/Message) objects.
+
+Under the hood, each API is represented as a `Message` object:
+
+-   [what](https://developer.android.com/reference/android/os/Message#what):
+    used to identify API.
+-   [data](https://developer.android.com/reference/android/os/Message#getData\(\)):
+    payload of the API parameters and result.
+
+This could be mapped to the `ApiHandler` interface abstraction exactly.
+Specifically, the API implementation needs to provide:
+
+-   An unique id for the API.
+-   How to marshall/unmarshall the request and response.
+-   Whether the given request is permitted.
+
+## Threading model
+
+`MessengerService` starts a dedicated
+[HandlerThread](https://developer.android.com/reference/android/os/HandlerThread)
+to handle requests. `ApiHandler` implementation uses Kotlin `suspend`, which
+allows flexible threading model on top of the
+[Kotlin coroutines](https://kotlinlang.org/docs/coroutines-overview.html).
+
+## Usage
+
+The service provider should extend `MessengerService` and provide API
+implementations. In `AndroidManifest.xml`, declare `<service>` with permission,
+intent filter, etc. if needed.
+
+Meanwhile, the service client implements `MessengerServiceClient` with API
+descriptors to make requests.
+
+Here is an example:
+
+```kotlin
+import android.app.Application
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import kotlinx.coroutines.runBlocking
+
+class EchoService :
+  MessengerService(
+    listOf(EchoApiImpl),
+    PermissionChecker { _, _, _ -> true },
+  )
+
+class EchoServiceClient(context: Context) : MessengerServiceClient(context) {
+  override val serviceIntentFactory: () -> Intent
+    get() = { Intent("example.intent.action.ECHO") }
+
+  fun echo(data: String?): String? =
+    runBlocking { invoke(context.packageName, EchoApi, data).await() }
+}
+
+object EchoApi : ApiDescriptor<String?, String?> {
+  private val codec =
+    object : MessageCodec<String?> {
+      override fun encode(data: String?) =
+        Bundle(1).apply { putString("data", data) }
+
+      override fun decode(data: Bundle): String? = data.getString("data", null)
+    }
+
+  override val id: Int
+    get() = 1
+
+  override val requestCodec: MessageCodec<String?>
+    get() = codec
+
+  override val responseCodec: MessageCodec<String?>
+    get() = codec
+}
+
+// This is not needed by EchoServiceClient.
+object EchoApiImpl : ApiHandler<String?, String?>,
+                     ApiDescriptor<String?, String?> by EchoApi {
+  override suspend fun invoke(
+    application: Application,
+    myUid: Int,
+    callingUid: Int,
+    request: String?,
+  ): String? = request
+
+  override fun hasPermission(
+    application: Application,
+    myUid: Int,
+    callingUid: Int,
+    request: String?,
+  ): Boolean = (request?.length ?: 0) <= 5
+}
+```
diff --git a/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/ApiException.kt b/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/ApiException.kt
new file mode 100644
index 0000000..42772a4
--- /dev/null
+++ b/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/ApiException.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.settingslib.ipc
+
+/** Exception raised when handle request. */
+sealed class ApiException : Exception {
+    constructor() : super()
+
+    constructor(cause: Throwable?) : super(cause)
+
+    constructor(message: String, cause: Throwable?) : super(message, cause)
+}
+
+/** Exception occurred on client side. */
+open class ApiClientException : ApiException {
+    constructor() : super()
+
+    constructor(cause: Throwable?) : super(cause)
+
+    constructor(message: String, cause: Throwable?) : super(message, cause)
+}
+
+/** Client has already been closed. */
+class ClientClosedException : ApiClientException()
+
+/** Api to request is invalid, e.g. negative identity number. */
+class ClientInvalidApiException(message: String) : ApiClientException(message, null)
+
+/**
+ * Exception when bind service failed.
+ *
+ * This exception may be raised for following reasons:
+ * - Context used to bind service has finished its lifecycle (e.g. activity stopped).
+ * - Service not found.
+ * - Permission denied.
+ */
+class ClientBindServiceException(cause: Throwable?) : ApiClientException(cause)
+
+/** Exception when encode request. */
+class ClientEncodeException(cause: Throwable) : ApiClientException(cause)
+
+/** Exception when decode response. */
+class ClientDecodeException(cause: Throwable) : ApiClientException(cause)
+
+/** Exception when send message. */
+class ClientSendException(message: String, cause: Throwable) : ApiClientException(message, cause)
+
+/** Service returns unknown error code. */
+class ClientUnknownResponseCodeException(code: Int) :
+    ApiClientException("Unknown code: $code", null)
+
+/** Exception returned from service. */
+open class ApiServiceException : ApiException() {
+    companion object {
+        internal const val CODE_OK = 0
+        internal const val CODE_PERMISSION_DENIED = 1
+        internal const val CODE_UNKNOWN_API = 2
+        internal const val CODE_INTERNAL_ERROR = 3
+
+        internal fun of(code: Int): ApiServiceException? =
+            when (code) {
+                CODE_PERMISSION_DENIED -> ServicePermissionDeniedException()
+                CODE_UNKNOWN_API -> ServiceUnknownApiException()
+                CODE_INTERNAL_ERROR -> ServiceInternalException()
+                else -> null
+            }
+    }
+}
+
+/** Exception indicates the request is rejected due to permission deny. */
+class ServicePermissionDeniedException : ApiServiceException()
+
+/** Exception indicates API request is unknown. */
+class ServiceUnknownApiException : ApiServiceException()
+
+/** Exception indicates internal issue occurred when service handles the request. */
+class ServiceInternalException : ApiServiceException()
diff --git a/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/ApiHandler.kt b/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/ApiHandler.kt
new file mode 100644
index 0000000..802141d
--- /dev/null
+++ b/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/ApiHandler.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.ipc
+
+import android.app.Application
+import android.os.Bundle
+
+/**
+ * Codec to marshall/unmarshall data between given type and [Bundle].
+ *
+ * The implementation must be threadsafe and stateless.
+ */
+interface MessageCodec<T> {
+    /** Converts given data to [Bundle]. */
+    fun encode(data: T): Bundle
+
+    /** Converts [Bundle] to an object of given data type. */
+    fun decode(data: Bundle): T
+}
+
+/**
+ * Descriptor of API.
+ *
+ * Used by both [MessengerService] and [MessengerServiceClient] to identify API and encode/decode
+ * messages.
+ */
+interface ApiDescriptor<Request, Response> {
+    /**
+     * Identity of the API.
+     *
+     * The id must be:
+     * - Positive: the negative numbers are reserved for internal messages.
+     * - Unique within the [MessengerService].
+     * - Permanent to achieve backward compatibility.
+     */
+    val id: Int
+
+    /** Codec for request. */
+    val requestCodec: MessageCodec<Request>
+
+    /** Codec for response. */
+    val responseCodec: MessageCodec<Response>
+}
+
+/**
+ * Handler of API.
+ *
+ * This is the API implementation portion, which is used by [MessengerService] only.
+ * [MessengerServiceClient] does NOT need this interface at all to make request.
+ *
+ * The implementation must be threadsafe.
+ */
+interface ApiHandler<Request, Response> : ApiDescriptor<Request, Response> {
+    /**
+     * Returns if the request is permitted.
+     *
+     * @return `false` if permission is denied, otherwise `true`
+     */
+    fun hasPermission(
+        application: Application,
+        myUid: Int,
+        callingUid: Int,
+        request: Request,
+    ): Boolean
+
+    /**
+     * Invokes the API.
+     *
+     * The API is invoked from Service handler thread, do not perform time-consuming task. Start
+     * coroutine in another thread if it takes time to complete.
+     */
+    suspend fun invoke(
+        application: Application,
+        myUid: Int,
+        callingUid: Int,
+        request: Request,
+    ): Response
+}
diff --git a/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/MessageCodecs.kt b/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/MessageCodecs.kt
new file mode 100644
index 0000000..4b7572b
--- /dev/null
+++ b/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/MessageCodecs.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.ipc
+
+import android.os.Bundle
+
+/** [MessageCodec] for [Int]. */
+object IntMessageCodec : MessageCodec<Int> {
+    override fun encode(data: Int): Bundle = Bundle(1).apply { putInt(null, data) }
+
+    override fun decode(data: Bundle): Int = data.getInt(null)
+}
+
+/** [MessageCodec] for [Set<Int>]. */
+class IntSetMessageCodec : MessageCodec<Set<Int>> {
+    override fun encode(data: Set<Int>): Bundle =
+        Bundle(1).apply { putIntArray(null, data.toIntArray()) }
+
+    override fun decode(data: Bundle): Set<Int> = data.getIntArray(null)?.toSet() ?: setOf()
+}
+
+/** [MessageCodec] for [Set<String>]. */
+class StringSetMessageCodec : MessageCodec<Set<String>> {
+    override fun encode(data: Set<String>): Bundle =
+        Bundle(1).apply { putStringArray(null, data.toTypedArray()) }
+
+    override fun decode(data: Bundle): Set<String> = data.getStringArray(null)?.toSet() ?: setOf()
+}
diff --git a/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/MessengerService.kt b/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/MessengerService.kt
new file mode 100644
index 0000000..0bdae38
--- /dev/null
+++ b/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/MessengerService.kt
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.ipc
+
+import android.app.Application
+import android.app.Service
+import android.content.Intent
+import android.os.Handler
+import android.os.HandlerThread
+import android.os.IBinder
+import android.os.Looper
+import android.os.Message
+import android.os.Messenger
+import android.os.Process
+import android.util.Log
+import androidx.annotation.VisibleForTesting
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.android.asCoroutineDispatcher
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.launch
+
+/**
+ * [Messenger] based bound service for IPC.
+ *
+ * A dedicated [HandlerThread] is created to handle all requests.
+ *
+ * @param apiHandlers API handlers associated with the service
+ * @param permissionChecker Checker for permission
+ * @param name name of the handler thread
+ */
+open class MessengerService(
+    private val apiHandlers: List<ApiHandler<*, *>>,
+    private val permissionChecker: PermissionChecker,
+    name: String = TAG,
+) : Service() {
+    @VisibleForTesting internal val handlerThread = HandlerThread(name)
+    @VisibleForTesting internal lateinit var handler: IncomingHandler
+    private lateinit var messenger: Messenger
+
+    override fun onCreate() {
+        super.onCreate()
+        handlerThread.start()
+        handler =
+            IncomingHandler(
+                handlerThread.looper,
+                applicationContext as Application,
+                apiHandlers.toSortedArray(),
+                permissionChecker,
+            )
+        messenger = Messenger(handler)
+        Log.i(TAG, "onCreate HandlerThread ${handlerThread.threadId}")
+    }
+
+    override fun onBind(intent: Intent): IBinder? {
+        // this method is executed only once even there is more than 1 client
+        Log.i(TAG, "onBind $intent")
+        return messenger.binder
+    }
+
+    override fun onUnbind(intent: Intent): Boolean {
+        // invoked when ALL clients are unbound
+        Log.i(TAG, "onUnbind $intent")
+        handler.coroutineScope.cancel()
+        return super.onUnbind(intent)
+    }
+
+    override fun onDestroy() {
+        Log.i(TAG, "onDestroy HandlerThread ${handlerThread.threadId}")
+        handlerThread.quitSafely()
+        super.onDestroy()
+    }
+
+    @VisibleForTesting
+    internal class IncomingHandler(
+        looper: Looper,
+        private val application: Application,
+        private val apiHandlers: Array<ApiHandler<*, *>>,
+        private val permissionChecker: PermissionChecker,
+    ) : Handler(looper) {
+        @VisibleForTesting internal val myUid = Process.myUid()
+        val coroutineScope = CoroutineScope(asCoroutineDispatcher().immediate + SupervisorJob())
+
+        override fun handleMessage(msg: Message) {
+            coroutineScope.launch { handle(msg) }
+        }
+
+        @VisibleForTesting
+        internal suspend fun handle(msg: Message) {
+            Log.d(TAG, "receive request $msg")
+            val replyTo = msg.replyTo
+            if (replyTo == null) {
+                Log.e(TAG, "Ignore msg without replyTo: $msg")
+                return
+            }
+            val apiId = msg.what
+            val txnId = msg.arg1
+            val callingUid = msg.sendingUid
+            val data = msg.data
+            // WARNING: never access "msg" beyond this point as it may be recycled by Looper
+            val response = Message.obtain(null, apiId, txnId, ApiServiceException.CODE_OK)
+            try {
+                if (permissionChecker.check(application, myUid, callingUid)) {
+                    @Suppress("UNCHECKED_CAST")
+                    val apiHandler = findApiHandler(apiId) as? ApiHandler<Any, Any>
+                    if (apiHandler != null) {
+                        val request = apiHandler.requestCodec.decode(data)
+                        if (apiHandler.hasPermission(application, myUid, callingUid, request)) {
+                            val result = apiHandler.invoke(application, myUid, callingUid, request)
+                            response.data = apiHandler.responseCodec.encode(result)
+                        } else {
+                            response.arg2 = ApiServiceException.CODE_PERMISSION_DENIED
+                        }
+                    } else {
+                        response.arg2 = ApiServiceException.CODE_UNKNOWN_API
+                        Log.e(TAG, "Unknown request [txnId=$txnId,apiId=$apiId]")
+                    }
+                } else {
+                    response.arg2 = ApiServiceException.CODE_PERMISSION_DENIED
+                }
+            } catch (e: Exception) {
+                response.arg2 = ApiServiceException.CODE_INTERNAL_ERROR
+                Log.e(TAG, "Internal error when handle [txnId=$txnId,apiId=$apiId]", e)
+            }
+            try {
+                replyTo.send(response)
+            } catch (e: Exception) {
+                Log.w(TAG, "Fail to send response for [txnId=$txnId,apiId=$apiId]", e)
+                // nothing to do
+            }
+        }
+
+        @VisibleForTesting
+        internal fun findApiHandler(id: Int): ApiHandler<*, *>? {
+            var low = 0
+            var high = apiHandlers.size
+            while (low < high) {
+                val mid = (low + high).ushr(1) // safe from overflows
+                val api = apiHandlers[mid]
+                when {
+                    api.id < id -> low = mid + 1
+                    api.id > id -> high = mid
+                    else -> return api
+                }
+            }
+            return null
+        }
+    }
+
+    companion object {
+        @VisibleForTesting internal const val TAG = "MessengerService"
+    }
+}
+
+@VisibleForTesting
+internal fun List<ApiHandler<*, *>>.toSortedArray() =
+    toTypedArray().also { array ->
+        if (array.isEmpty()) return@also
+        array.sortBy { it.id }
+        if (array[0].id < 0) throw IllegalArgumentException("negative id: ${array[0]}")
+        for (index in 1 until array.size) {
+            if (array[index - 1].id == array[index].id) {
+                throw IllegalArgumentException("conflict id: ${array[index - 1]} ${array[index]}")
+            }
+        }
+    }
diff --git a/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/MessengerServiceClient.kt b/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/MessengerServiceClient.kt
new file mode 100644
index 0000000..7ffefed
--- /dev/null
+++ b/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/MessengerServiceClient.kt
@@ -0,0 +1,471 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.ipc
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
+import android.os.Bundle
+import android.os.DeadObjectException
+import android.os.Handler
+import android.os.HandlerThread
+import android.os.IBinder
+import android.os.Looper
+import android.os.Message
+import android.os.Messenger
+import android.util.Log
+import androidx.annotation.OpenForTesting
+import androidx.annotation.VisibleForTesting
+import androidx.collection.ArrayMap
+import com.google.common.base.Ticker
+import java.util.concurrent.atomic.AtomicInteger
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.CompletionHandler
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.DisposableHandle
+
+/**
+ * Client to communicate with [MessengerService].
+ *
+ * A dedicated [HandlerThread] is created to handle requests **sequentially**, there is only one
+ * ongoing request per package.
+ *
+ * Must call [close] before [context] is destroyed to avoid context leaking. Note that
+ * [MessengerService] is automatically unbound when context lifecycle is stopped. Further request
+ * will result in service binding exception.
+ *
+ * @param context context used for service binding, note that context lifecycle affects the IPC
+ *   service lifecycle
+ * @param serviceConnectionIdleMs idle time in milliseconds before closing the service connection
+ * @param name name of the handler thread
+ */
+abstract class MessengerServiceClient
+@JvmOverloads
+constructor(
+    protected val context: Context,
+    private val serviceConnectionIdleMs: Long = 30000L,
+    name: String = TAG,
+    private val metricsLogger: MetricsLogger? = null,
+) : AutoCloseable {
+    /** Per package [ServiceConnection]. */
+    @VisibleForTesting internal val messengers = ArrayMap<String, Connection>()
+    private val handlerThread = HandlerThread(name)
+    @VisibleForTesting internal val handler: Handler
+
+    init {
+        handlerThread.start()
+        val looper = handlerThread.looper
+        handler = Handler(looper)
+    }
+
+    /**
+     * Factory for service [Intent] creation.
+     *
+     * A typical implementation is create [Intent] with specific action.
+     */
+    protected abstract val serviceIntentFactory: () -> Intent
+
+    override fun close() = close(true)
+
+    fun close(join: Boolean) {
+        handler.post {
+            val exception = ClientClosedException()
+            val connections = messengers.values.toTypedArray()
+            for (connection in connections) connection.close(exception)
+        }
+        handlerThread.quitSafely()
+        if (join) handlerThread.join()
+    }
+
+    /**
+     * Invokes given API.
+     *
+     * @param packageName package name of the target service
+     * @param apiDescriptor descriptor of API
+     * @param request request parameter
+     * @return Deferred object of the response, which could be used for [Deferred.await],
+     *   [Deferred.cancel], etc.
+     * @exception ApiException
+     */
+    // TODO: support timeout
+    fun <Request, Response> invoke(
+        packageName: String,
+        apiDescriptor: ApiDescriptor<Request, Response>,
+        request: Request,
+    ): Deferred<Response> {
+        if (apiDescriptor.id < 0) throw ClientInvalidApiException("Invalid id: ${apiDescriptor.id}")
+        if (
+            packageName == context.packageName &&
+                Looper.getMainLooper().thread === Thread.currentThread()
+        ) {
+            // Deadlock as it might involve service creation, which requires main thread
+            throw IllegalStateException("Invoke on main thread causes deadlock")
+        }
+        val wrapper = RequestWrapper(packageName, apiDescriptor, request, txnId.getAndIncrement())
+        metricsLogger?.run {
+            wrapper.logIpcEvent(this, IpcEvent.ENQUEUED)
+            wrapper.deferred.invokeOnCompletion {
+                wrapper.logIpcEvent(this, IpcEvent.COMPLETED, it)
+            }
+        }
+        if (!handler.post { getConnection(packageName).enqueueRequest(wrapper) }) {
+            wrapper.completeExceptionally(ClientClosedException())
+        }
+        return wrapper.deferred
+    }
+
+    private fun getConnection(packageName: String) =
+        messengers.getOrPut(packageName) {
+            Connection(
+                handler.looper,
+                context,
+                packageName,
+                serviceConnectionIdleMs,
+                serviceIntentFactory,
+                messengers,
+                metricsLogger,
+            )
+        }
+
+    @VisibleForTesting
+    internal data class RequestWrapper<Request, Response>(
+        val packageName: String,
+        val apiDescriptor: ApiDescriptor<Request, Response>,
+        val request: Request,
+        val txnId: Int,
+        val deferred: CompletableDeferred<Response> = CompletableDeferred(),
+    ) {
+        val data: Bundle
+            get() = request.let { apiDescriptor.requestCodec.encode(it) }
+
+        fun completeExceptionally(e: Exception) {
+            deferred.completeExceptionally(e)
+        }
+
+        fun logIpcEvent(
+            metricsLogger: MetricsLogger,
+            event: @IpcEvent Int,
+            cause: Throwable? = null,
+        ) {
+            try {
+                metricsLogger.logIpcEvent(
+                    packageName,
+                    txnId,
+                    apiDescriptor.id,
+                    event,
+                    cause,
+                    ticker.read(),
+                )
+            } catch (e: Exception) {
+                Log.e(TAG, "fail to log ipc event: $event", e)
+            }
+        }
+    }
+
+    // NOTE: All ServiceConnection callbacks are invoked from main thread.
+    @OpenForTesting
+    @VisibleForTesting
+    internal open class Connection(
+        looper: Looper,
+        private val context: Context,
+        private val packageName: String,
+        private val serviceConnectionIdleMs: Long,
+        private val serviceIntentFactory: () -> Intent,
+        private val messengers: ArrayMap<String, Connection>,
+        private val metricsLogger: MetricsLogger?,
+    ) : Handler(looper), ServiceConnection {
+        private val clientMessenger = Messenger(this)
+        internal val pendingRequests = ArrayDeque<RequestWrapper<*, *>>()
+        internal var serviceMessenger: Messenger? = null
+        internal open var connectionState: Int = STATE_INIT
+
+        internal var disposableHandle: DisposableHandle? = null
+        private val requestCompletionHandler =
+            object : CompletionHandler {
+                override fun invoke(cause: Throwable?) {
+                    sendEmptyMessage(MSG_CHECK_REQUEST_STATE)
+                }
+            }
+
+        override fun handleMessage(msg: Message) {
+            if (msg.what < 0) {
+                handleClientMessage(msg)
+                return
+            }
+            Log.d(TAG, "receive response $msg")
+            val request = pendingRequests.removeFirstOrNull()
+            if (request == null) {
+                Log.w(TAG, "Pending request is empty when got response")
+                return
+            }
+            if (msg.arg1 != request.txnId || request.apiDescriptor.id != msg.what) {
+                Log.w(TAG, "Mismatch ${request.apiDescriptor.id}, response=$msg")
+                // add request back for retry
+                pendingRequests.addFirst(request)
+                return
+            }
+            handleServiceMessage(request, msg)
+        }
+
+        internal open fun handleClientMessage(msg: Message) {
+            when (msg.what) {
+                MSG_ON_SERVICE_CONNECTED -> {
+                    if (connectionState == STATE_BINDING) {
+                        connectionState = STATE_CONNECTED
+                        serviceMessenger = Messenger(msg.obj as IBinder)
+                        drainPendingRequests()
+                    } else {
+                        Log.w(TAG, "Got onServiceConnected when state is $connectionState")
+                    }
+                }
+                MSG_REBIND_SERVICE -> {
+                    if (pendingRequests.isEmpty()) {
+                        removeMessages(MSG_CLOSE_ON_IDLE)
+                        close(null)
+                    } else {
+                        // died when binding, reset state for rebinding
+                        if (msg.obj != null && connectionState == STATE_BINDING) {
+                            connectionState = STATE_CONNECTED
+                        }
+                        rebindService()
+                    }
+                }
+                MSG_CLOSE_ON_IDLE -> {
+                    if (pendingRequests.isEmpty()) close(null)
+                }
+                MSG_CHECK_REQUEST_STATE -> {
+                    val request = pendingRequests.firstOrNull()
+                    if (request != null && request.deferred.isCompleted) {
+                        drainPendingRequests()
+                    }
+                }
+                else -> Log.e(TAG, "Unknown msg: $msg")
+            }
+        }
+
+        internal open fun handleServiceMessage(request: RequestWrapper<*, *>, response: Message) {
+            @Suppress("UNCHECKED_CAST") val deferred = request.deferred as CompletableDeferred<Any?>
+            if (deferred.isCompleted) {
+                drainPendingRequests()
+                return
+            }
+            metricsLogger?.let { request.logIpcEvent(it, IpcEvent.RESPONSE_RECEIVED) }
+            disposableHandle?.dispose()
+            if (response.arg2 == ApiServiceException.CODE_OK) {
+                try {
+                    deferred.complete(request.apiDescriptor.responseCodec.decode(response.data))
+                } catch (e: Exception) {
+                    request.completeExceptionally(ClientDecodeException(e))
+                }
+            } else {
+                val errorCode = response.arg2
+                val exception = ApiServiceException.of(errorCode)
+                if (exception != null) {
+                    request.completeExceptionally(exception)
+                } else {
+                    request.completeExceptionally(ClientUnknownResponseCodeException(errorCode))
+                }
+            }
+            drainPendingRequests()
+        }
+
+        fun enqueueRequest(request: RequestWrapper<*, *>) {
+            if (connectionState == STATE_CLOSED) {
+                request.completeExceptionally(ClientClosedException())
+                return
+            }
+            pendingRequests.add(request)
+            if (pendingRequests.size == 1) {
+                removeMessages(MSG_CLOSE_ON_IDLE)
+                drainPendingRequests()
+            }
+        }
+
+        override fun onServiceConnected(name: ComponentName, service: IBinder) {
+            Log.i(TAG, "onServiceConnected $name")
+            metricsLogger?.logServiceEvent(ServiceEvent.ON_SERVICE_CONNECTED)
+            sendMessage(obtainMessage(MSG_ON_SERVICE_CONNECTED, service))
+        }
+
+        override fun onServiceDisconnected(name: ComponentName) {
+            // Service process crashed or killed, the connection remains alive, will receive
+            // onServiceConnected when the Service is next running
+            Log.i(TAG, "onServiceDisconnected $name")
+            metricsLogger?.logServiceEvent(ServiceEvent.ON_SERVICE_DISCONNECTED)
+            sendMessage(obtainMessage(MSG_REBIND_SERVICE))
+        }
+
+        override fun onBindingDied(name: ComponentName) {
+            Log.i(TAG, "onBindingDied $name")
+            metricsLogger?.logServiceEvent(ServiceEvent.ON_BINDING_DIED)
+            // When service is connected and peer happens to be updated, both onServiceDisconnected
+            // and onBindingDied callbacks are invoked.
+            if (!hasMessages(MSG_REBIND_SERVICE)) {
+                sendMessage(obtainMessage(MSG_REBIND_SERVICE, true))
+            }
+        }
+
+        internal open fun drainPendingRequests() {
+            disposableHandle = null
+            if (pendingRequests.isEmpty()) {
+                closeOnIdle(serviceConnectionIdleMs)
+                return
+            }
+            val serviceMessenger = this.serviceMessenger
+            if (serviceMessenger == null) {
+                bindService()
+                return
+            }
+            do {
+                val request = pendingRequests.first()
+                if (request.deferred.isCompleted) {
+                    pendingRequests.removeFirst()
+                } else {
+                    sendServiceMessage(serviceMessenger, request)
+                    return
+                }
+            } while (pendingRequests.isNotEmpty())
+            closeOnIdle(serviceConnectionIdleMs)
+        }
+
+        internal open fun closeOnIdle(idleMs: Long) {
+            if (idleMs <= 0 || !sendEmptyMessageDelayed(MSG_CLOSE_ON_IDLE, idleMs)) {
+                close(null)
+            }
+        }
+
+        internal open fun sendServiceMessage(
+            serviceMessenger: Messenger,
+            request: RequestWrapper<*, *>,
+        ) {
+            fun completeExceptionally(exception: Exception) {
+                pendingRequests.removeFirst()
+                request.completeExceptionally(exception)
+                drainPendingRequests()
+            }
+            val message =
+                obtainMessage(request.apiDescriptor.id, request.txnId, 0).apply {
+                    replyTo = clientMessenger
+                }
+            try {
+                message.data = request.data
+            } catch (e: Exception) {
+                completeExceptionally(ClientEncodeException(e))
+                return
+            }
+            Log.d(TAG, "send $message")
+            try {
+                sendServiceMessage(serviceMessenger, message)
+                metricsLogger?.let { request.logIpcEvent(it, IpcEvent.REQUEST_SENT) }
+                disposableHandle = request.deferred.invokeOnCompletion(requestCompletionHandler)
+            } catch (e: DeadObjectException) {
+                Log.w(TAG, "Got DeadObjectException")
+                rebindService()
+            } catch (e: Exception) {
+                completeExceptionally(ClientSendException("Fail to send $message", e))
+            }
+        }
+
+        @Throws(Exception::class)
+        internal open fun sendServiceMessage(serviceMessenger: Messenger, message: Message) =
+            serviceMessenger.send(message)
+
+        internal fun bindService() {
+            if (connectionState == STATE_BINDING || connectionState == STATE_CLOSED) {
+                Log.w(TAG, "Ignore bindService $packageName, state: $connectionState")
+                return
+            }
+            connectionState = STATE_BINDING
+            Log.i(TAG, "bindService $packageName")
+            val intent = serviceIntentFactory.invoke()
+            intent.setPackage(packageName)
+            metricsLogger?.logServiceEvent(ServiceEvent.BIND_SERVICE)
+            bindService(intent)?.let { close(it) }
+        }
+
+        private fun bindService(intent: Intent): Exception? =
+            try {
+                if (context.bindService(intent, this, Context.BIND_AUTO_CREATE)) {
+                    null
+                } else {
+                    ClientBindServiceException(null)
+                }
+            } catch (e: Exception) {
+                ClientBindServiceException(e)
+            }
+
+        internal open fun rebindService() {
+            Log.i(TAG, "rebindService $packageName")
+            metricsLogger?.logServiceEvent(ServiceEvent.REBIND_SERVICE)
+            unbindService()
+            bindService()
+        }
+
+        internal fun close(exception: Exception?) {
+            Log.i(TAG, "close connection $packageName", exception)
+            connectionState = STATE_CLOSED
+            messengers.remove(packageName, this)
+            unbindService()
+            if (pendingRequests.isNotEmpty()) {
+                val reason = exception ?: ClientClosedException()
+                do {
+                    pendingRequests.removeFirst().deferred.completeExceptionally(reason)
+                } while (pendingRequests.isNotEmpty())
+            }
+        }
+
+        private fun unbindService() {
+            disposableHandle?.dispose()
+            disposableHandle = null
+            serviceMessenger = null
+            metricsLogger?.logServiceEvent(ServiceEvent.UNBIND_SERVICE)
+            try {
+                // "IllegalArgumentException: Service not registered" may be raised when peer app is
+                // just updated (e.g. upgraded)
+                context.unbindService(this)
+            } catch (e: Exception) {
+                Log.w(TAG, "exception raised when unbindService", e)
+            }
+        }
+
+        private fun MetricsLogger.logServiceEvent(event: @ServiceEvent Int) {
+            try {
+                logServiceEvent(packageName, event, ticker.read())
+            } catch (e: Exception) {
+                Log.e(TAG, "fail to log service event: $event", e)
+            }
+        }
+    }
+
+    companion object {
+        private const val TAG = "MessengerServiceClient"
+        private val ticker: Ticker by lazy { Ticker.systemTicker() }
+
+        @VisibleForTesting internal const val STATE_INIT = 0
+        @VisibleForTesting internal const val STATE_BINDING = 1
+        @VisibleForTesting internal const val STATE_CONNECTED = 2
+        @VisibleForTesting internal const val STATE_CLOSED = 3
+
+        @VisibleForTesting internal const val MSG_ON_SERVICE_CONNECTED = -1
+        @VisibleForTesting internal const val MSG_REBIND_SERVICE = -2
+        @VisibleForTesting internal const val MSG_CLOSE_ON_IDLE = -3
+        @VisibleForTesting internal const val MSG_CHECK_REQUEST_STATE = -4
+
+        @VisibleForTesting internal val txnId = AtomicInteger()
+    }
+}
diff --git a/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/MetricsLogger.kt b/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/MetricsLogger.kt
new file mode 100644
index 0000000..795a920
--- /dev/null
+++ b/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/MetricsLogger.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.ipc
+
+import androidx.annotation.IntDef
+
+/** Interface for metrics logging. */
+interface MetricsLogger {
+
+    /**
+     * Logs service connection event.
+     *
+     * @param packageName package name of the service connection
+     * @param event service event type
+     * @param elapsedRealtimeNanos nanoseconds since boot, including time spent in sleep
+     * @see [android.os.SystemClock.elapsedRealtimeNanos]
+     */
+    fun logServiceEvent(packageName: String, event: @ServiceEvent Int, elapsedRealtimeNanos: Long)
+
+    /**
+     * Logs ipc call event.
+     *
+     * @param packageName package name of the service connection
+     * @param txnId unique transaction id of the ipc call
+     * @param ipc ipc API id
+     * @param event ipc event type
+     * @param cause cause when ipc request completed, provided only when [event] is
+     *   [IpcEvent.COMPLETED]
+     * @param elapsedRealtimeNanos nanoseconds since boot, including time spent in sleep
+     * @see [android.os.SystemClock.elapsedRealtimeNanos]
+     */
+    fun logIpcEvent(
+        packageName: String,
+        txnId: Int,
+        ipc: Int,
+        event: Int,
+        cause: Throwable?,
+        elapsedRealtimeNanos: Long,
+    )
+}
+
+/** Service connection events (for client). */
+@Target(AnnotationTarget.TYPE)
+@IntDef(
+    ServiceEvent.BIND_SERVICE,
+    ServiceEvent.UNBIND_SERVICE,
+    ServiceEvent.REBIND_SERVICE,
+    ServiceEvent.ON_SERVICE_CONNECTED,
+    ServiceEvent.ON_SERVICE_DISCONNECTED,
+    ServiceEvent.ON_BINDING_DIED,
+)
+@Retention(AnnotationRetention.SOURCE)
+annotation class ServiceEvent {
+    companion object {
+        /** Event of [android.content.Context.bindService] call. */
+        const val BIND_SERVICE = 0
+
+        /** Event of [android.content.Context.unbindService] call. */
+        const val UNBIND_SERVICE = 1
+
+        /** Event to rebind service. */
+        const val REBIND_SERVICE = 2
+
+        /** Event of [android.content.ServiceConnection.onServiceConnected] callback. */
+        const val ON_SERVICE_CONNECTED = 3
+
+        /** Event of [android.content.ServiceConnection.onServiceDisconnected] callback. */
+        const val ON_SERVICE_DISCONNECTED = 4
+
+        /** Event of [android.content.ServiceConnection.onBindingDied] callback. */
+        const val ON_BINDING_DIED = 5
+    }
+}
+
+/** Events of a ipc call. */
+@Target(AnnotationTarget.TYPE)
+@IntDef(IpcEvent.ENQUEUED, IpcEvent.REQUEST_SENT, IpcEvent.RESPONSE_RECEIVED, IpcEvent.COMPLETED)
+@Retention(AnnotationRetention.SOURCE)
+annotation class IpcEvent {
+    companion object {
+        /** Event of IPC request enqueued. */
+        const val ENQUEUED = 0
+
+        /** Event of IPC request has been sent to service. */
+        const val REQUEST_SENT = 1
+
+        /** Event of IPC response received from service. */
+        const val RESPONSE_RECEIVED = 2
+
+        /** Event of IPC request completed. */
+        const val COMPLETED = 3
+    }
+}
diff --git a/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/PermissionChecker.kt b/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/PermissionChecker.kt
new file mode 100644
index 0000000..da9c955
--- /dev/null
+++ b/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/PermissionChecker.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.ipc
+
+import android.app.Application
+import android.content.pm.PackageManager
+import androidx.collection.mutableIntIntMapOf
+
+/** Checker for permission. */
+fun interface PermissionChecker {
+    /**
+     * Checks permission.
+     *
+     * @param application application context
+     * @param myUid uid of current process
+     * @param callingUid uid of peer process
+     */
+    fun check(application: Application, myUid: Int, callingUid: Int): Boolean
+}
+
+/** Verifies apk signatures as permission check. */
+class SignatureChecker : PermissionChecker {
+    private val cache = mutableIntIntMapOf()
+
+    override fun check(application: Application, myUid: Int, callingUid: Int): Boolean =
+        cache.getOrPut(callingUid) {
+            application.packageManager.checkSignatures(myUid, callingUid)
+        } == PackageManager.SIGNATURE_MATCH
+}
diff --git a/packages/SettingsLib/Ipc/testutils/com/android/settingslib/ipc/MessengerServiceRule.kt b/packages/SettingsLib/Ipc/testutils/com/android/settingslib/ipc/MessengerServiceRule.kt
new file mode 100644
index 0000000..8b2deaf
--- /dev/null
+++ b/packages/SettingsLib/Ipc/testutils/com/android/settingslib/ipc/MessengerServiceRule.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.ipc
+
+import android.app.Application
+import android.app.Service
+import android.content.ComponentName
+import android.content.Intent
+import android.os.Build
+import android.os.Looper
+import androidx.test.core.app.ApplicationProvider
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
+import org.junit.rules.TestWatcher
+import org.junit.runner.Description
+import org.robolectric.Robolectric
+import org.robolectric.Shadows
+import org.robolectric.android.controller.ServiceController
+
+/** Rule for messenger service testing. */
+open class MessengerServiceRule<C : MessengerServiceClient>(
+    private val serviceClass: Class<out MessengerService>,
+    val client: C,
+) : TestWatcher() {
+    val application: Application = ApplicationProvider.getApplicationContext()
+    val isRobolectric = Build.FINGERPRINT.contains("robolectric")
+
+    private var serviceController: ServiceController<out Service>? = null
+
+    override fun starting(description: Description) {
+        if (isRobolectric) {
+            runBlocking { setupRobolectricService() }
+        }
+    }
+
+    override fun finished(description: Description) {
+        client.close()
+        if (isRobolectric) {
+            runBlocking {
+                withContext(Dispatchers.Main) { serviceController?.run { unbind().destroy() } }
+            }
+        }
+    }
+
+    private suspend fun setupRobolectricService() {
+        if (Thread.currentThread() == Looper.getMainLooper().thread) {
+            throw IllegalStateException(
+                "To avoid deadlock, run test with @LooperMode(LooperMode.Mode.INSTRUMENTATION_TEST)"
+            )
+        }
+        withContext(Dispatchers.Main) {
+            serviceController = Robolectric.buildService(serviceClass)
+            val service = serviceController!!.create().get()
+            Shadows.shadowOf(application).apply {
+                setComponentNameAndServiceForBindService(
+                    ComponentName(application, serviceClass),
+                    service.onBind(Intent(application, serviceClass)),
+                )
+                setBindServiceCallsOnServiceConnectedDirectly(true)
+                setUnbindServiceCallsOnServiceDisconnected(false)
+            }
+        }
+    }
+}
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
index 9be0e71..5fcf478 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
@@ -108,6 +108,9 @@
 /** Abstract preference screen to provide preference hierarchy and binding factory. */
 interface PreferenceScreenCreator : PreferenceScreenMetadata, PreferenceScreenProvider {
 
+    /** Returns if the flag (e.g. for rollout) is enabled on current screen. */
+    fun isFlagEnabled(context: Context): Boolean = true
+
     val preferenceBindingFactory: PreferenceBindingFactory
         get() = DefaultPreferenceBindingFactory
 
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt
index 68f640b..a270681 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt
@@ -44,13 +44,8 @@
         fun createPreferenceScreenFromResource() =
             factory.inflate(getPreferenceScreenResId(context))
 
-        if (!usePreferenceScreenMetadata()) return createPreferenceScreenFromResource()
-
-        val screenKey = getPreferenceScreenBindingKey(context)
         val screenCreator =
-            (PreferenceScreenRegistry[screenKey] as? PreferenceScreenCreator)
-                ?: return createPreferenceScreenFromResource()
-
+            getPreferenceScreenCreator(context) ?: return createPreferenceScreenFromResource()
         val preferenceBindingFactory = screenCreator.preferenceBindingFactory
         val preferenceHierarchy = screenCreator.getPreferenceHierarchy(context)
         val preferenceScreen =
@@ -73,17 +68,14 @@
         return preferenceScreen
     }
 
-    /**
-     * Returns if preference screen metadata can be used to set up preference screen.
-     *
-     * This is for flagging purpose. If false (e.g. flag is disabled), xml resource is used to build
-     * preference screen.
-     */
-    protected open fun usePreferenceScreenMetadata(): Boolean = false
-
     /** Returns the xml resource to create preference screen. */
     @XmlRes protected open fun getPreferenceScreenResId(context: Context): Int = 0
 
+    protected fun getPreferenceScreenCreator(context: Context): PreferenceScreenCreator? =
+        (PreferenceScreenRegistry[getPreferenceScreenBindingKey(context)]
+                as? PreferenceScreenCreator)
+            ?.run { if (isFlagEnabled(context)) this else null }
+
     override fun getPreferenceScreenBindingKey(context: Context): String? =
         arguments?.getString(EXTRA_BINDING_SCREEN_KEY)
 
@@ -91,19 +83,4 @@
         preferenceScreenBindingHelper?.close()
         super.onDestroy()
     }
-
-    companion object {
-        /** Returns [PreferenceFragment] instance to display the preference screen of given key. */
-        fun of(screenKey: String): PreferenceFragment? {
-            val screenMetadata = PreferenceScreenRegistry[screenKey] ?: return null
-            if (
-                screenMetadata is PreferenceScreenCreator && screenMetadata.hasCompleteHierarchy()
-            ) {
-                return PreferenceFragment().apply {
-                    arguments = Bundle().apply { putString(EXTRA_BINDING_SCREEN_KEY, screenKey) }
-                }
-            }
-            return null
-        }
-    }
 }
diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
index 34b597b..79c3ff9 100644
--- a/packages/SettingsLib/aconfig/settingslib.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -139,3 +139,13 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "audio_sharing_qs_dialog_improvement"
+    namespace: "cross_device_experiences"
+    description: "Gates whether to enable audio sharing qs dialog improvement"
+    bug: "360759048"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 4d771c0..feee89a 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1411,6 +1411,8 @@
     <string name="media_transfer_this_device_name_tablet">This tablet</string>
     <!-- Name of the default media output of the TV. [CHAR LIMIT=30] -->
     <string name="media_transfer_this_device_name_tv">@string/tv_media_transfer_default</string>
+    <!-- Name of the internal mic. [CHAR LIMIT=30] -->
+    <string name="media_transfer_internal_mic">Microphone (internal)</string>
     <!-- Name of the dock device. [CHAR LIMIT=30] -->
     <string name="media_transfer_dock_speaker_device_name">Dock speaker</string>
     <!-- Default name of the external device. [CHAR LIMIT=30] -->
@@ -1637,6 +1639,12 @@
     <!-- Name of the 3.5mm and usb audio device. [CHAR LIMIT=50] -->
     <string name="media_transfer_wired_usb_device_name">Wired headphone</string>
 
+    <!-- Name of the 3.5mm audio device mic. [CHAR LIMIT=50] -->
+    <string name="media_transfer_wired_device_mic_name">Mic jack</string>
+
+    <!-- Name of the usb audio device mic. [CHAR LIMIT=50] -->
+    <string name="media_transfer_usb_device_mic_name">USB mic</string>
+
     <!-- Label for Wifi hotspot switch on. Toggles hotspot on [CHAR LIMIT=30] -->
     <string name="wifi_hotspot_switch_on_text">On</string>
     <!-- Label for Wifi hotspot switch off. Toggles hotspot off [CHAR LIMIT=30] -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 1e58335..9d56c77 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -520,8 +520,6 @@
 
         if (android.webkit.Flags.updateServiceIpcWrapper()) {
             if (pm.hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) {
-                // WebViewUpdateManager.getInstance() will not return null on devices with
-                // FEATURE_WEBVIEW.
                 provider = WebViewUpdateManager.getInstance().getDefaultWebViewPackage();
             }
         } else {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
index c6eb9fd..6dab224 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
@@ -18,6 +18,7 @@
 
 import android.bluetooth.BluetoothCsipSetCoordinator;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothUuid;
 import android.os.Build;
@@ -356,6 +357,8 @@
         final CachedBluetoothDeviceManager deviceManager = mBtManager.getCachedDeviceManager();
         preferredMainDevice = deviceManager.findDevice(bluetoothDeviceOfPreferredMainDevice);
         if (haveMultiMainDevicesInAllOfDevicesList) {
+            log("addMemberDevicesIntoMainDevice: haveMultiMainDevicesInAllOfDevicesList. "
+                    + "Combine them and also keep the preferred main device as main device.");
             // put another devices into main device.
             for (CachedBluetoothDevice deviceItem : topLevelOfGroupDevicesList) {
                 if (deviceItem.getDevice() == null || deviceItem.getDevice().equals(
@@ -376,6 +379,7 @@
                 preferredMainDevice.refresh();
                 hasChanged = true;
             }
+            syncAudioSharingSourceIfNeeded(preferredMainDevice);
         }
         if (hasChanged) {
             log("addMemberDevicesIntoMainDevice: After changed, CachedBluetoothDevice list: "
@@ -384,6 +388,41 @@
         return hasChanged;
     }
 
+    private void syncAudioSharingSourceIfNeeded(CachedBluetoothDevice mainDevice) {
+        boolean isAudioSharingEnabled = BluetoothUtils.isAudioSharingEnabled();
+        if (isAudioSharingEnabled) {
+            boolean hasBroadcastSource = BluetoothUtils.isBroadcasting(mBtManager)
+                    && BluetoothUtils.hasConnectedBroadcastSource(
+                    mainDevice, mBtManager);
+            if (hasBroadcastSource) {
+                LocalBluetoothLeBroadcast broadcast = mBtManager == null ? null
+                        : mBtManager.getProfileManager().getLeAudioBroadcastProfile();
+                BluetoothLeBroadcastMetadata metadata = broadcast == null ? null :
+                        broadcast.getLatestBluetoothLeBroadcastMetadata();
+                LocalBluetoothLeBroadcastAssistant assistant = mBtManager == null ? null
+                        : mBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
+                if (metadata != null && assistant != null) {
+                    log("addMemberDevicesIntoMainDevice: sync audio sharing source after "
+                            + "combining the top level devices.");
+                    Set<CachedBluetoothDevice> deviceSet = new HashSet<>();
+                    deviceSet.add(mainDevice);
+                    deviceSet.addAll(mainDevice.getMemberDevice());
+                    Set<BluetoothDevice> sinksToSync = deviceSet.stream()
+                            .map(CachedBluetoothDevice::getDevice)
+                            .filter(device ->
+                                    !BluetoothUtils.hasConnectedBroadcastSourceForBtDevice(
+                                            device, mBtManager))
+                            .collect(Collectors.toSet());
+                    for (BluetoothDevice device : sinksToSync) {
+                        log("addMemberDevicesIntoMainDevice: sync audio sharing source to "
+                                + device.getAnonymizedAddress());
+                        assistant.addSource(device, metadata, /* isGroupOp= */ false);
+                    }
+                }
+            }
+        }
+    }
+
     private void log(String msg) {
         if (DEBUG) {
             Log.d(TAG, msg);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingItem.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingItem.kt
index a0fe5d2..38183d5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingItem.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingItem.kt
@@ -36,6 +36,7 @@
     val className: String,
     val intentAction: String,
     val preferenceKey: String? = null,
+    val highlighted: Boolean = false,
     val extras: Bundle = Bundle.EMPTY,
 ) : Parcelable {
 
@@ -47,6 +48,7 @@
             writeString(packageName)
             writeString(className)
             writeString(intentAction)
+            writeBoolean(highlighted)
             writeString(preferenceKey)
             writeBundle(extras)
         }
@@ -63,6 +65,7 @@
                             packageName = readString() ?: "",
                             className = readString() ?: "",
                             intentAction = readString() ?: "",
+                            highlighted = readBoolean(),
                             preferenceKey = readString() ?: "",
                             extras = readBundle((Bundle::class.java.classLoader)) ?: Bundle.EMPTY,
                         )
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingPreference.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingPreference.java
index 4b67ef7..c8c7562 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingPreference.java
@@ -42,6 +42,8 @@
                 return MultiTogglePreference.readFromParcel(in);
             case DeviceSettingType.DEVICE_SETTING_TYPE_FOOTER:
                 return DeviceSettingFooterPreference.readFromParcel(in);
+            case DeviceSettingType.DEVICE_SETTING_TYPE_HELP:
+                return DeviceSettingHelpPreference.readFromParcel(in);
             default:
                 return UNKNOWN;
         }
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 769b6e6..29664f6 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
@@ -110,15 +110,16 @@
             if (settingId == DeviceSettingId.DEVICE_SETTING_ID_BLUETOOTH_PROFILES) {
                 BluetoothProfilesItem(
                     settingId,
+                    highlighted,
                     preferenceKey!!,
                     extras.getStringArrayList(DeviceSettingContract.INVISIBLE_PROFILES)
                         ?: emptyList()
                 )
             } else {
-                CommonBuiltinItem(settingId, preferenceKey!!)
+                CommonBuiltinItem(settingId, highlighted, preferenceKey!!)
             }
         } else {
-            AppProvidedItem(settingId)
+            AppProvidedItem(settingId, highlighted)
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt
index 08fb3fb..5958c30 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt
@@ -34,6 +34,7 @@
 /** Models a device setting item in config. */
 sealed interface DeviceSettingConfigItemModel {
     @DeviceSettingId val settingId: Int
+    val highlighted: Boolean
 
     /** A built-in item in Settings. */
     sealed interface BuiltinItem : DeviceSettingConfigItemModel {
@@ -43,18 +44,22 @@
         /** A general built-in item in Settings. */
         data class CommonBuiltinItem(
             @DeviceSettingId override val settingId: Int,
+            override val highlighted: Boolean,
             override val preferenceKey: String,
         ) : BuiltinItem
 
         /** A bluetooth profiles in Settings. */
         data class BluetoothProfilesItem(
             @DeviceSettingId override val settingId: Int,
+            override val highlighted: Boolean,
             override val preferenceKey: String,
             val invisibleProfiles: List<String>,
         ) : BuiltinItem
     }
 
     /** A remote item provided by other apps. */
-    data class AppProvidedItem(@DeviceSettingId override val settingId: Int) :
-        DeviceSettingConfigItemModel
+    data class AppProvidedItem(
+        @DeviceSettingId override val settingId: Int,
+        override val highlighted: Boolean,
+    ) : DeviceSettingConfigItemModel
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservablePreferenceFragment.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservablePreferenceFragment.java
deleted file mode 100644
index 7994924..0000000
--- a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservablePreferenceFragment.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.core.lifecycle;
-
-
-import static androidx.lifecycle.Lifecycle.Event.ON_CREATE;
-import static androidx.lifecycle.Lifecycle.Event.ON_DESTROY;
-import static androidx.lifecycle.Lifecycle.Event.ON_PAUSE;
-import static androidx.lifecycle.Lifecycle.Event.ON_RESUME;
-import static androidx.lifecycle.Lifecycle.Event.ON_START;
-import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
-
-import android.annotation.CallSuper;
-import android.content.Context;
-import android.os.Bundle;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-
-import androidx.lifecycle.LifecycleOwner;
-import androidx.preference.PreferenceScreen;
-
-import com.android.settingslib.preference.PreferenceFragment;
-
-/**
- * Preference fragment that has hooks to observe fragment lifecycle events.
- */
-public abstract class ObservablePreferenceFragment extends PreferenceFragment
-        implements LifecycleOwner {
-
-    private final Lifecycle mLifecycle = new Lifecycle(this);
-
-    public Lifecycle getSettingsLifecycle() {
-        return mLifecycle;
-    }
-
-    @CallSuper
-    @Override
-    public void onAttach(Context context) {
-        super.onAttach(context);
-        mLifecycle.onAttach(context);
-    }
-
-    @CallSuper
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        mLifecycle.onCreate(savedInstanceState);
-        mLifecycle.handleLifecycleEvent(ON_CREATE);
-        super.onCreate(savedInstanceState);
-    }
-
-    @Override
-    public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
-        mLifecycle.setPreferenceScreen(preferenceScreen);
-        super.setPreferenceScreen(preferenceScreen);
-    }
-
-    @CallSuper
-    @Override
-    public void onSaveInstanceState(Bundle outState) {
-        super.onSaveInstanceState(outState);
-        mLifecycle.onSaveInstanceState(outState);
-    }
-
-    @CallSuper
-    @Override
-    public void onStart() {
-        mLifecycle.handleLifecycleEvent(ON_START);
-        super.onStart();
-    }
-
-    @CallSuper
-    @Override
-    public void onResume() {
-        mLifecycle.handleLifecycleEvent(ON_RESUME);
-        super.onResume();
-    }
-
-    @CallSuper
-    @Override
-    public void onPause() {
-        mLifecycle.handleLifecycleEvent(ON_PAUSE);
-        super.onPause();
-    }
-
-    @CallSuper
-    @Override
-    public void onStop() {
-        mLifecycle.handleLifecycleEvent(ON_STOP);
-        super.onStop();
-    }
-
-    @CallSuper
-    @Override
-    public void onDestroy() {
-        mLifecycle.handleLifecycleEvent(ON_DESTROY);
-        super.onDestroy();
-    }
-
-    @CallSuper
-    @Override
-    public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
-        mLifecycle.onCreateOptionsMenu(menu, inflater);
-        super.onCreateOptionsMenu(menu, inflater);
-    }
-
-    @CallSuper
-    @Override
-    public void onPrepareOptionsMenu(final Menu menu) {
-        mLifecycle.onPrepareOptionsMenu(menu);
-        super.onPrepareOptionsMenu(menu);
-    }
-
-    @CallSuper
-    @Override
-    public boolean onOptionsItemSelected(final MenuItem menuItem) {
-        boolean lifecycleHandled = mLifecycle.onOptionsItemSelected(menuItem);
-        if (!lifecycleHandled) {
-            return super.onOptionsItemSelected(menuItem);
-        }
-        return lifecycleHandled;
-    }
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java
new file mode 100644
index 0000000..766cd43
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java
@@ -0,0 +1,161 @@
+/*
+ * 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.settingslib.media;
+
+import static android.media.AudioDeviceInfo.TYPE_BUILTIN_MIC;
+import static android.media.AudioDeviceInfo.TYPE_USB_ACCESSORY;
+import static android.media.AudioDeviceInfo.TYPE_USB_DEVICE;
+import static android.media.AudioDeviceInfo.TYPE_USB_HEADSET;
+import static android.media.AudioDeviceInfo.TYPE_WIRED_HEADSET;
+
+import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.media.AudioDeviceInfo.AudioDeviceType;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.settingslib.R;
+
+/** {@link MediaDevice} implementation that represents an input device. */
+public class InputMediaDevice extends MediaDevice {
+
+    private static final String TAG = "InputMediaDevice";
+
+    private final String mId;
+
+    private final @AudioDeviceType int mAudioDeviceInfoType;
+
+    private final int mMaxVolume;
+
+    private final int mCurrentVolume;
+
+    private final boolean mIsVolumeFixed;
+
+    private InputMediaDevice(
+            @NonNull Context context,
+            @NonNull String id,
+            @AudioDeviceType int audioDeviceInfoType,
+            int maxVolume,
+            int currentVolume,
+            boolean isVolumeFixed) {
+        super(context, /* info= */ null, /* item= */ null);
+        mId = id;
+        mAudioDeviceInfoType = audioDeviceInfoType;
+        mMaxVolume = maxVolume;
+        mCurrentVolume = currentVolume;
+        mIsVolumeFixed = isVolumeFixed;
+        initDeviceRecord();
+    }
+
+    @Nullable
+    public static InputMediaDevice create(
+            @NonNull Context context,
+            @NonNull String id,
+            @AudioDeviceType int audioDeviceInfoType,
+            int maxVolume,
+            int currentVolume,
+            boolean isVolumeFixed) {
+        if (!isSupportedInputDevice(audioDeviceInfoType)) {
+            return null;
+        }
+
+        return new InputMediaDevice(
+                context, id, audioDeviceInfoType, maxVolume, currentVolume, isVolumeFixed);
+    }
+
+    public static boolean isSupportedInputDevice(@AudioDeviceType int audioDeviceInfoType) {
+        return switch (audioDeviceInfoType) {
+            case TYPE_BUILTIN_MIC,
+                            TYPE_WIRED_HEADSET,
+                            TYPE_USB_DEVICE,
+                            TYPE_USB_HEADSET,
+                            TYPE_USB_ACCESSORY ->
+                    true;
+            default -> false;
+        };
+    }
+
+    @Override
+    public @NonNull String getName() {
+        CharSequence name =
+                switch (mAudioDeviceInfoType) {
+                    case TYPE_WIRED_HEADSET ->
+                            mContext.getString(R.string.media_transfer_wired_device_mic_name);
+                    case TYPE_USB_DEVICE, TYPE_USB_HEADSET, TYPE_USB_ACCESSORY ->
+                            mContext.getString(R.string.media_transfer_usb_device_mic_name);
+                    default -> mContext.getString(R.string.media_transfer_internal_mic);
+                };
+        return name.toString();
+    }
+
+    @Override
+    public @SelectionBehavior int getSelectionBehavior() {
+        // We don't allow apps to override the selection behavior of system routes.
+        return SELECTION_BEHAVIOR_TRANSFER;
+    }
+
+    @Override
+    public @NonNull String getSummary() {
+        return "";
+    }
+
+    @Override
+    public @Nullable Drawable getIcon() {
+        return getIconWithoutBackground();
+    }
+
+    @Override
+    public @Nullable Drawable getIconWithoutBackground() {
+        return mContext.getDrawable(getDrawableResId());
+    }
+
+    @VisibleForTesting
+    int getDrawableResId() {
+        // TODO(b/357122624): check with UX to obtain the icon for desktop devices.
+        return R.drawable.ic_media_tablet;
+    }
+
+    @Override
+    public @NonNull String getId() {
+        return mId;
+    }
+
+    @Override
+    public boolean isConnected() {
+        // Indicating if the device is connected and thus showing the status of STATE_CONNECTED.
+        // Upon creation, this device is already connected.
+        return true;
+    }
+
+    @Override
+    public int getMaxVolume() {
+        return mMaxVolume;
+    }
+
+    @Override
+    public int getCurrentVolume() {
+        return mCurrentVolume;
+    }
+
+    @Override
+    public boolean isVolumeFixed() {
+        return mIsVolumeFixed;
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java
new file mode 100644
index 0000000..548eb3f
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java
@@ -0,0 +1,126 @@
+/*
+ * 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.settingslib.media;
+
+import android.content.Context;
+import android.media.AudioDeviceCallback;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.os.Handler;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/** Provides functionalities to get/observe input routes, control input routing and volume gain. */
+public final class InputRouteManager {
+
+    private static final String TAG = "InputRouteManager";
+
+    private final Context mContext;
+
+    private final AudioManager mAudioManager;
+
+    @VisibleForTesting final List<MediaDevice> mInputMediaDevices = new CopyOnWriteArrayList<>();
+
+    private final Collection<InputDeviceCallback> mCallbacks = new CopyOnWriteArrayList<>();
+
+    @VisibleForTesting
+    final AudioDeviceCallback mAudioDeviceCallback =
+            new AudioDeviceCallback() {
+                @Override
+                public void onAudioDevicesAdded(@NonNull AudioDeviceInfo[] addedDevices) {
+                    dispatchInputDeviceListUpdate();
+                }
+
+                @Override
+                public void onAudioDevicesRemoved(@NonNull AudioDeviceInfo[] removedDevices) {
+                    dispatchInputDeviceListUpdate();
+                }
+            };
+
+    /* package */ InputRouteManager(@NonNull Context context, @NonNull AudioManager audioManager) {
+        mContext = context;
+        mAudioManager = audioManager;
+        Handler handler = new Handler(context.getMainLooper());
+
+        mAudioManager.registerAudioDeviceCallback(mAudioDeviceCallback, handler);
+    }
+
+    public void registerCallback(@NonNull InputDeviceCallback callback) {
+        if (!mCallbacks.contains(callback)) {
+            mCallbacks.add(callback);
+            dispatchInputDeviceListUpdate();
+        }
+    }
+
+    public void unregisterCallback(@NonNull InputDeviceCallback callback) {
+        mCallbacks.remove(callback);
+    }
+
+    private void dispatchInputDeviceListUpdate() {
+        // TODO (b/360175574): Get selected input device.
+
+        // Get all input devices.
+        AudioDeviceInfo[] audioDeviceInfos =
+                mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
+        mInputMediaDevices.clear();
+        for (AudioDeviceInfo info : audioDeviceInfos) {
+            MediaDevice mediaDevice =
+                    InputMediaDevice.create(
+                            mContext,
+                            String.valueOf(info.getId()),
+                            info.getType(),
+                            getMaxInputGain(),
+                            getCurrentInputGain(),
+                            isInputGainFixed());
+            if (mediaDevice != null) {
+                mInputMediaDevices.add(mediaDevice);
+            }
+        }
+
+        final List<MediaDevice> inputMediaDevices = new ArrayList<>(mInputMediaDevices);
+        for (InputDeviceCallback callback : mCallbacks) {
+            callback.onInputDeviceListUpdated(inputMediaDevices);
+        }
+    }
+
+    public int getMaxInputGain() {
+        // TODO (b/357123335): use real input gain implementation.
+        // Using 15 for now since it matches the max index for output.
+        return 15;
+    }
+
+    public int getCurrentInputGain() {
+        // TODO (b/357123335): use real input gain implementation.
+        return 8;
+    }
+
+    public boolean isInputGainFixed() {
+        // TODO (b/357123335): use real input gain implementation.
+        return true;
+    }
+
+    /** Callback for listening to input device changes. */
+    public interface InputDeviceCallback {
+        void onInputDeviceListUpdated(@NonNull List<MediaDevice> devices);
+    }
+}
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 7b2a284..3cc111f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
@@ -140,7 +140,7 @@
 
     private static Status computeStatus(@NonNull ZenModeConfig.ZenRule zenRuleExtraData) {
         if (zenRuleExtraData.enabled) {
-            if (zenRuleExtraData.isAutomaticActive()) {
+            if (zenRuleExtraData.isActive()) {
                 return Status.ENABLED_AND_ACTIVE;
             } else {
                 return Status.ENABLED;
@@ -241,10 +241,6 @@
                         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();
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/TEST_MAPPING b/packages/SettingsLib/src/com/android/settingslib/users/TEST_MAPPING
index 71cbcb5..1346ee5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/TEST_MAPPING
+++ b/packages/SettingsLib/src/com/android/settingslib/users/TEST_MAPPING
@@ -1,12 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "SettingsLibTests",
-      "options": [
-        {
-          "include-filter": "com.android.settingslib.users."
-        }
-      ]
+      "name": "SettingsLibTests_settingslib_users"
     }
   ]
 }
\ No newline at end of file
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 698eb81..b180b69 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
@@ -18,31 +18,51 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
+import static org.robolectric.Shadows.shadowOf;
 
+import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothCsipSetCoordinator;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
+import android.bluetooth.BluetoothLeBroadcastReceiveState;
 import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothStatusCodes;
 import android.content.Context;
+import android.os.Looper;
 import android.os.Parcel;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import com.android.settingslib.flags.Flags;
+import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter;
+
+import com.google.common.collect.ImmutableList;
 
 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.MockitoAnnotations;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
 
 import java.util.ArrayList;
 import java.util.List;
 
 @RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowBluetoothAdapter.class})
 public class CsipDeviceManagerTest {
     private final static String DEVICE_NAME_1 = "TestName_1";
     private final static String DEVICE_NAME_2 = "TestName_2";
@@ -59,6 +79,9 @@
     private final BluetoothClass DEVICE_CLASS_2 =
             createBtClass(BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE);
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Mock
     private LocalBluetoothManager mLocalBluetoothManager;
     @Mock
@@ -77,7 +100,12 @@
     private A2dpProfile mA2dpProfile;
     @Mock
     private LeAudioProfile mLeAudioProfile;
+    @Mock
+    private LocalBluetoothLeBroadcast mBroadcast;
+    @Mock
+    private LocalBluetoothLeBroadcastAssistant mAssistant;
 
+    private ShadowBluetoothAdapter mShadowBluetoothAdapter;
     private CachedBluetoothDevice mCachedDevice1;
     private CachedBluetoothDevice mCachedDevice2;
     private CachedBluetoothDevice mCachedDevice3;
@@ -101,6 +129,12 @@
         MockitoAnnotations.initMocks(this);
 
         mContext = RuntimeEnvironment.application;
+        mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
+        mShadowBluetoothAdapter.setEnabled(true);
+        mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
+                BluetoothStatusCodes.FEATURE_SUPPORTED);
+        mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
+                BluetoothStatusCodes.FEATURE_SUPPORTED);
         when(mDevice1.getAddress()).thenReturn(DEVICE_ADDRESS_1);
         when(mDevice2.getAddress()).thenReturn(DEVICE_ADDRESS_2);
         when(mDevice3.getAddress()).thenReturn(DEVICE_ADDRESS_3);
@@ -124,6 +158,8 @@
         when(mLocalProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile);
         when(mLocalProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile);
         when(mLocalProfileManager.getHeadsetProfile()).thenReturn(mHfpProfile);
+        when(mLocalProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(mAssistant);
+        when(mLocalProfileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast);
 
         when(mLeAudioProfile.getConnectedGroupLeadDevice(anyInt())).thenReturn(null);
         mCachedDeviceManager = new CachedBluetoothDeviceManager(mContext, mLocalBluetoothManager);
@@ -307,6 +343,7 @@
         mCachedDevices.add(preferredDevice);
         mCachedDevices.add(mCachedDevice2);
         mCachedDevices.add(mCachedDevice3);
+        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
 
         assertThat(mCsipDeviceManager.addMemberDevicesIntoMainDevice(GROUP1, preferredDevice))
                 .isTrue();
@@ -314,6 +351,36 @@
         assertThat(mCachedDevices.contains(mCachedDevice2)).isFalse();
         assertThat(mCachedDevices.contains(mCachedDevice3)).isTrue();
         assertThat(preferredDevice.getMemberDevice()).contains(mCachedDevice2);
+        verify(mAssistant, never()).addSource(any(BluetoothDevice.class),
+                any(BluetoothLeBroadcastMetadata.class), anyBoolean());
+    }
+
+    @Test
+    public void addMemberDevicesIntoMainDevice_preferredDeviceIsMainAndTwoMain_syncSource() {
+        // Condition: The preferredDevice is main and there is another main device in top list
+        // Expected Result: return true and there is the preferredDevice in top list
+        CachedBluetoothDevice preferredDevice = mCachedDevice1;
+        mCachedDevice1.getMemberDevice().clear();
+        mCachedDevices.clear();
+        mCachedDevices.add(preferredDevice);
+        mCachedDevices.add(mCachedDevice2);
+        mCachedDevices.add(mCachedDevice3);
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+        when(mBroadcast.isEnabled(null)).thenReturn(true);
+        BluetoothLeBroadcastMetadata metadata = Mockito.mock(BluetoothLeBroadcastMetadata.class);
+        when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(metadata);
+        BluetoothLeBroadcastReceiveState state = Mockito.mock(
+                BluetoothLeBroadcastReceiveState.class);
+        when(state.getBisSyncState()).thenReturn(ImmutableList.of(1L));
+        when(mAssistant.getAllSources(mDevice2)).thenReturn(ImmutableList.of(state));
+
+        assertThat(mCsipDeviceManager.addMemberDevicesIntoMainDevice(GROUP1, preferredDevice))
+                .isTrue();
+        assertThat(mCachedDevices.contains(preferredDevice)).isTrue();
+        assertThat(mCachedDevices.contains(mCachedDevice2)).isFalse();
+        assertThat(mCachedDevices.contains(mCachedDevice3)).isTrue();
+        assertThat(preferredDevice.getMemberDevice()).contains(mCachedDevice2);
+        verify(mAssistant).addSource(mDevice1, metadata, /* isGroupOp= */ false);
     }
 
     @Test
@@ -341,6 +408,8 @@
         CachedBluetoothDevice preferredDevice = mCachedDevice2;
         BluetoothDevice expectedMainBluetoothDevice = preferredDevice.getDevice();
         mCachedDevice3.setGroupId(GROUP1);
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+        when(mBroadcast.isEnabled(null)).thenReturn(false);
 
         assertThat(mCsipDeviceManager.addMemberDevicesIntoMainDevice(GROUP1, preferredDevice))
                 .isTrue();
@@ -351,8 +420,40 @@
         assertThat(mCachedDevices.contains(mCachedDevice3)).isFalse();
         assertThat(mCachedDevice1.getMemberDevice()).contains(mCachedDevice2);
         assertThat(mCachedDevice1.getMemberDevice()).contains(mCachedDevice3);
+        assertThat(mCachedDevice1.getDevice()).isEqualTo(expectedMainBluetoothDevice);
+        verify(mAssistant, never()).addSource(any(BluetoothDevice.class),
+                any(BluetoothLeBroadcastMetadata.class), anyBoolean());
+    }
+
+    @Test
+    public void addMemberDevicesIntoMainDevice_preferredDeviceIsMemberAndTwoMain_syncSource() {
+        // Condition: The preferredDevice is member and there are two main device in top list
+        // Expected Result: return true and there is the preferredDevice in top list
+        CachedBluetoothDevice preferredDevice = mCachedDevice2;
+        BluetoothDevice expectedMainBluetoothDevice = preferredDevice.getDevice();
+        mCachedDevice3.setGroupId(GROUP1);
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+        when(mBroadcast.isEnabled(null)).thenReturn(true);
+        BluetoothLeBroadcastMetadata metadata = Mockito.mock(BluetoothLeBroadcastMetadata.class);
+        when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(metadata);
+        BluetoothLeBroadcastReceiveState state = Mockito.mock(
+                BluetoothLeBroadcastReceiveState.class);
+        when(state.getBisSyncState()).thenReturn(ImmutableList.of(1L));
+        when(mAssistant.getAllSources(mDevice1)).thenReturn(ImmutableList.of(state));
+
+        assertThat(mCsipDeviceManager.addMemberDevicesIntoMainDevice(GROUP1, preferredDevice))
+                .isTrue();
+        shadowOf(Looper.getMainLooper()).idle();
+        // expected main is mCachedDevice1 which is the main of preferredDevice, since system
+        // switch the relationship between preferredDevice and the main of preferredDevice
+        assertThat(mCachedDevices.contains(mCachedDevice1)).isTrue();
+        assertThat(mCachedDevices.contains(mCachedDevice2)).isFalse();
+        assertThat(mCachedDevices.contains(mCachedDevice3)).isFalse();
+        assertThat(mCachedDevice1.getMemberDevice()).contains(mCachedDevice2);
         assertThat(mCachedDevice1.getMemberDevice()).contains(mCachedDevice3);
         assertThat(mCachedDevice1.getDevice()).isEqualTo(expectedMainBluetoothDevice);
+        verify(mAssistant).addSource(mDevice2, metadata, /* isGroupOp= */ false);
+        verify(mAssistant).addSource(mDevice3, metadata, /* isGroupOp= */ false);
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingItemTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingItemTest.kt
index 56e9b6c..86071bb 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingItemTest.kt
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingItemTest.kt
@@ -34,6 +34,8 @@
                 packageName = "package_name",
                 className = "class_name",
                 intentAction = "intent_action",
+                preferenceKey = "key1",
+                highlighted = true,
                 extras = Bundle().apply { putString("key1", "value1") },
             )
 
@@ -43,6 +45,7 @@
         assertThat(fromParcel.packageName).isEqualTo(item.packageName)
         assertThat(fromParcel.className).isEqualTo(item.className)
         assertThat(fromParcel.intentAction).isEqualTo(item.intentAction)
+        assertThat(fromParcel.preferenceKey).isEqualTo(item.preferenceKey)
         assertThat(fromParcel.extras.getString("key1")).isEqualTo(item.extras.getString("key1"))
     }
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigTest.kt
index a0a2658..7f17293 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigTest.kt
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigTest.kt
@@ -33,30 +33,33 @@
                 mainContentItems =
                     listOf(
                         DeviceSettingItem(
-                            1,
-                            "package_name_1",
-                            "class_name_1",
-                            "intent_action_1",
-                            null,
-                            Bundle(),
+                            settingId = 1,
+                            packageName = "package_name_1",
+                            className = "class_name_1",
+                            intentAction = "intent_action_1",
+                            preferenceKey = null,
+                            highlighted = false,
+                            extras = Bundle(),
                         )),
                 moreSettingsItems =
                     listOf(
                         DeviceSettingItem(
-                            2,
-                            "package_name_2",
-                            "class_name_2",
-                            "intent_action_2",
-                            null,
-                            Bundle(),
+                            settingId = 2,
+                            packageName = "package_name_2",
+                            className = "class_name_2",
+                            intentAction = "intent_action_2",
+                            preferenceKey = null,
+                            highlighted = false,
+                            extras = Bundle(),
                         )),
                 moreSettingsHelpItem = DeviceSettingItem(
-                    3,
-                    "package_name_2",
-                    "class_name_2",
-                    "intent_action_2",
-                    null,
-                    Bundle(),
+                    settingId = 3,
+                    packageName = "package_name_2",
+                    className = "class_name_2",
+                    intentAction = "intent_action_2",
+                    preferenceKey = null,
+                    highlighted = false,
+                    extras = Bundle(),
                 ),
                 extras = Bundle().apply { putString("key1", "value1") },
             )
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java
new file mode 100644
index 0000000..bc1ea6c
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java
@@ -0,0 +1,114 @@
+/*
+ * 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.settingslib.media;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.media.AudioDeviceInfo;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import com.android.settingslib.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class InputMediaDeviceTest {
+
+    private final int BUILTIN_MIC_ID = 1;
+    private final int WIRED_HEADSET_ID = 2;
+    private final int USB_HEADSET_ID = 3;
+    private final int MAX_VOLUME = 1;
+    private final int CURRENT_VOLUME = 0;
+    private final boolean IS_VOLUME_FIXED = true;
+
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+    }
+
+    @Test
+    public void getDrawableResId_returnCorrectResId() {
+        InputMediaDevice builtinMediaDevice =
+                InputMediaDevice.create(
+                        mContext,
+                        String.valueOf(BUILTIN_MIC_ID),
+                        AudioDeviceInfo.TYPE_BUILTIN_MIC,
+                        MAX_VOLUME,
+                        CURRENT_VOLUME,
+                        IS_VOLUME_FIXED);
+        assertThat(builtinMediaDevice).isNotNull();
+        assertThat(builtinMediaDevice.getDrawableResId()).isEqualTo(R.drawable.ic_media_tablet);
+    }
+
+    @Test
+    public void getName_returnCorrectName_builtinMic() {
+        InputMediaDevice builtinMediaDevice =
+                InputMediaDevice.create(
+                        mContext,
+                        String.valueOf(BUILTIN_MIC_ID),
+                        AudioDeviceInfo.TYPE_BUILTIN_MIC,
+                        MAX_VOLUME,
+                        CURRENT_VOLUME,
+                        IS_VOLUME_FIXED);
+        assertThat(builtinMediaDevice).isNotNull();
+        assertThat(builtinMediaDevice.getName())
+                .isEqualTo(mContext.getString(R.string.media_transfer_internal_mic));
+    }
+
+    @Test
+    public void getName_returnCorrectName_wiredHeadset() {
+        InputMediaDevice wiredMediaDevice =
+                InputMediaDevice.create(
+                        mContext,
+                        String.valueOf(WIRED_HEADSET_ID),
+                        AudioDeviceInfo.TYPE_WIRED_HEADSET,
+                        MAX_VOLUME,
+                        CURRENT_VOLUME,
+                        IS_VOLUME_FIXED);
+        assertThat(wiredMediaDevice).isNotNull();
+        assertThat(wiredMediaDevice.getName())
+                .isEqualTo(mContext.getString(R.string.media_transfer_wired_device_mic_name));
+    }
+
+    @Test
+    public void getName_returnCorrectName_usbHeadset() {
+        InputMediaDevice usbMediaDevice =
+                InputMediaDevice.create(
+                        mContext,
+                        String.valueOf(USB_HEADSET_ID),
+                        AudioDeviceInfo.TYPE_USB_HEADSET,
+                        MAX_VOLUME,
+                        CURRENT_VOLUME,
+                        IS_VOLUME_FIXED);
+        assertThat(usbMediaDevice).isNotNull();
+        assertThat(usbMediaDevice.getName())
+                .isEqualTo(mContext.getString(R.string.media_transfer_usb_device_mic_name));
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java
new file mode 100644
index 0000000..2501ae6
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java
@@ -0,0 +1,140 @@
+/*
+ * 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.settingslib.media;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+
+import com.android.settingslib.testutils.shadow.ShadowRouter2Manager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowRouter2Manager.class})
+public class InputRouteManagerTest {
+    private static final int BUILTIN_MIC_ID = 1;
+    private static final int INPUT_WIRED_HEADSET_ID = 2;
+    private static final int INPUT_USB_DEVICE_ID = 3;
+    private static final int INPUT_USB_HEADSET_ID = 4;
+    private static final int INPUT_USB_ACCESSORY_ID = 5;
+
+    private final Context mContext = spy(RuntimeEnvironment.application);
+    private InputRouteManager mInputRouteManager;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        final AudioManager audioManager = mock(AudioManager.class);
+        mInputRouteManager = new InputRouteManager(mContext, audioManager);
+    }
+
+    @Test
+    public void onAudioDevicesAdded_shouldUpdateInputMediaDevice() {
+        final AudioDeviceInfo info1 = mock(AudioDeviceInfo.class);
+        when(info1.getType()).thenReturn(AudioDeviceInfo.TYPE_BUILTIN_MIC);
+        when(info1.getId()).thenReturn(BUILTIN_MIC_ID);
+
+        final AudioDeviceInfo info2 = mock(AudioDeviceInfo.class);
+        when(info2.getType()).thenReturn(AudioDeviceInfo.TYPE_WIRED_HEADSET);
+        when(info2.getId()).thenReturn(INPUT_WIRED_HEADSET_ID);
+
+        final AudioDeviceInfo info3 = mock(AudioDeviceInfo.class);
+        when(info3.getType()).thenReturn(AudioDeviceInfo.TYPE_USB_DEVICE);
+        when(info3.getId()).thenReturn(INPUT_USB_DEVICE_ID);
+
+        final AudioDeviceInfo info4 = mock(AudioDeviceInfo.class);
+        when(info4.getType()).thenReturn(AudioDeviceInfo.TYPE_USB_HEADSET);
+        when(info4.getId()).thenReturn(INPUT_USB_HEADSET_ID);
+
+        final AudioDeviceInfo info5 = mock(AudioDeviceInfo.class);
+        when(info5.getType()).thenReturn(AudioDeviceInfo.TYPE_USB_ACCESSORY);
+        when(info5.getId()).thenReturn(INPUT_USB_ACCESSORY_ID);
+
+        final AudioDeviceInfo unsupportedInfo = mock(AudioDeviceInfo.class);
+        when(unsupportedInfo.getType()).thenReturn(AudioDeviceInfo.TYPE_HDMI);
+
+        final AudioManager audioManager = mock(AudioManager.class);
+        AudioDeviceInfo[] devices = {info1, info2, info3, info4, info5, unsupportedInfo};
+        when(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn(devices);
+
+        InputRouteManager inputRouteManager = new InputRouteManager(mContext, audioManager);
+
+        assertThat(inputRouteManager.mInputMediaDevices).isEmpty();
+
+        inputRouteManager.mAudioDeviceCallback.onAudioDevicesAdded(devices);
+
+        // The unsupported info should be filtered out.
+        assertThat(inputRouteManager.mInputMediaDevices).hasSize(devices.length - 1);
+        assertThat(inputRouteManager.mInputMediaDevices.get(0).getId())
+                .isEqualTo(String.valueOf(BUILTIN_MIC_ID));
+        assertThat(inputRouteManager.mInputMediaDevices.get(1).getId())
+                .isEqualTo(String.valueOf(INPUT_WIRED_HEADSET_ID));
+        assertThat(inputRouteManager.mInputMediaDevices.get(2).getId())
+                .isEqualTo(String.valueOf(INPUT_USB_DEVICE_ID));
+        assertThat(inputRouteManager.mInputMediaDevices.get(3).getId())
+                .isEqualTo(String.valueOf(INPUT_USB_HEADSET_ID));
+        assertThat(inputRouteManager.mInputMediaDevices.get(4).getId())
+                .isEqualTo(String.valueOf(INPUT_USB_ACCESSORY_ID));
+    }
+
+    @Test
+    public void onAudioDevicesRemoved_shouldUpdateInputMediaDevice() {
+        final AudioManager audioManager = mock(AudioManager.class);
+        when(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS))
+                .thenReturn(new AudioDeviceInfo[] {});
+
+        InputRouteManager inputRouteManager = new InputRouteManager(mContext, audioManager);
+
+        final MediaDevice device = mock(MediaDevice.class);
+        inputRouteManager.mInputMediaDevices.add(device);
+
+        final AudioDeviceInfo info = mock(AudioDeviceInfo.class);
+        when(info.getType()).thenReturn(AudioDeviceInfo.TYPE_WIRED_HEADSET);
+        inputRouteManager.mAudioDeviceCallback.onAudioDevicesRemoved(new AudioDeviceInfo[] {info});
+
+        assertThat(inputRouteManager.mInputMediaDevices).isEmpty();
+    }
+
+    @Test
+    public void getMaxInputGain_returnMaxInputGain() {
+        assertThat(mInputRouteManager.getMaxInputGain()).isEqualTo(15);
+    }
+
+    @Test
+    public void getCurrentInputGain_returnCurrentInputGain() {
+        assertThat(mInputRouteManager.getCurrentInputGain()).isEqualTo(8);
+    }
+
+    @Test
+    public void isInputGainFixed() {
+        assertThat(mInputRouteManager.isInputGainFixed()).isTrue();
+    }
+}
diff --git a/packages/SettingsProvider/TEST_MAPPING b/packages/SettingsProvider/TEST_MAPPING
index 0eed2b7..cf9ed2e 100644
--- a/packages/SettingsProvider/TEST_MAPPING
+++ b/packages/SettingsProvider/TEST_MAPPING
@@ -4,12 +4,7 @@
             "name": "SettingsProviderTest"
         },
         {
-            "name": "CtsProviderTestCases",
-            "options": [
-                {
-                    "include-filter": "android.provider.cts.settings."
-                }
-            ]
+            "name": "CtsProviderTestCases_cts_settings"
         }
     ],
     "postsubmit": [
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
index 62401a1..aca26ec 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
+++ b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
@@ -91,3 +91,13 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "support_local_overrides_sysprops"
+    namespace: "core_experiments_team_internal"
+    description: "When DeviceConfig overrides are deleted, delete new storage overrides too."
+    bug: "366022906"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index d39b564..b491b5a 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -223,6 +223,7 @@
                     Settings.Global.ENABLE_DELETION_HELPER_NO_THRESHOLD_TOGGLE,
                     Settings.Global.ENABLE_DISKSTATS_LOGGING,
                     Settings.Global.ENABLE_EPHEMERAL_FEATURE,
+                    Settings.Global.ENABLE_USE_APP_INFO_NOT_LAUNCHED,
                     Settings.Global.DYNAMIC_POWER_SAVINGS_ENABLED,
                     Settings.Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD,
                     Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS,
diff --git a/packages/Shell/TEST_MAPPING b/packages/Shell/TEST_MAPPING
index 9bb1b4b..6b9f1eb 100644
--- a/packages/Shell/TEST_MAPPING
+++ b/packages/Shell/TEST_MAPPING
@@ -1,23 +1,10 @@
 {
   "presubmit": [
     {
-      "name": "CtsBugreportTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.LargeTest"
-        }
-      ]
+      "name": "CtsBugreportTestCases_android_server_os"
     },
     {
-      "name": "ShellTests",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.LargeTest"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "ShellTests_android_server_os"
     },
     {
       "name": "CtsUiAutomationTestCases",
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index be4e9a1..f59eab0 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -934,9 +934,9 @@
         "androidx.compose.runtime_runtime",
     ],
     libs: [
-        "android.test.runner",
-        "android.test.base",
-        "android.test.mock",
+        "android.test.runner.stubs.system",
+        "android.test.base.stubs.system",
+        "android.test.mock.stubs.system",
         "truth",
     ],
 
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 16dd4e5..07a1e63 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -21,15 +21,7 @@
   // v2/android-virtual-infra/test_mapping/presubmit-avd
   "presubmit": [
     {
-      "name": "SystemUIGoogleTests",
-      "options": [
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "SystemUIGoogleTests"
     },
     {
       // Permission indicators
@@ -48,15 +40,7 @@
     },
     {
       // Permission indicators
-      "name": "CtsVoiceRecognitionTestCases",
-      "options": [
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsVoiceRecognitionTestCases"
     }
   ],
 
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/TEST_MAPPING b/packages/SystemUI/accessibility/accessibilitymenu/TEST_MAPPING
index 4a10108..1820f39 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/TEST_MAPPING
+++ b/packages/SystemUI/accessibility/accessibilitymenu/TEST_MAPPING
@@ -2,12 +2,7 @@
   // TODO: b/324945360 - Re-enable on presubmit after fixing failures
   "postsubmit": [
     {
-      "name": "AccessibilityMenuServiceTests",
-      "options": [
-        {
-          "exclude-annotation": "android.support.test.filters.FlakyTest"
-        }
-      ]
+      "name": "AccessibilityMenuServiceTests"
     }
   ]
 }
\ No newline at end of file
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 02e8cd6..7974f92 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1313,6 +1313,16 @@
 }
 
 flag {
+   name: "sim_pin_bouncer_reset"
+   namespace: "systemui"
+   description: "The SIM PIN bouncer does not close after unlocking"
+   bug: "297461589"
+   metadata {
+        purpose: PURPOSE_BUGFIX
+   }
+}
+
+flag {
    name: "use_transitions_for_keyguard_occluded"
    namespace: "systemui"
    description: "Use Keyguard Transitions to set Notification Shade occlusion state"
diff --git a/packages/SystemUI/animation/lib/Android.bp b/packages/SystemUI/animation/lib/Android.bp
new file mode 100644
index 0000000..4324d463
--- /dev/null
+++ b/packages/SystemUI/animation/lib/Android.bp
@@ -0,0 +1,41 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+java_library {
+    name: "PlatformAnimationLib-server",
+    srcs: [
+        "src/com/android/systemui/animation/server/*.java",
+        ":PlatformAnimationLib-aidl",
+    ],
+    static_libs: [
+        "WindowManager-Shell-shared",
+    ],
+}
+
+filegroup {
+    name: "PlatformAnimationLib-aidl",
+    srcs: [
+        "src/**/*.aidl",
+    ],
+}
diff --git a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/server/IOriginTransitionsImpl.java b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/server/IOriginTransitionsImpl.java
new file mode 100644
index 0000000..3cbb688
--- /dev/null
+++ b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/server/IOriginTransitionsImpl.java
@@ -0,0 +1,424 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.animation.server;
+
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
+
+import android.Manifest;
+import android.annotation.Nullable;
+import android.app.TaskInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.view.SurfaceControl;
+import android.window.IRemoteTransition;
+import android.window.IRemoteTransitionFinishedCallback;
+import android.window.RemoteTransition;
+import android.window.TransitionFilter;
+import android.window.TransitionInfo;
+import android.window.TransitionInfo.Change;
+import android.window.WindowAnimationState;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.systemui.animation.shared.IOriginTransitions;
+import com.android.wm.shell.shared.ShellTransitions;
+import com.android.wm.shell.shared.TransitionUtil;
+
+import java.util.Map;
+import java.util.concurrent.Executor;
+import java.util.function.Predicate;
+
+/** An implementation of the {@link IOriginTransitions}. */
+public class IOriginTransitionsImpl extends IOriginTransitions.Stub {
+    private static final boolean DEBUG = true;
+    private static final String TAG = "OriginTransitions";
+
+    private final Object mLock = new Object();
+    private final ShellTransitions mShellTransitions;
+    private final Context mContext;
+
+    @GuardedBy("mLock")
+    private final Map<IBinder, OriginTransitionRecord> mRecords = new ArrayMap<>();
+
+    public IOriginTransitionsImpl(Context context, ShellTransitions shellTransitions) {
+        mShellTransitions = shellTransitions;
+        mContext = context;
+    }
+
+    @Override
+    public RemoteTransition makeOriginTransition(
+            RemoteTransition launchTransition, RemoteTransition returnTransition)
+            throws RemoteException {
+        if (DEBUG) {
+            Log.d(
+                    TAG,
+                    "makeOriginTransition: (" + launchTransition + ", " + returnTransition + ")");
+        }
+        enforceRemoteTransitionPermission();
+        synchronized (mLock) {
+            OriginTransitionRecord record =
+                    new OriginTransitionRecord(launchTransition, returnTransition);
+            mRecords.put(record.getToken(), record);
+            return record.asLaunchableTransition();
+        }
+    }
+
+    @Override
+    public void cancelOriginTransition(RemoteTransition originTransition) {
+        if (DEBUG) {
+            Log.d(TAG, "cancelOriginTransition: " + originTransition);
+        }
+        enforceRemoteTransitionPermission();
+        synchronized (mLock) {
+            if (!mRecords.containsKey(originTransition.asBinder())) {
+                return;
+            }
+            mRecords.get(originTransition.asBinder()).destroy();
+        }
+    }
+
+    private void enforceRemoteTransitionPermission() {
+        mContext.enforceCallingPermission(
+                Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS,
+                "Missing permission "
+                        + Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS);
+    }
+
+    public void dump(IndentingPrintWriter ipw) {
+        ipw.println("IOriginTransitionsImpl");
+        ipw.println("Active records:");
+        ipw.increaseIndent();
+        synchronized (mLock) {
+            if (mRecords.isEmpty()) {
+                ipw.println("none");
+            } else {
+                for (OriginTransitionRecord record : mRecords.values()) {
+                    record.dump(ipw);
+                }
+            }
+        }
+        ipw.decreaseIndent();
+    }
+
+    /**
+     * An {@link IRemoteTransition} that delegates animation to another {@link IRemoteTransition}
+     * and notify callbacks when the transition starts.
+     */
+    private static class RemoteTransitionDelegate extends IRemoteTransition.Stub {
+        private final IRemoteTransition mTransition;
+        private final Predicate<TransitionInfo> mOnStarting;
+        private final Executor mExecutor;
+
+        RemoteTransitionDelegate(
+                Executor executor,
+                IRemoteTransition transition,
+                Predicate<TransitionInfo> onStarting) {
+            mExecutor = executor;
+            mTransition = transition;
+            mOnStarting = onStarting;
+        }
+
+        @Override
+        public void startAnimation(
+                IBinder token,
+                TransitionInfo info,
+                SurfaceControl.Transaction t,
+                IRemoteTransitionFinishedCallback finishCallback)
+                throws RemoteException {
+            if (DEBUG) {
+                Log.d(TAG, "startAnimation: " + info);
+            }
+            if (!mOnStarting.test(info)) {
+                Log.w(TAG, "Skipping cancelled transition " + mTransition);
+                t.addTransactionCommittedListener(
+                                mExecutor,
+                                () -> {
+                                    try {
+                                        finishCallback.onTransitionFinished(null, null);
+                                    } catch (RemoteException e) {
+                                        Log.e(TAG, "Unable to report finish.", e);
+                                    }
+                                })
+                        .apply();
+                return;
+            }
+            mTransition.startAnimation(token, info, t, finishCallback);
+        }
+
+        @Override
+        public void mergeAnimation(
+                IBinder transition,
+                TransitionInfo info,
+                SurfaceControl.Transaction t,
+                IBinder mergeTarget,
+                IRemoteTransitionFinishedCallback finishCallback)
+                throws RemoteException {
+            if (DEBUG) {
+                Log.d(TAG, "mergeAnimation: " + info);
+            }
+            mTransition.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+        }
+
+        @Override
+        public void takeOverAnimation(
+                IBinder transition,
+                TransitionInfo info,
+                SurfaceControl.Transaction t,
+                IRemoteTransitionFinishedCallback finishCallback,
+                WindowAnimationState[] states)
+                throws RemoteException {
+            if (DEBUG) {
+                Log.d(TAG, "takeOverAnimation: " + info);
+            }
+            mTransition.takeOverAnimation(transition, info, t, finishCallback, states);
+        }
+
+        @Override
+        public void onTransitionConsumed(IBinder transition, boolean aborted)
+                throws RemoteException {
+            if (DEBUG) {
+                Log.d(TAG, "onTransitionConsumed: aborted=" + aborted);
+            }
+            mTransition.onTransitionConsumed(transition, aborted);
+        }
+
+        @Override
+        public String toString() {
+            return "RemoteTransitionDelegate{transition=" + mTransition + "}";
+        }
+    }
+
+    /** A data record containing the origin transition pieces. */
+    private class OriginTransitionRecord implements IBinder.DeathRecipient {
+        private final RemoteTransition mWrappedLaunchTransition;
+        private final RemoteTransition mWrappedReturnTransition;
+
+        @GuardedBy("mLock")
+        private boolean mDestroyed;
+
+        OriginTransitionRecord(RemoteTransition launchTransition, RemoteTransition returnTransition)
+                throws RemoteException {
+            mWrappedLaunchTransition = wrap(launchTransition, this::onLaunchTransitionStarting);
+            mWrappedReturnTransition = wrap(returnTransition, this::onReturnTransitionStarting);
+            linkToDeath();
+        }
+
+        private boolean onLaunchTransitionStarting(TransitionInfo info) {
+            synchronized (mLock) {
+                if (mDestroyed) {
+                    return false;
+                }
+                TransitionFilter filter = createFilterForReverseTransition(info);
+                if (filter != null) {
+                    if (DEBUG) {
+                        Log.d(TAG, "Registering filter " + filter);
+                    }
+                    mShellTransitions.registerRemote(filter, mWrappedReturnTransition);
+                }
+                return true;
+            }
+        }
+
+        private boolean onReturnTransitionStarting(TransitionInfo info) {
+            synchronized (mLock) {
+                if (mDestroyed) {
+                    return false;
+                }
+                // Clean up stuff.
+                destroy();
+                return true;
+            }
+        }
+
+        public void destroy() {
+            synchronized (mLock) {
+                if (mDestroyed) {
+                    // Already destroyed.
+                    return;
+                }
+                if (DEBUG) {
+                    Log.d(TAG, "Destroying origin transition record " + this);
+                }
+                mDestroyed = true;
+                unlinkToDeath();
+                mShellTransitions.unregisterRemote(mWrappedReturnTransition);
+                mRecords.remove(getToken());
+            }
+        }
+
+        private void linkToDeath() throws RemoteException {
+            asDelegate(mWrappedLaunchTransition).mTransition.asBinder().linkToDeath(this, 0);
+            asDelegate(mWrappedReturnTransition).mTransition.asBinder().linkToDeath(this, 0);
+        }
+
+        private void unlinkToDeath() {
+            asDelegate(mWrappedLaunchTransition).mTransition.asBinder().unlinkToDeath(this, 0);
+            asDelegate(mWrappedReturnTransition).mTransition.asBinder().unlinkToDeath(this, 0);
+        }
+
+        public IBinder getToken() {
+            return asLaunchableTransition().asBinder();
+        }
+
+        public RemoteTransition asLaunchableTransition() {
+            return mWrappedLaunchTransition;
+        }
+
+        @Override
+        public void binderDied() {
+            destroy();
+        }
+
+        @Override
+        public String toString() {
+            return "OriginTransitionRecord{launch="
+                    + mWrappedReturnTransition
+                    + ", return="
+                    + mWrappedReturnTransition
+                    + "}";
+        }
+
+        public void dump(IndentingPrintWriter ipw) {
+            synchronized (mLock) {
+                ipw.println("OriginTransitionRecord");
+                ipw.increaseIndent();
+                ipw.println("mDestroyed: " + mDestroyed);
+                ipw.println("Launch transition:");
+                ipw.increaseIndent();
+                ipw.println(mWrappedLaunchTransition);
+                ipw.decreaseIndent();
+                ipw.println("Return transition:");
+                ipw.increaseIndent();
+                ipw.println(mWrappedReturnTransition);
+                ipw.decreaseIndent();
+                ipw.decreaseIndent();
+            }
+        }
+
+        private static RemoteTransitionDelegate asDelegate(RemoteTransition transition) {
+            return (RemoteTransitionDelegate) transition.getRemoteTransition();
+        }
+
+        private RemoteTransition wrap(
+                RemoteTransition transition, Predicate<TransitionInfo> onStarting) {
+            return new RemoteTransition(
+                    new RemoteTransitionDelegate(
+                            mContext.getMainExecutor(),
+                            transition.getRemoteTransition(),
+                            onStarting),
+                    transition.getDebugName());
+        }
+
+        @Nullable
+        private static TransitionFilter createFilterForReverseTransition(TransitionInfo info) {
+            TaskInfo launchingTaskInfo = null;
+            TaskInfo launchedTaskInfo = null;
+            ComponentName launchingActivity = null;
+            ComponentName launchedActivity = null;
+            for (Change change : info.getChanges()) {
+                int mode = change.getMode();
+                TaskInfo taskInfo = change.getTaskInfo();
+                ComponentName activity = change.getActivityComponent();
+                if (TransitionUtil.isClosingMode(mode)
+                        && launchingTaskInfo == null
+                        && taskInfo != null) {
+                    // Found the launching task!
+                    launchingTaskInfo = taskInfo;
+                } else if (TransitionUtil.isOpeningMode(mode)
+                        && launchedTaskInfo == null
+                        && taskInfo != null) {
+                    // Found the launched task!
+                    launchedTaskInfo = taskInfo;
+                } else if (TransitionUtil.isClosingMode(mode)
+                        && launchingActivity == null
+                        && activity != null) {
+                    // Found the launching activity
+                    launchingActivity = activity;
+                } else if (TransitionUtil.isOpeningMode(mode)
+                        && launchedActivity == null
+                        && activity != null) {
+                    // Found the launched activity!
+                    launchedActivity = activity;
+                }
+            }
+            if (DEBUG) {
+                Log.d(
+                        TAG,
+                        "createFilterForReverseTransition: launchingTaskInfo="
+                                + launchingTaskInfo
+                                + ", launchedTaskInfo="
+                                + launchedTaskInfo
+                                + ", launchingActivity="
+                                + launchedActivity
+                                + ", launchedActivity="
+                                + launchedActivity);
+            }
+            if (launchingTaskInfo == null && launchingActivity == null) {
+                Log.w(
+                        TAG,
+                        "createFilterForReverseTransition: unable to find launching task or"
+                                + " launching activity!");
+                return null;
+            }
+            if (launchedTaskInfo == null && launchedActivity == null) {
+                Log.w(
+                        TAG,
+                        "createFilterForReverseTransition: unable to find launched task or launched"
+                                + " activity!");
+                return null;
+            }
+            if (launchedTaskInfo != null && launchedTaskInfo.launchCookies.isEmpty()) {
+                Log.w(
+                        TAG,
+                        "createFilterForReverseTransition: skipped - launched task has no launch"
+                                + " cookie!");
+                return null;
+            }
+            TransitionFilter filter = new TransitionFilter();
+            filter.mTypeSet = new int[] {TRANSIT_CLOSE, TRANSIT_TO_BACK};
+
+            // The opening activity of the return transition must match the activity we just closed.
+            TransitionFilter.Requirement req1 = new TransitionFilter.Requirement();
+            req1.mModes = new int[] {TRANSIT_OPEN, TRANSIT_TO_FRONT};
+            req1.mTopActivity =
+                    launchingActivity == null ? launchingTaskInfo.topActivity : launchingActivity;
+
+            TransitionFilter.Requirement req2 = new TransitionFilter.Requirement();
+            req2.mModes = new int[] {TRANSIT_CLOSE, TRANSIT_TO_BACK};
+            if (launchedTaskInfo != null) {
+                // For task transitions, the closing task's cookie must match the task we just
+                // launched.
+                req2.mLaunchCookie = launchedTaskInfo.launchCookies.get(0);
+            } else {
+                // For activity transitions, the closing activity of the return transition must
+                // match
+                // the activity we just launched.
+                req2.mTopActivity = launchedActivity;
+            }
+
+            filter.mRequirements = new TransitionFilter.Requirement[] {req1, req2};
+            return filter;
+        }
+    }
+}
diff --git a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/shared/IOriginTransitions.aidl b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/shared/IOriginTransitions.aidl
new file mode 100644
index 0000000..31cca70
--- /dev/null
+++ b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/shared/IOriginTransitions.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.animation.shared;
+
+import android.window.RemoteTransition;
+
+/**
+ * An interface for an app to link a launch transition and a return transition together into an
+ * origin transition.
+ */
+interface IOriginTransitions {
+
+    /**
+     * Create a new "origin transition" which wraps a launch transition and a return transition.
+     * The returned {@link RemoteTransition} is expected to be passed to
+     * {@link ActivityOptions#makeRemoteTransition(RemoteTransition)} to create an
+     * {@link ActivityOptions} and being used to launch an intent. When being used with
+     * {@link ActivityOptions}, the launch transition will be triggered for launching the intent,
+     * and the return transition will be remembered and triggered for returning from the launched
+     * activity.
+     */
+    RemoteTransition makeOriginTransition(in RemoteTransition launchTransition,
+            in RemoteTransition returnTransition) = 1;
+
+    /**
+     * Cancels an origin transition. Any parts not yet played will no longer be triggered, and the
+     * origin transition object will reset to a single frame animation.
+     */
+    void cancelOriginTransition(in RemoteTransition originTransition) = 2;
+}
diff --git a/packages/SystemUI/compose/core/TEST_MAPPING b/packages/SystemUI/compose/core/TEST_MAPPING
index b71c5fb..56e531d 100644
--- a/packages/SystemUI/compose/core/TEST_MAPPING
+++ b/packages/SystemUI/compose/core/TEST_MAPPING
@@ -1,26 +1,10 @@
 {
   "presubmit": [
     {
-      "name": "PlatformComposeCoreTests",
-      "options": [
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "PlatformComposeCoreTests"
     },
     {
-      "name": "SystemUIComposeGalleryTests",
-      "options": [
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "SystemUIComposeGalleryTests"
     }
   ]
 }
\ No newline at end of file
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/windowsizeclass/WindowSizeClass.kt b/packages/SystemUI/compose/core/src/com/android/compose/windowsizeclass/WindowSizeClass.kt
index 4674d6e..c01396a 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/windowsizeclass/WindowSizeClass.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/windowsizeclass/WindowSizeClass.kt
@@ -16,15 +16,16 @@
 
 package com.android.compose.windowsizeclass
 
+import android.view.WindowManager
 import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
 import androidx.compose.material3.windowsizeclass.WindowSizeClass
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
 import androidx.compose.runtime.staticCompositionLocalOf
 import androidx.compose.ui.graphics.toComposeRect
 import androidx.compose.ui.platform.LocalConfiguration
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalDensity
-import androidx.window.layout.WindowMetricsCalculator
 
 val LocalWindowSizeClass =
     staticCompositionLocalOf<WindowSizeClass> {
@@ -41,7 +42,10 @@
     LocalConfiguration.current
     val density = LocalDensity.current
     val context = LocalContext.current
-    val metrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(context)
+    val metrics =
+        remember(context) {
+            context.getSystemService(WindowManager::class.java)!!.currentWindowMetrics
+        }
     val size = with(density) { metrics.bounds.toComposeRect().size.toDpSize() }
     return WindowSizeClass.calculateFromSize(size)
 }
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/NotificationsShadeSceneModule.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/NotificationsShadeSceneModule.kt
deleted file mode 100644
index c58df35..0000000
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/NotificationsShadeSceneModule.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.scene
-
-import com.android.systemui.notifications.ui.composable.NotificationsShadeScene
-import com.android.systemui.scene.ui.composable.Scene
-import dagger.Binds
-import dagger.Module
-import dagger.multibindings.IntoSet
-
-@Module
-interface NotificationsShadeSceneModule {
-
-    @Binds @IntoSet fun notificationsShade(scene: NotificationsShadeScene): Scene
-}
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/QuickSettingsShadeSceneModule.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/QuickSettingsShadeSceneModule.kt
deleted file mode 100644
index 5bb6ae4..0000000
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/QuickSettingsShadeSceneModule.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.scene
-
-import com.android.systemui.qs.ui.composable.QuickSettingsShadeScene
-import com.android.systemui.scene.ui.composable.Scene
-import dagger.Binds
-import dagger.Module
-import dagger.multibindings.IntoSet
-
-@Module
-interface QuickSettingsShadeSceneModule {
-
-    @Binds @IntoSet fun quickSettingsShade(scene: QuickSettingsShadeScene): Scene
-}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
index 34eafde..d326f00 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
@@ -830,7 +830,7 @@
             Image(
                 bitmap = it.asImageBitmap(),
                 contentDescription = null,
-                modifier = Modifier.size(SelectedUserImageSize),
+                modifier = Modifier.size(SelectedUserImageSize).sysuiResTag("user_icon"),
             )
         }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
index 1287993..489e24e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
@@ -63,6 +63,7 @@
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.res.R
 import kotlin.time.Duration.Companion.milliseconds
 import kotlin.time.DurationUnit
@@ -103,7 +104,7 @@
         columns = columns,
         verticalSpacing = verticalSpacing,
         horizontalSpacing = calculateHorizontalSpacingBetweenColumns(gridWidth = 300.dp),
-        modifier = modifier.focusRequester(focusRequester)
+        modifier = modifier.focusRequester(focusRequester).sysuiResTag("pin_pad_grid")
     ) {
         repeat(9) { index ->
             DigitButton(
@@ -128,6 +129,7 @@
             onLongPressed = viewModel::onBackspaceButtonLongPressed,
             appearance = backspaceButtonAppearance,
             scaling = buttonScaleAnimatables[9]::value,
+            elementId = "delete_button"
         )
 
         DigitButton(
@@ -150,6 +152,7 @@
             onClicked = viewModel::onAuthenticateButtonClicked,
             appearance = confirmButtonAppearance,
             scaling = buttonScaleAnimatables[11]::value,
+            elementId = "key_enter"
         )
     }
 }
@@ -192,6 +195,7 @@
     icon: Icon,
     isInputEnabled: Boolean,
     onClicked: () -> Unit,
+    elementId: String,
     onLongPressed: (() -> Unit)? = null,
     appearance: ActionButtonAppearance,
     scaling: () -> Float,
@@ -217,6 +221,7 @@
         backgroundColor = backgroundColor,
         foregroundColor = foregroundColor,
         isAnimationEnabled = true,
+        elementId = elementId,
         modifier =
             Modifier.graphicsLayer {
                 alpha = hiddenAlpha
@@ -240,6 +245,7 @@
     foregroundColor: Color,
     isAnimationEnabled: Boolean,
     modifier: Modifier = Modifier,
+    elementId: String? = null,
     onLongPressed: (() -> Unit)? = null,
     onPointerDown: (() -> Unit)? = null,
     content: @Composable (contentColor: () -> Color) -> Unit,
@@ -321,7 +327,8 @@
                             }
                             false
                         }
-                },
+                }
+                .thenIf(elementId != null) { Modifier.sysuiResTag(elementId!!) },
     ) {
         content(contentColor::value)
     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index a4dc8fc..557257d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -72,7 +72,7 @@
     override fun matches(key: ElementKey, content: ContentKey) = true
 }
 
-private object TransitionDuration {
+object TransitionDuration {
     const val BETWEEN_HUB_AND_EDIT_MODE_MS = 1000
     const val EDIT_MODE_TO_HUB_CONTENT_MS = 167
     const val EDIT_MODE_TO_HUB_GRID_DELAY_MS = 167
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 efe0f2e..f4d1242 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
@@ -108,6 +108,8 @@
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.drawBehind
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
 import androidx.compose.ui.geometry.CornerRadius
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Size
@@ -179,6 +181,7 @@
 import com.android.systemui.communal.widgets.WidgetConfigurator
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.phone.SystemUIDialogFactory
+import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
 
 @OptIn(ExperimentalMaterial3Api::class)
@@ -1155,6 +1158,15 @@
     val selectedIndex =
         selectedKey?.let { key -> contentListState.list.indexOfFirst { it.key == key } }
 
+    val interactionSource = remember { MutableInteractionSource() }
+    val focusRequester = remember { FocusRequester() }
+    if (viewModel.isEditMode && selected) {
+        LaunchedEffect(Unit) {
+            delay(TransitionDuration.BETWEEN_HUB_AND_EDIT_MODE_MS.toLong())
+            focusRequester.requestFocus()
+        }
+    }
+
     val isSelected = selectedKey == model.key
 
     val selectableModifier =
@@ -1162,7 +1174,7 @@
             Modifier.selectable(
                 selected = isSelected,
                 onClick = { viewModel.setSelectedKey(model.key) },
-                interactionSource = remember { MutableInteractionSource() },
+                interactionSource = interactionSource,
                 indication = null,
             )
         } else {
@@ -1172,6 +1184,8 @@
     Box(
         modifier =
             modifier
+                .focusRequester(focusRequester)
+                .focusable(interactionSource = interactionSource)
                 .then(selectableModifier)
                 .thenIf(!viewModel.isEditMode && !model.inQuietMode) {
                     Modifier.pointerInput(Unit) {
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
deleted file mode 100644
index 1f4cd04..0000000
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
+++ /dev/null
@@ -1,116 +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.notifications.ui.composable
-
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
-import com.android.compose.animation.scene.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.lifecycle.ExclusiveActivatable
-import com.android.systemui.lifecycle.rememberViewModel
-import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeUserActionsViewModel
-import com.android.systemui.scene.session.ui.composable.SaveableSession
-import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.scene.ui.composable.Scene
-import com.android.systemui.shade.shared.model.ShadeMode
-import com.android.systemui.shade.ui.composable.ExpandedShadeHeader
-import com.android.systemui.shade.ui.composable.OverlayShade
-import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
-import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
-import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
-import com.android.systemui.statusbar.phone.ui.StatusBarIconController
-import com.android.systemui.statusbar.phone.ui.TintedIconManager
-import dagger.Lazy
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-
-@SysUISingleton
-class NotificationsShadeScene
-@Inject
-constructor(
-    private val actionsViewModelFactory: NotificationsShadeUserActionsViewModel.Factory,
-    private val shadeHeaderViewModelFactory: ShadeHeaderViewModel.Factory,
-    private val notificationsPlaceholderViewModelFactory: NotificationsPlaceholderViewModel.Factory,
-    private val tintedIconManagerFactory: TintedIconManager.Factory,
-    private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
-    private val statusBarIconController: StatusBarIconController,
-    private val shadeSession: SaveableSession,
-    private val stackScrollView: Lazy<NotificationScrollView>,
-) : ExclusiveActivatable(), Scene {
-
-    override val key = Scenes.NotificationsShade
-
-    private val actionsViewModel: NotificationsShadeUserActionsViewModel by lazy {
-        actionsViewModelFactory.create()
-    }
-
-    override val userActions: Flow<Map<UserAction, UserActionResult>> = actionsViewModel.actions
-
-    override suspend fun onActivated(): Nothing {
-        actionsViewModel.activate()
-    }
-
-    @Composable
-    override fun SceneScope.Content(
-        modifier: Modifier,
-    ) {
-        val notificationsPlaceholderViewModel =
-            rememberViewModel("NotificationsShadeScene") {
-                notificationsPlaceholderViewModelFactory.create()
-            }
-
-        OverlayShade(
-            modifier = modifier,
-            onScrimClicked = {},
-        ) {
-            Column {
-                ExpandedShadeHeader(
-                    viewModelFactory = shadeHeaderViewModelFactory,
-                    createTintedIconManager = tintedIconManagerFactory::create,
-                    createBatteryMeterViewController = batteryMeterViewControllerFactory::create,
-                    statusBarIconController = statusBarIconController,
-                    modifier = Modifier.padding(horizontal = 16.dp),
-                )
-
-                NotificationScrollingStack(
-                    shadeSession = shadeSession,
-                    stackScrollView = stackScrollView.get(),
-                    viewModel = notificationsPlaceholderViewModel,
-                    maxScrimTop = { 0f },
-                    shouldPunchHoleBehindScrim = false,
-                    shouldFillMaxSize = false,
-                    shouldReserveSpaceForNavBar = false,
-                    shadeMode = ShadeMode.Dual,
-                    modifier = Modifier.fillMaxWidth(),
-                )
-
-                // Communicates the bottom position of the drawable area within the shade to NSSL.
-                NotificationStackCutoffGuideline(
-                    stackScrollView = stackScrollView.get(),
-                    viewModel = notificationsPlaceholderViewModel,
-                )
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
index 671b012..a6d5c1c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
@@ -48,17 +48,13 @@
 import com.android.systemui.scene.shared.model.Scenes
 
 object QuickSettings {
-    private val SCENES =
-        setOf(
-            Scenes.QuickSettings,
-            Scenes.Shade,
-        )
+    private val SCENES = setOf(Scenes.QuickSettings, Scenes.Shade)
 
     object Elements {
         val Content =
             MovableElementKey(
                 "QuickSettingsContent",
-                contentPicker = MovableElementContentPicker(SCENES)
+                contentPicker = MovableElementContentPicker(SCENES),
             )
         val QuickQuickSettings = ElementKey("QuickQuickSettings")
         val SplitShadeQuickSettings = ElementKey("SplitShadeQuickSettings")
@@ -87,7 +83,7 @@
 
 private fun SceneScope.stateForQuickSettingsContent(
     isSplitShade: Boolean,
-    squishiness: () -> Float = { QuickSettings.SharedValues.SquishinessValues.Default }
+    squishiness: () -> Float = { QuickSettings.SharedValues.SquishinessValues.Default },
 ): QSSceneAdapter.State {
     return when (val transitionState = layoutState.transitionState) {
         is TransitionState.Idle -> {
@@ -122,7 +118,7 @@
                 }
             }
         is TransitionState.Transition.OverlayTransition ->
-            TODO("b/359173565: Handle overlay transitions")
+            error("Bad transition for QuickSettings scene: overlays not supported")
     }
 }
 
@@ -172,7 +168,7 @@
                 val height = heightProvider().coerceAtLeast(0)
 
                 layout(placeable.width, height) { placeable.placeRelative(0, 0) }
-            }
+            },
     ) {
         content { QuickSettingsContent(qsSceneAdapter = qsSceneAdapter, contentState) }
     }
@@ -225,7 +221,7 @@
                             it.addView(view)
                         }
                     },
-                    onRelease = { it.removeAllViews() }
+                    onRelease = { it.removeAllViews() },
                 )
             }
         }
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 d34295e..fa92bef34 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
@@ -64,6 +64,7 @@
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -146,9 +147,7 @@
     }
 
     @Composable
-    override fun SceneScope.Content(
-        modifier: Modifier,
-    ) {
+    override fun SceneScope.Content(modifier: Modifier) {
         QuickSettingsScene(
             notificationStackScrollView = notificationStackScrollView.get(),
             viewModelFactory = contentViewModelFactory,
@@ -199,13 +198,17 @@
         onDispose { notificationsPlaceholderViewModel.setAlphaForBrightnessMirror(1f) }
     }
 
+    val shadeHorizontalPadding =
+        dimensionResource(id = R.dimen.notification_panel_margin_horizontal)
+
     BrightnessMirror(
         viewModel = brightnessMirrorViewModel,
         qsSceneAdapter = viewModel.qsSceneAdapter,
         modifier =
             Modifier.thenIf(cutoutLocation != CutoutLocation.CENTER) {
-                Modifier.displayCutoutPadding()
-            }
+                    Modifier.displayCutoutPadding()
+                }
+                .padding(horizontal = shadeHorizontalPadding),
     )
 
     val shouldPunchHoleBehindScrim =
@@ -224,9 +227,7 @@
                     // scene (and not the one under it) during a scene transition.
                     Modifier.graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
                 }
-                .thenIf(cutoutLocation != CutoutLocation.CENTER) {
-                    Modifier.displayCutoutPadding()
-                },
+                .thenIf(cutoutLocation != CutoutLocation.CENTER) { Modifier.displayCutoutPadding() }
     ) {
         val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsStateWithLifecycle()
         val isCustomizerShowing by
@@ -235,11 +236,7 @@
             viewModel.qsSceneAdapter.customizerAnimationDuration.collectAsStateWithLifecycle()
         val screenHeight = LocalRawScreenHeight.current
 
-        BackHandler(
-            enabled = isCustomizing,
-        ) {
-            viewModel.qsSceneAdapter.requestCloseCustomizer()
-        }
+        BackHandler(enabled = isCustomizing) { viewModel.qsSceneAdapter.requestCloseCustomizer() }
 
         val collapsedHeaderHeight =
             with(LocalDensity.current) { ShadeHeader.Dimensions.CollapsedHeight.roundToPx() }
@@ -276,13 +273,13 @@
             animateDpAsState(
                 targetValue = if (isCustomizing) 0.dp else navBarBottomHeight,
                 animationSpec = tween(customizingAnimationDuration),
-                label = "animateQSSceneBottomPaddingAsState"
+                label = "animateQSSceneBottomPaddingAsState",
             )
         val topPadding by
             animateDpAsState(
                 targetValue = if (isCustomizing) ShadeHeader.Dimensions.CollapsedHeight else 0.dp,
                 animationSpec = tween(customizingAnimationDuration),
-                label = "animateQSSceneTopPaddingAsState"
+                label = "animateQSSceneTopPaddingAsState",
             )
 
         LaunchedEffect(navBarBottomHeight, density) {
@@ -313,18 +310,15 @@
                 Modifier.fillMaxSize()
                     .padding(
                         top = topPadding.coerceAtLeast(0.dp),
-                        bottom = bottomPadding.coerceAtLeast(0.dp)
-                    )
+                        bottom = bottomPadding.coerceAtLeast(0.dp),
+                    ),
         ) {
             Box(modifier = Modifier.fillMaxSize().weight(1f)) {
                 val shadeHeaderAndQuickSettingsModifier =
                     if (isCustomizerShowing) {
                         Modifier.fillMaxHeight().align(Alignment.TopCenter)
                     } else {
-                        Modifier.verticalScroll(
-                                scrollState,
-                                enabled = isScrollable,
-                            )
+                        Modifier.verticalScroll(scrollState, enabled = isScrollable)
                             .clipScrollableContainer(Orientation.Horizontal)
                             .fillMaxWidth()
                             .wrapContentHeight(unbounded = true)
@@ -333,7 +327,7 @@
 
                 Column(
                     modifier =
-                        shadeHeaderAndQuickSettingsModifier.sysuiResTag("expanded_qs_scroll_view"),
+                        shadeHeaderAndQuickSettingsModifier.sysuiResTag("expanded_qs_scroll_view")
                 ) {
                     when (LocalWindowSizeClass.current.widthSizeClass) {
                         WindowWidthSizeClass.Compact ->
@@ -345,7 +339,7 @@
                                         expandFrom = Alignment.Top,
                                     ) +
                                         slideInVertically(
-                                            animationSpec = tween(customizingAnimationDuration),
+                                            animationSpec = tween(customizingAnimationDuration)
                                         ) +
                                         fadeIn(tween(customizingAnimationDuration)),
                                 exit =
@@ -354,7 +348,7 @@
                                         shrinkTowards = Alignment.Top,
                                     ) +
                                         slideOutVertically(
-                                            animationSpec = tween(customizingAnimationDuration),
+                                            animationSpec = tween(customizingAnimationDuration)
                                         ) +
                                         fadeOut(tween(customizingAnimationDuration)),
                             ) {
@@ -382,7 +376,7 @@
                             viewModel.qsSceneAdapter,
                             { viewModel.qsSceneAdapter.qsHeight },
                             isSplitShade = false,
-                            modifier = Modifier.layoutId(QSMediaMeasurePolicy.LayoutId.QS)
+                            modifier = Modifier.layoutId(QSMediaMeasurePolicy.LayoutId.QS),
                         )
 
                         MediaCarousel(
@@ -400,13 +394,12 @@
                             { mediaOffset.roundToPx() },
                         )
                     }
-                    if (mediaInRow) {
-                        Layout(
-                            content = content,
-                            measurePolicy = landscapeQsMediaMeasurePolicy,
-                        )
-                    } else {
-                        content()
+                    Box(modifier = Modifier.padding(horizontal = shadeHorizontalPadding)) {
+                        if (mediaInRow) {
+                            Layout(content = content, measurePolicy = landscapeQsMediaMeasurePolicy)
+                        } else {
+                            content()
+                        }
                     }
                 }
             }
@@ -417,13 +410,18 @@
                 customizingAnimationDuration = customizingAnimationDuration,
                 lifecycleOwner = lifecycleOwner,
                 modifier =
-                    Modifier.align(Alignment.CenterHorizontally).sysuiResTag("qs_footer_actions"),
+                    Modifier.align(Alignment.CenterHorizontally)
+                        .sysuiResTag("qs_footer_actions")
+                        .padding(horizontal = shadeHorizontalPadding),
             )
         }
         HeadsUpNotificationSpace(
             stackScrollView = notificationStackScrollView,
             viewModel = notificationsPlaceholderViewModel,
-            modifier = Modifier.align(Alignment.BottomCenter).navigationBarsPadding(),
+            modifier =
+                Modifier.align(Alignment.BottomCenter)
+                    .navigationBarsPadding()
+                    .padding(horizontal = shadeHorizontalPadding),
             isPeekFromBottom = true,
         )
         NotificationScrollingStack(
@@ -435,15 +433,18 @@
             shouldIncludeHeadsUpSpace = false,
             shadeMode = ShadeMode.Single,
             modifier =
-                Modifier.fillMaxWidth().offset { IntOffset(x = 0, y = screenHeight.roundToInt()) },
+                Modifier.fillMaxWidth()
+                    .offset { IntOffset(x = 0, y = screenHeight.roundToInt()) }
+                    .padding(horizontal = shadeHorizontalPadding),
         )
         NotificationStackCutoffGuideline(
             stackScrollView = notificationStackScrollView,
             viewModel = notificationsPlaceholderViewModel,
             modifier =
-                Modifier.align(Alignment.BottomCenter).navigationBarsPadding().offset {
-                    IntOffset(x = 0, y = screenHeight.roundToInt())
-                }
+                Modifier.align(Alignment.BottomCenter)
+                    .navigationBarsPadding()
+                    .offset { IntOffset(x = 0, y = screenHeight.roundToInt()) }
+                    .padding(horizontal = shadeHorizontalPadding),
         )
     }
 }
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
deleted file mode 100644
index e27c7e2..0000000
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs.ui.composable
-
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.padding
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-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.lifecycle.ExclusiveActivatable
-import com.android.systemui.lifecycle.rememberViewModel
-import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeSceneContentViewModel
-import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeUserActionsViewModel
-import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.scene.ui.composable.Scene
-import com.android.systemui.shade.ui.composable.ExpandedShadeHeader
-import com.android.systemui.shade.ui.composable.OverlayShade
-import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
-import com.android.systemui.statusbar.phone.ui.StatusBarIconController
-import com.android.systemui.statusbar.phone.ui.TintedIconManager
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-
-@SysUISingleton
-class QuickSettingsShadeScene
-@Inject
-constructor(
-    private val actionsViewModelFactory: QuickSettingsShadeUserActionsViewModel.Factory,
-    private val contentViewModelFactory: QuickSettingsShadeSceneContentViewModel.Factory,
-    private val shadeHeaderViewModelFactory: ShadeHeaderViewModel.Factory,
-    private val tintedIconManagerFactory: TintedIconManager.Factory,
-    private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
-    private val statusBarIconController: StatusBarIconController,
-) : ExclusiveActivatable(), Scene {
-
-    override val key = Scenes.QuickSettingsShade
-
-    private val actionsViewModel: QuickSettingsShadeUserActionsViewModel by lazy {
-        actionsViewModelFactory.create()
-    }
-
-    override val userActions: Flow<Map<UserAction, UserActionResult>> = actionsViewModel.actions
-
-    override suspend fun onActivated(): Nothing {
-        actionsViewModel.activate()
-    }
-
-    @Composable
-    override fun SceneScope.Content(
-        modifier: Modifier,
-    ) {
-        val viewModel =
-            rememberViewModel("QuickSettingsShadeScene") { contentViewModelFactory.create() }
-
-        OverlayShade(
-            modifier = modifier,
-            onScrimClicked = {},
-        ) {
-            Column {
-                ExpandedShadeHeader(
-                    viewModelFactory = shadeHeaderViewModelFactory,
-                    createTintedIconManager = tintedIconManagerFactory::create,
-                    createBatteryMeterViewController = batteryMeterViewControllerFactory::create,
-                    statusBarIconController = statusBarIconController,
-                    modifier = Modifier.padding(QuickSettingsShade.Dimensions.Padding),
-                )
-
-                ShadeBody(
-                    viewModel = viewModel.quickSettingsContainerViewModel,
-                )
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
index f660808..f64d0ed 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
@@ -6,6 +6,7 @@
 import com.android.compose.animation.scene.transitions
 import com.android.systemui.bouncer.ui.composable.Bouncer
 import com.android.systemui.notifications.ui.composable.Notifications
+import com.android.systemui.scene.shared.model.Overlays
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.shared.model.TransitionKeys.SlightlyFasterShadeCollapse
 import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
@@ -20,7 +21,11 @@
 import com.android.systemui.scene.ui.composable.transitions.lockscreenToQuickSettingsTransition
 import com.android.systemui.scene.ui.composable.transitions.lockscreenToShadeTransition
 import com.android.systemui.scene.ui.composable.transitions.lockscreenToSplitShadeTransition
+import com.android.systemui.scene.ui.composable.transitions.notificationsShadeToQuickSettingsShadeTransition
 import com.android.systemui.scene.ui.composable.transitions.shadeToQuickSettingsTransition
+import com.android.systemui.scene.ui.composable.transitions.toNotificationsShadeTransition
+import com.android.systemui.scene.ui.composable.transitions.toQuickSettingsShadeTransition
+import com.android.systemui.shade.ui.composable.OverlayShade
 import com.android.systemui.shade.ui.composable.Shade
 
 /**
@@ -74,6 +79,14 @@
     from(Scenes.Lockscreen, to = Scenes.Gone) { lockscreenToGoneTransition() }
     from(Scenes.Shade, to = Scenes.QuickSettings) { shadeToQuickSettingsTransition() }
 
+    // Overlay transitions
+
+    to(Overlays.NotificationsShade) { toNotificationsShadeTransition() }
+    to(Overlays.QuickSettingsShade) { toQuickSettingsShadeTransition() }
+    from(Overlays.NotificationsShade, Overlays.QuickSettingsShade) {
+        notificationsShadeToQuickSettingsShadeTransition()
+    }
+
     // Scene overscroll
 
     overscrollDisabled(Scenes.Gone, Orientation.Vertical)
@@ -91,4 +104,10 @@
             y = Shade.Dimensions.ScrimOverscrollLimit,
         )
     }
+    overscroll(Overlays.NotificationsShade, Orientation.Vertical) {
+        translate(OverlayShade.Elements.Panel, y = OverlayShade.Dimensions.OverscrollLimit)
+    }
+    overscroll(Overlays.QuickSettingsShade, Orientation.Vertical) {
+        translate(OverlayShade.Elements.Panel, y = OverlayShade.Dimensions.OverscrollLimit)
+    }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsShadeTransition.kt
deleted file mode 100644
index 8a03e29..0000000
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsShadeTransition.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.scene.ui.composable.transitions
-
-import com.android.compose.animation.scene.Edge
-import com.android.compose.animation.scene.TransitionBuilder
-
-fun TransitionBuilder.goneToQuickSettingsShadeTransition(
-    edge: Edge = Edge.Top,
-    durationScale: Double = 1.0,
-) {
-    toQuickSettingsShadeTransition(edge, durationScale)
-}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToNotificationsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToNotificationsShadeTransition.kt
deleted file mode 100644
index 02664c1..0000000
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToNotificationsShadeTransition.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.scene.ui.composable.transitions
-
-import com.android.compose.animation.scene.TransitionBuilder
-
-fun TransitionBuilder.lockscreenToNotificationsShadeTransition(
-    durationScale: Double = 1.0,
-) {
-    toNotificationsShadeTransition(durationScale = durationScale)
-}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsShadeTransition.kt
deleted file mode 100644
index 19aa3a7..0000000
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsShadeTransition.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.scene.ui.composable.transitions
-
-import com.android.compose.animation.scene.Edge
-import com.android.compose.animation.scene.TransitionBuilder
-
-fun TransitionBuilder.lockscreenToQuickSettingsShadeTransition(
-    durationScale: Double = 1.0,
-) {
-    toQuickSettingsShadeTransition(Edge.Top, durationScale)
-}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromNotificationsShadeToQuickSettingsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromNotificationsShadeToQuickSettingsShadeTransition.kt
new file mode 100644
index 0000000..24f285e
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromNotificationsShadeToQuickSettingsShadeTransition.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.spring
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.shade.ui.composable.Shade
+import kotlin.time.Duration.Companion.milliseconds
+
+fun TransitionBuilder.notificationsShadeToQuickSettingsShadeTransition(
+    durationScale: Double = 1.0
+) {
+    spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
+    swipeSpec =
+        spring(
+            stiffness = Spring.StiffnessMediumLow,
+            visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
+        )
+}
+
+private val DefaultDuration = 300.milliseconds
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt
index 9d13647..55fa6ad 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt
@@ -26,10 +26,7 @@
 import com.android.systemui.shade.ui.composable.Shade
 import kotlin.time.Duration.Companion.milliseconds
 
-fun TransitionBuilder.toQuickSettingsShadeTransition(
-    edge: Edge = Edge.Top,
-    durationScale: Double = 1.0,
-) {
+fun TransitionBuilder.toQuickSettingsShadeTransition(durationScale: Double = 1.0) {
     spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
     swipeSpec =
         spring(
@@ -38,7 +35,7 @@
         )
     distance = UserActionDistance { fromSceneSize, _ -> fromSceneSize.height.toFloat() * 2 / 3f }
 
-    translate(OverlayShade.Elements.Panel, edge)
+    translate(OverlayShade.Elements.Panel, Edge.Top)
 
     fractionRange(end = .5f) { fade(OverlayShade.Elements.Scrim) }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
index 8922224..b85523b 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
@@ -61,23 +61,17 @@
     Box(modifier) {
         Scrim(onClicked = onScrimClicked)
 
-        Box(
-            modifier = Modifier.fillMaxSize().panelPadding(),
-            contentAlignment = Alignment.TopEnd,
-        ) {
+        Box(modifier = Modifier.fillMaxSize().panelPadding(), contentAlignment = Alignment.TopEnd) {
             Panel(
                 modifier = Modifier.element(OverlayShade.Elements.Panel).panelSize(),
-                content = content
+                content = content,
             )
         }
     }
 }
 
 @Composable
-private fun SceneScope.Scrim(
-    onClicked: () -> Unit,
-    modifier: Modifier = Modifier,
-) {
+private fun SceneScope.Scrim(onClicked: () -> Unit, modifier: Modifier = Modifier) {
     Spacer(
         modifier =
             modifier
@@ -89,10 +83,7 @@
 }
 
 @Composable
-private fun SceneScope.Panel(
-    modifier: Modifier = Modifier,
-    content: @Composable () -> Unit,
-) {
+private fun SceneScope.Panel(modifier: Modifier = Modifier, content: @Composable () -> Unit) {
     Box(modifier = modifier.clip(OverlayShade.Shapes.RoundedCornerPanel)) {
         Spacer(
             modifier =
@@ -101,7 +92,7 @@
                     .background(
                         color = OverlayShade.Colors.PanelBackground,
                         shape = OverlayShade.Shapes.RoundedCornerPanel,
-                    ),
+                    )
         )
 
         // This content is intentionally rendered as a separate element from the background in order
@@ -137,7 +128,7 @@
             systemBars.asPaddingValues(),
             displayCutout.asPaddingValues(),
             waterfall.asPaddingValues(),
-            contentPadding
+            contentPadding,
         )
 
     return if (widthSizeClass == WindowWidthSizeClass.Compact) {
@@ -156,14 +147,19 @@
         start = paddingValues.maxOfOrNull { it.calculateStartPadding(layoutDirection) } ?: 0.dp,
         top = paddingValues.maxOfOrNull { it.calculateTopPadding() } ?: 0.dp,
         end = paddingValues.maxOfOrNull { it.calculateEndPadding(layoutDirection) } ?: 0.dp,
-        bottom = paddingValues.maxOfOrNull { it.calculateBottomPadding() } ?: 0.dp
+        bottom = paddingValues.maxOfOrNull { it.calculateBottomPadding() } ?: 0.dp,
     )
 }
 
 object OverlayShade {
     object Elements {
         val Scrim = ElementKey("OverlayShadeScrim", contentPicker = LowestZIndexContentPicker)
-        val Panel = ElementKey("OverlayShadePanel", contentPicker = LowestZIndexContentPicker)
+        val Panel =
+            ElementKey(
+                "OverlayShadePanel",
+                contentPicker = LowestZIndexContentPicker,
+                placeAllCopies = true,
+            )
         val PanelBackground =
             ElementKey("OverlayShadePanelBackground", contentPicker = LowestZIndexContentPicker)
     }
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 df22264..8a59e20 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
@@ -16,6 +16,7 @@
 
 package com.android.systemui.shade.ui.composable
 
+import android.view.HapticFeedbackConstants
 import android.view.ViewGroup
 import androidx.compose.animation.core.animateDpAsState
 import androidx.compose.animation.core.animateFloatAsState
@@ -60,6 +61,7 @@
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.compose.ui.platform.LocalView
 import androidx.compose.ui.res.colorResource
 import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.unit.dp
@@ -178,9 +180,7 @@
     override val userActions: Flow<Map<UserAction, UserActionResult>> = actionsViewModel.actions
 
     @Composable
-    override fun SceneScope.Content(
-        modifier: Modifier,
-    ) =
+    override fun SceneScope.Content(modifier: Modifier) =
         ShadeScene(
             notificationStackScrollView.get(),
             viewModel =
@@ -224,6 +224,13 @@
     modifier: Modifier = Modifier,
     shadeSession: SaveableSession,
 ) {
+    val view = LocalView.current
+    LaunchedEffect(Unit) {
+        if (layoutState.currentTransition?.fromContent == Scenes.Gone) {
+            view.performHapticFeedback(HapticFeedbackConstants.GESTURE_START)
+        }
+    }
+
     val shadeMode by viewModel.shadeMode.collectAsStateWithLifecycle()
     when (shadeMode) {
         is ShadeMode.Single ->
@@ -282,7 +289,7 @@
         animateSceneFloatAsState(
             value = 1f,
             key = QuickSettings.SharedValues.TilesSquishiness,
-            canOverflow = false
+            canOverflow = false,
         )
     val isEmptySpaceClickable by viewModel.isEmptySpaceClickable.collectAsStateWithLifecycle()
     val isMediaVisible by viewModel.isMediaVisible.collectAsStateWithLifecycle()
@@ -320,7 +327,7 @@
                     } else {
                         cutoutInsets
                     }
-                }
+                },
             )
         }
 
@@ -337,7 +344,7 @@
             modifier =
                 Modifier.fillMaxSize()
                     .element(Shade.Elements.BackgroundScrim)
-                    .background(colorResource(R.color.shade_scrim_background_dark)),
+                    .background(colorResource(R.color.shade_scrim_background_dark))
         )
         Layout(
             modifier =
@@ -398,13 +405,13 @@
                     .pointerInteropFilter { true }
                     .verticalNestedScrollToScene(
                         topBehavior = NestedScrollBehavior.EdgeAlways,
-                        isExternalOverscrollGesture = { false }
+                        isExternalOverscrollGesture = { false },
                     )
         ) {
             NotificationStackCutoffGuideline(
                 stackScrollView = notificationStackScrollView,
                 viewModel = notificationsPlaceholderViewModel,
-                modifier = Modifier.align(Alignment.TopCenter)
+                modifier = Modifier.align(Alignment.TopCenter),
             )
         }
     }
@@ -440,24 +447,16 @@
             canOverflow = false,
         )
     val unfoldTranslationXForStartSide by
-        viewModel
-            .unfoldTranslationX(
-                isOnStartSide = true,
-            )
-            .collectAsStateWithLifecycle(0f)
+        viewModel.unfoldTranslationX(isOnStartSide = true).collectAsStateWithLifecycle(0f)
     val unfoldTranslationXForEndSide by
-        viewModel
-            .unfoldTranslationX(
-                isOnStartSide = false,
-            )
-            .collectAsStateWithLifecycle(0f)
+        viewModel.unfoldTranslationX(isOnStartSide = false).collectAsStateWithLifecycle(0f)
 
     val navBarBottomHeight = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
     val bottomPadding by
         animateDpAsState(
             targetValue = if (isCustomizing) 0.dp else navBarBottomHeight,
             animationSpec = tween(customizingAnimationDuration),
-            label = "animateQSSceneBottomPaddingAsState"
+            label = "animateQSSceneBottomPaddingAsState",
         )
     val density = LocalDensity.current
     LaunchedEffect(navBarBottomHeight, density) {
@@ -516,9 +515,7 @@
                     )
         )
 
-        Column(
-            modifier = Modifier.fillMaxSize(),
-        ) {
+        Column(modifier = Modifier.fillMaxSize()) {
             CollapsedShadeHeader(
                 viewModelFactory = viewModel.shadeHeaderViewModelFactory,
                 createTintedIconManager = createTintedIconManager,
@@ -526,9 +523,7 @@
                 statusBarIconController = statusBarIconController,
                 modifier =
                     Modifier.then(brightnessMirrorShowingModifier)
-                        .padding(
-                            horizontal = { unfoldTranslationXForStartSide.roundToInt() },
-                        )
+                        .padding(horizontal = { unfoldTranslationXForStartSide.roundToInt() }),
             )
 
             Row(modifier = Modifier.fillMaxWidth().weight(1f)) {
@@ -536,14 +531,14 @@
                     modifier =
                         Modifier.element(Shade.Elements.SplitShadeStartColumn)
                             .weight(1f)
-                            .graphicsLayer { translationX = unfoldTranslationXForStartSide },
+                            .graphicsLayer { translationX = unfoldTranslationXForStartSide }
                 ) {
                     BrightnessMirror(
                         viewModel = brightnessMirrorViewModel,
                         qsSceneAdapter = viewModel.qsSceneAdapter,
                         // Need to use the offset measured from the container as the header
                         // has to be accounted for
-                        measureFromContainer = true
+                        measureFromContainer = true,
                     )
                     Column(
                         verticalArrangement = Arrangement.Top,
@@ -557,7 +552,7 @@
                                     .thenIf(!isCustomizerShowing) {
                                         Modifier.verticalScroll(
                                                 quickSettingsScrollState,
-                                                enabled = isScrollable
+                                                enabled = isScrollable,
                                             )
                                             .clipScrollableContainer(Orientation.Horizontal)
                                     }
@@ -619,16 +614,16 @@
                             .padding(
                                 end =
                                     dimensionResource(R.dimen.notification_panel_margin_horizontal),
-                                bottom = navBarBottomHeight
+                                bottom = navBarBottomHeight,
                             )
-                            .then(brightnessMirrorShowingModifier)
+                            .then(brightnessMirrorShowingModifier),
                 )
             }
         }
         NotificationStackCutoffGuideline(
             stackScrollView = notificationStackScrollView,
             viewModel = notificationsPlaceholderViewModel,
-            modifier = Modifier.align(Alignment.BottomCenter).navigationBarsPadding()
+            modifier = Modifier.align(Alignment.BottomCenter).navigationBarsPadding(),
         )
     }
 }
@@ -652,6 +647,6 @@
                 null
             } else {
                 { mediaOffsetProvider.offset }
-            }
+            },
     )
 }
diff --git a/packages/SystemUI/compose/scene/TEST_MAPPING b/packages/SystemUI/compose/scene/TEST_MAPPING
index f9424ed..65ba037 100644
--- a/packages/SystemUI/compose/scene/TEST_MAPPING
+++ b/packages/SystemUI/compose/scene/TEST_MAPPING
@@ -1,37 +1,13 @@
 {
   "presubmit": [
     {
-      "name": "PlatformComposeSceneTransitionLayoutTests",
-      "options": [
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "PlatformComposeSceneTransitionLayoutTests"
     },
     {
-      "name": "PlatformComposeCoreTests",
-      "options": [
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "PlatformComposeCoreTests"
     },
     {
-      "name": "SystemUIComposeGalleryTests",
-      "options": [
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "SystemUIComposeGalleryTests"
     }
   ]
 }
\ No newline at end of file
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index 5a084db..ebe1df4 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -813,6 +813,10 @@
     element: Element,
     elementState: TransitionState,
 ): Boolean {
+    if (element.key.placeAllCopies) {
+        return true
+    }
+
     val transition =
         when (elementState) {
             is TransitionState.Idle -> {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
index f9a9eeb..2e7488b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
@@ -79,6 +79,16 @@
      * or compose MovableElements.
      */
     open val contentPicker: ElementContentPicker = DefaultElementContentPicker,
+
+    /**
+     * Whether we should place all copies of this element when it is shared.
+     *
+     * This should usually be false, but it can be useful when sharing a container that has a
+     * different content in different scenes/overlays. That way the container will have the same
+     * size and position in all scenes/overlays but all different contents will be placed and
+     * visible on screen.
+     */
+    val placeAllCopies: Boolean = false,
 ) : Key(debugName, identity), ElementMatcher {
     @VisibleForTesting
     // TODO(b/240432457): Make internal once PlatformComposeSceneTransitionLayoutTestsUtils can
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index f20548b..cec8883 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -100,6 +100,10 @@
      * By default overlays are centered in their layout but they can be aligned differently using
      * [alignment].
      *
+     * If [isModal] is true (the default), then a protective layer will be added behind the overlay
+     * to prevent swipes from reaching other scenes or overlays behind this one. Clicking this
+     * protective layer will close the overlay.
+     *
      * Important: overlays must be defined after all scenes. Overlay order along the z-axis follows
      * call order. Calling overlay(A) followed by overlay(B) will mean that overlay B renders
      * after/above overlay A.
@@ -109,6 +113,7 @@
         userActions: Map<UserAction, UserActionResult> =
             mapOf(Back to UserActionResult.HideOverlay(key)),
         alignment: Alignment = Alignment.Center,
+        isModal: Boolean = true,
         content: @Composable ContentScope.() -> Unit,
     )
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index fe05234..65c4043 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -17,12 +17,16 @@
 package com.android.compose.animation.scene
 
 import androidx.annotation.VisibleForTesting
+import androidx.compose.foundation.clickable
 import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.key
+import androidx.compose.runtime.remember
 import androidx.compose.runtime.snapshots.SnapshotStateMap
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.ExperimentalComposeUiApi
@@ -253,6 +257,7 @@
                     key: OverlayKey,
                     userActions: Map<UserAction, UserActionResult>,
                     alignment: Alignment,
+                    isModal: Boolean,
                     content: @Composable (ContentScope.() -> Unit),
                 ) {
                     overlaysDefined = true
@@ -266,6 +271,7 @@
                         overlay.zIndex = zIndex
                         overlay.userActions = resolvedUserActions
                         overlay.alignment = alignment
+                        overlay.isModal = isModal
                     } else {
                         // New overlay.
                         overlays[key] =
@@ -276,6 +282,7 @@
                                 resolvedUserActions,
                                 zIndex,
                                 alignment,
+                                isModal,
                             )
                     }
 
@@ -399,12 +406,30 @@
             return
         }
 
-        // We put the overlays inside a Box that is matching the layout size so that overlays are
-        // measured after all scenes and that their max size is the size of the layout without the
-        // overlays.
-        Box(Modifier.matchParentSize().zIndex(overlaysOrderedByZIndex.first().zIndex)) {
-            overlaysOrderedByZIndex.fastForEach { overlay ->
-                key(overlay.key) { overlay.Content(Modifier.align(overlay.alignment)) }
+        overlaysOrderedByZIndex.fastForEach { overlay ->
+            val key = overlay.key
+            key(key) {
+                // We put the overlays inside a Box that is matching the layout size so that they
+                // are measured after all scenes and that their max size is the size of the layout
+                // without the overlays.
+                Box(Modifier.matchParentSize().zIndex(overlay.zIndex)) {
+                    if (overlay.isModal) {
+                        // Add a fullscreen clickable to prevent swipes from reaching the scenes and
+                        // other overlays behind this overlay. Clicking will close the overlay.
+                        Box(
+                            Modifier.fillMaxSize().clickable(
+                                interactionSource = remember { MutableInteractionSource() },
+                                indication = null,
+                            ) {
+                                if (state.canHideOverlay(key)) {
+                                    state.hideOverlay(key, animationScope = animationScope)
+                                }
+                            }
+                        )
+                    }
+
+                    overlay.Content(Modifier.align(overlay.alignment))
+                }
             }
         }
     }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index dbff8a4..2e8fc14 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -176,6 +176,35 @@
         animationScope: CoroutineScope,
         transitionKey: TransitionKey? = null,
     )
+
+    /**
+     * Instantly start a [transition], running it in [animationScope].
+     *
+     * This call returns immediately and [transition] will be the [currentTransition] of this
+     * [MutableSceneTransitionLayoutState].
+     *
+     * @see startTransition
+     */
+    fun startTransitionImmediately(
+        animationScope: CoroutineScope,
+        transition: TransitionState.Transition,
+        chain: Boolean = true,
+    ): Job
+
+    /**
+     * Start a new [transition].
+     *
+     * If [chain] is `true`, then the transitions will simply be added to [currentTransitions] and
+     * will run in parallel to the current transitions. If [chain] is `false`, then the list of
+     * [currentTransitions] will be cleared and [transition] will be the only running transition.
+     *
+     * If any transition is currently ongoing, it will be interrupted and forced to animate to its
+     * current state by calling [TransitionState.Transition.freezeAndAnimateToCurrentState].
+     *
+     * This method returns when [transition] is done running, i.e. when the call to
+     * [run][TransitionState.Transition.run] returns.
+     */
+    suspend fun startTransition(transition: TransitionState.Transition, chain: Boolean = true)
 }
 
 /**
@@ -313,18 +342,10 @@
         )
     }
 
-    /**
-     * Instantly start a [transition], running it in [animationScope].
-     *
-     * This call returns immediately and [transition] will be the [currentTransition] of this
-     * [MutableSceneTransitionLayoutState].
-     *
-     * @see startTransition
-     */
-    internal fun startTransitionImmediately(
+    override fun startTransitionImmediately(
         animationScope: CoroutineScope,
         transition: TransitionState.Transition,
-        chain: Boolean = true,
+        chain: Boolean,
     ): Job {
         // Note that we start with UNDISPATCHED so that startTransition() is called directly and
         // transition becomes the current [transitionState] right after this call.
@@ -333,23 +354,7 @@
         }
     }
 
-    /**
-     * Start a new [transition].
-     *
-     * If [chain] is `true`, then the transitions will simply be added to [currentTransitions] and
-     * will run in parallel to the current transitions. If [chain] is `false`, then the list of
-     * [currentTransitions] will be cleared and [transition] will be the only running transition.
-     *
-     * If any transition is currently ongoing, it will be interrupted and forced to animate to its
-     * current state.
-     *
-     * This method returns when [transition] is done running, i.e. when the call to
-     * [run][TransitionState.Transition.run] returns.
-     */
-    internal suspend fun startTransition(
-        transition: TransitionState.Transition,
-        chain: Boolean = true,
-    ) {
+    override suspend fun startTransition(transition: TransitionState.Transition, chain: Boolean) {
         checkThread()
 
         try {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Overlay.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Overlay.kt
index ccec9e8..d4de559 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Overlay.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Overlay.kt
@@ -37,8 +37,10 @@
     actions: Map<UserAction.Resolved, UserActionResult>,
     zIndex: Float,
     alignment: Alignment,
+    isModal: Boolean,
 ) : Content(key, layoutImpl, content, actions, zIndex) {
     var alignment by mutableStateOf(alignment)
+    var isModal by mutableStateOf(isModal)
 
     override fun toString(): String {
         return "Overlay(key=$key)"
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
index 364c203..d6751ae 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
@@ -300,7 +300,7 @@
         }
 
         /** Run this transition and return once it is finished. */
-        internal abstract suspend fun run()
+        abstract suspend fun run()
 
         /**
          * Freeze this transition state so that neither [currentScene] nor [currentOverlays] will
@@ -311,7 +311,7 @@
          *
          * This is called when this transition is interrupted (replaced) by another transition.
          */
-        internal abstract fun freezeAndAnimateToCurrentState()
+        abstract fun freezeAndAnimateToCurrentState()
 
         internal fun updateOverscrollSpecs(
             fromSpec: OverscrollSpecImpl?,
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index 43fc131..1eed54e 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -2519,4 +2519,65 @@
             .onNode(hasTestTag(contentTestTag) and hasParent(isElement(movable)))
             .assertSizeIsEqualTo(40.dp)
     }
+
+    @Test
+    fun placeAllCopies() {
+        val foo = ElementKey("Foo", placeAllCopies = true)
+
+        @Composable
+        fun SceneScope.Foo(size: Dp, modifier: Modifier = Modifier) {
+            Box(modifier.element(foo).size(size))
+        }
+
+        rule.testTransition(
+            fromSceneContent = { Box(Modifier.size(100.dp)) { Foo(size = 10.dp) } },
+            toSceneContent = {
+                Box(Modifier.size(100.dp)) {
+                    Foo(size = 50.dp, Modifier.align(Alignment.BottomEnd))
+                }
+            },
+            transition = { spec = tween(4 * 16, easing = LinearEasing) },
+        ) {
+            before {
+                onElement(foo, SceneA)
+                    .assertSizeIsEqualTo(10.dp)
+                    .assertPositionInRootIsEqualTo(0.dp, 0.dp)
+                onElement(foo, SceneB).assertDoesNotExist()
+            }
+
+            at(16) {
+                onElement(foo, SceneA)
+                    .assertSizeIsEqualTo(20.dp)
+                    .assertPositionInRootIsEqualTo(12.5.dp, 12.5.dp)
+                onElement(foo, SceneB)
+                    .assertSizeIsEqualTo(20.dp)
+                    .assertPositionInRootIsEqualTo(12.5.dp, 12.5.dp)
+            }
+
+            at(32) {
+                onElement(foo, SceneA)
+                    .assertSizeIsEqualTo(30.dp)
+                    .assertPositionInRootIsEqualTo(25.dp, 25.dp)
+                onElement(foo, SceneB)
+                    .assertSizeIsEqualTo(30.dp)
+                    .assertPositionInRootIsEqualTo(25.dp, 25.dp)
+            }
+
+            at(48) {
+                onElement(foo, SceneA)
+                    .assertSizeIsEqualTo(40.dp)
+                    .assertPositionInRootIsEqualTo(37.5.dp, 37.5.dp)
+                onElement(foo, SceneB)
+                    .assertSizeIsEqualTo(40.dp)
+                    .assertPositionInRootIsEqualTo(37.5.dp, 37.5.dp)
+            }
+
+            after {
+                onElement(foo, SceneA).assertDoesNotExist()
+                onElement(foo, SceneB)
+                    .assertSizeIsEqualTo(50.dp)
+                    .assertPositionInRootIsEqualTo(50.dp, 50.dp)
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt
index ffed15b..cae6617 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt
@@ -18,10 +18,12 @@
 
 import androidx.compose.animation.core.LinearEasing
 import androidx.compose.animation.core.tween
+import androidx.compose.foundation.ScrollState
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.verticalScroll
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
@@ -31,19 +33,26 @@
 import androidx.compose.runtime.snapshotFlow
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.assertIsDisplayed
 import androidx.compose.ui.test.assertIsNotDisplayed
 import androidx.compose.ui.test.assertPositionInRootIsEqualTo
+import androidx.compose.ui.test.click
 import androidx.compose.ui.test.hasTestTag
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.swipe
+import androidx.compose.ui.test.swipeUp
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.compose.animation.scene.TestOverlays.OverlayA
 import com.android.compose.animation.scene.TestOverlays.OverlayB
 import com.android.compose.animation.scene.TestScenes.SceneA
+import com.android.compose.animation.scene.subjects.assertThat
 import com.android.compose.test.assertSizeIsEqualTo
 import com.android.compose.test.setContentAndCreateMainScope
 import com.android.compose.test.subjects.assertThat
@@ -769,4 +778,59 @@
             .assertSizeIsEqualTo(100.dp)
             .assertIsDisplayed()
     }
+
+    @Test
+    fun overlaysAreModalByDefault() {
+        val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateImpl(SceneA) }
+
+        val scrollState = ScrollState(initial = 0)
+        val scope =
+            rule.setContentAndCreateMainScope {
+                SceneTransitionLayout(state) {
+                    // Make the scene vertically scrollable.
+                    scene(SceneA) {
+                        Box(Modifier.size(200.dp).verticalScroll(scrollState)) {
+                            Box(Modifier.size(200.dp, 400.dp))
+                        }
+                    }
+
+                    // The overlay is at the center end of the scene.
+                    overlay(OverlayA, alignment = Alignment.CenterEnd) {
+                        Box(Modifier.size(100.dp))
+                    }
+                }
+            }
+
+        fun swipeUp() {
+            rule.onRoot().performTouchInput {
+                swipe(start = Offset(x = 0f, y = bottom), end = Offset(x = 0f, y = top))
+            }
+        }
+
+        // Swiping up on the scene scrolls the list.
+        assertThat(scrollState.value).isEqualTo(0)
+        swipeUp()
+        assertThat(scrollState.value).isNotEqualTo(0)
+
+        // Reset the scroll.
+        scope.launch { scrollState.scrollTo(0) }
+        rule.waitForIdle()
+        assertThat(scrollState.value).isEqualTo(0)
+
+        // Show the overlay.
+        rule.runOnUiThread { state.showOverlay(OverlayA, animationScope = scope) }
+        rule.waitForIdle()
+        assertThat(state.transitionState).isIdle()
+        assertThat(state.transitionState).hasCurrentOverlays(OverlayA)
+
+        // Swiping up does not scroll the scene behind the overlay.
+        swipeUp()
+        assertThat(scrollState.value).isEqualTo(0)
+
+        // Clicking outside the overlay will close it.
+        rule.onRoot().performTouchInput { click(Offset.Zero) }
+        rule.waitForIdle()
+        assertThat(state.transitionState).isIdle()
+        assertThat(state.transitionState).hasCurrentOverlays(/* empty */ )
+    }
 }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt
index de55e2f..ea6f208 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt
@@ -31,16 +31,23 @@
 import com.android.compose.animation.scene.TransitionRecordingSpec
 import com.android.compose.animation.scene.featureOfElement
 import com.android.compose.animation.scene.recordTransition
+import org.junit.ClassRule
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import platform.test.motion.compose.ComposeFeatureCaptures
 import platform.test.motion.compose.createComposeMotionTestRule
 import platform.test.motion.testing.createGoldenPathManager
+import platform.test.screenshot.ResetDeviceEmulationRule
 
 @RunWith(AndroidJUnit4::class)
 @MotionTest
 class AnchoredSizeTest {
+
+    companion object {
+        @JvmField @ClassRule val cleanupRule: ResetDeviceEmulationRule = ResetDeviceEmulationRule()
+    }
+
     private val goldenPaths =
         createGoldenPathManager("frameworks/base/packages/SystemUI/compose/scene/tests/goldens")
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewControllerTest.java
index 43db5a7..ab59051 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewControllerTest.java
@@ -50,6 +50,9 @@
 import com.android.systemui.kosmos.KosmosJavaAdapter;
 import com.android.systemui.log.LogBuffer;
 import com.android.systemui.log.core.FakeLogBuffer;
+import com.android.systemui.privacy.PrivacyItem;
+import com.android.systemui.privacy.PrivacyItemController;
+import com.android.systemui.privacy.PrivacyType;
 import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository;
@@ -66,8 +69,10 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
+import java.util.Arrays;
 import java.util.List;
 import java.util.Optional;
 import java.util.concurrent.Executor;
@@ -107,6 +112,8 @@
     DreamOverlayStateController mDreamOverlayStateController;
     @Mock
     UserTracker mUserTracker;
+    @Mock
+    PrivacyItemController mPrivacyItemController;
 
     LogBuffer mLogBuffer = FakeLogBuffer.Factory.Companion.create();
 
@@ -146,6 +153,7 @@
                 mDreamOverlayStateController,
                 mUserTracker,
                 mKosmos.getWifiInteractor(),
+                mPrivacyItemController,
                 mKosmos.getCommunalSceneInteractor(),
                 mLogBuffer);
         mController.onInit();
@@ -160,6 +168,7 @@
         verify(mDreamOverlayNotificationCountProvider).addCallback(any());
         verify(mDreamOverlayStatusBarItemsProvider).addCallback(any());
         verify(mDreamOverlayStateController).addCallback(any());
+        verify(mPrivacyItemController).addCallback(any());
     }
 
     @Test
@@ -172,6 +181,52 @@
     }
 
     @Test
+    public void testLocationIconShownWhenLocationActive() {
+        mController.onViewAttached();
+        final ArgumentCaptor<PrivacyItemController.Callback> callbackCaptor =
+                ArgumentCaptor.forClass(PrivacyItemController.Callback.class);
+        verify(mPrivacyItemController).addCallback(callbackCaptor.capture());
+
+        final PrivacyItem item = Mockito.mock(PrivacyItem.class);
+        when(item.getPrivacyType()).thenReturn(PrivacyType.TYPE_LOCATION);
+        callbackCaptor.getValue().onPrivacyItemsChanged(Arrays.asList(item));
+
+        verify(mView).showIcon(
+                eq(AmbientStatusBarView.STATUS_ICON_LOCATION_ACTIVE), eq(true), any());
+    }
+
+    @Test
+    public void testLocationIconNotShownForOtherPrivacyItems() {
+        mController.onViewAttached();
+        final ArgumentCaptor<PrivacyItemController.Callback> callbackCaptor =
+                ArgumentCaptor.forClass(PrivacyItemController.Callback.class);
+        verify(mPrivacyItemController).addCallback(callbackCaptor.capture());
+
+        final PrivacyItem item = Mockito.mock(PrivacyItem.class);
+        when(item.getPrivacyType()).thenReturn(PrivacyType.TYPE_CAMERA);
+        callbackCaptor.getValue().onPrivacyItemsChanged(Arrays.asList(item));
+
+        verify(mView, never()).showIcon(
+                eq(AmbientStatusBarView.STATUS_ICON_LOCATION_ACTIVE), eq(true), any());
+    }
+
+    @Test
+    public void testLocationIconNotShownForNoItems() {
+        mController.onViewAttached();
+        final ArgumentCaptor<PrivacyItemController.Callback> callbackCaptor =
+                ArgumentCaptor.forClass(PrivacyItemController.Callback.class);
+        verify(mPrivacyItemController).addCallback(callbackCaptor.capture());
+
+        verify(mView, never()).showIcon(
+                eq(AmbientStatusBarView.STATUS_ICON_LOCATION_ACTIVE), eq(true), any());
+
+        callbackCaptor.getValue().onPrivacyItemsChanged(Arrays.asList());
+
+        verify(mView, never()).showIcon(
+                eq(AmbientStatusBarView.STATUS_ICON_LOCATION_ACTIVE), eq(true), any());
+    }
+
+    @Test
     public void testWifiIconHiddenWhenWifiAvailable() {
         mController.onViewAttached();
         mController.updateWifiUnavailableStatusIcon(true);
@@ -274,6 +329,7 @@
                 mDreamOverlayStateController,
                 mUserTracker,
                 mKosmos.getWifiInteractor(),
+                mPrivacyItemController,
                 mKosmos.getCommunalSceneInteractor(),
                 mLogBuffer);
         controller.onViewAttached();
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index 75ae414..b96e40f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -244,10 +244,7 @@
 
             val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK)
             userRepository.setUserInfos(userInfos)
-            userTracker.set(
-                userInfos = userInfos,
-                selectedUserIndex = 0,
-            )
+            userTracker.set(userInfos = userInfos, selectedUserIndex = 0)
             runCurrent()
 
             // Widgets available.
@@ -267,21 +264,14 @@
     fun smartspaceDynamicSizing_oneCard_fullSize() =
         testSmartspaceDynamicSizing(
             totalTargets = 1,
-            expectedSizes =
-                listOf(
-                    CommunalContentSize.FULL,
-                )
+            expectedSizes = listOf(CommunalContentSize.FULL),
         )
 
     @Test
     fun smartspace_dynamicSizing_twoCards_halfSize() =
         testSmartspaceDynamicSizing(
             totalTargets = 2,
-            expectedSizes =
-                listOf(
-                    CommunalContentSize.HALF,
-                    CommunalContentSize.HALF,
-                )
+            expectedSizes = listOf(CommunalContentSize.HALF, CommunalContentSize.HALF),
         )
 
     @Test
@@ -293,34 +283,34 @@
                     CommunalContentSize.THIRD,
                     CommunalContentSize.THIRD,
                     CommunalContentSize.THIRD,
-                )
+                ),
         )
 
     @Test
-    fun smartspace_dynamicSizing_fourCards_oneFullAndThreeThirdSize() =
+    fun smartspace_dynamicSizing_fourCards_threeThirdSizeAndOneFullSize() =
         testSmartspaceDynamicSizing(
             totalTargets = 4,
             expectedSizes =
                 listOf(
+                    CommunalContentSize.THIRD,
+                    CommunalContentSize.THIRD,
+                    CommunalContentSize.THIRD,
                     CommunalContentSize.FULL,
-                    CommunalContentSize.THIRD,
-                    CommunalContentSize.THIRD,
-                    CommunalContentSize.THIRD,
-                )
+                ),
         )
 
     @Test
-    fun smartspace_dynamicSizing_fiveCards_twoHalfAndThreeThirdSize() =
+    fun smartspace_dynamicSizing_fiveCards_threeThirdAndTwoHalfSize() =
         testSmartspaceDynamicSizing(
             totalTargets = 5,
             expectedSizes =
                 listOf(
+                    CommunalContentSize.THIRD,
+                    CommunalContentSize.THIRD,
+                    CommunalContentSize.THIRD,
                     CommunalContentSize.HALF,
                     CommunalContentSize.HALF,
-                    CommunalContentSize.THIRD,
-                    CommunalContentSize.THIRD,
-                    CommunalContentSize.THIRD,
-                )
+                ),
         )
 
     @Test
@@ -335,7 +325,7 @@
                     CommunalContentSize.THIRD,
                     CommunalContentSize.THIRD,
                     CommunalContentSize.THIRD,
-                )
+                ),
         )
 
     private fun testSmartspaceDynamicSizing(
@@ -355,7 +345,7 @@
 
             smartspaceRepository.setTimers(targets)
 
-            val smartspaceContent by collectLastValue(underTest.ongoingContent)
+            val smartspaceContent by collectLastValue(underTest.ongoingContent(false))
             assertThat(smartspaceContent?.size).isEqualTo(totalTargets)
             for (index in 0 until totalTargets) {
                 assertThat(smartspaceContent?.get(index)?.size).isEqualTo(expectedSizes[index])
@@ -371,7 +361,7 @@
             // Media is playing.
             mediaRepository.mediaActive()
 
-            val umoContent by collectLastValue(underTest.ongoingContent)
+            val umoContent by collectLastValue(underTest.ongoingContent(true))
 
             assertThat(umoContent?.size).isEqualTo(1)
             assertThat(umoContent?.get(0)).isInstanceOf(CommunalContentModel.Umo::class.java)
@@ -379,6 +369,19 @@
         }
 
     @Test
+    fun umo_mediaPlaying_mediaHostNotVisible_hidesUmo() =
+        testScope.runTest {
+            // Tutorial completed.
+            tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+
+            // Media is playing.
+            mediaRepository.mediaActive()
+
+            val umoContent by collectLastValue(underTest.ongoingContent(false))
+            assertThat(umoContent?.size).isEqualTo(0)
+        }
+
+    @Test
     fun ongoing_shouldOrderAndSizeByTimestamp() =
         testScope.runTest {
             // Keyguard showing, and tutorial completed.
@@ -401,19 +404,19 @@
             val timer3 = smartspaceTimer("timer3", timestamp = 4L)
             smartspaceRepository.setTimers(listOf(timer1, timer2, timer3))
 
-            val ongoingContent by collectLastValue(underTest.ongoingContent)
+            val ongoingContent by collectLastValue(underTest.ongoingContent(true))
             assertThat(ongoingContent?.size).isEqualTo(4)
             assertThat(ongoingContent?.get(0)?.key)
                 .isEqualTo(CommunalContentModel.KEY.smartspace("timer3"))
-            assertThat(ongoingContent?.get(0)?.size).isEqualTo(CommunalContentSize.FULL)
+            assertThat(ongoingContent?.get(0)?.size).isEqualTo(CommunalContentSize.HALF)
             assertThat(ongoingContent?.get(1)?.key)
                 .isEqualTo(CommunalContentModel.KEY.smartspace("timer2"))
-            assertThat(ongoingContent?.get(1)?.size).isEqualTo(CommunalContentSize.THIRD)
+            assertThat(ongoingContent?.get(1)?.size).isEqualTo(CommunalContentSize.HALF)
             assertThat(ongoingContent?.get(2)?.key).isEqualTo(CommunalContentModel.KEY.umo())
-            assertThat(ongoingContent?.get(2)?.size).isEqualTo(CommunalContentSize.THIRD)
+            assertThat(ongoingContent?.get(2)?.size).isEqualTo(CommunalContentSize.HALF)
             assertThat(ongoingContent?.get(3)?.key)
                 .isEqualTo(CommunalContentModel.KEY.smartspace("timer1"))
-            assertThat(ongoingContent?.get(3)?.size).isEqualTo(CommunalContentSize.THIRD)
+            assertThat(ongoingContent?.get(3)?.size).isEqualTo(CommunalContentSize.HALF)
         }
 
     @Test
@@ -435,10 +438,7 @@
         testScope.runTest {
             // Set to main user, so we can dismiss the tile for the main user.
             val user = userRepository.asMainUser()
-            userTracker.set(
-                userInfos = listOf(user),
-                selectedUserIndex = 0,
-            )
+            userTracker.set(userInfos = listOf(user), selectedUserIndex = 0)
             runCurrent()
 
             tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
@@ -816,10 +816,7 @@
             // Only main user exists.
             val userInfos = listOf(MAIN_USER_INFO)
             userRepository.setUserInfos(userInfos)
-            userTracker.set(
-                userInfos = userInfos,
-                selectedUserIndex = 0,
-            )
+            userTracker.set(userInfos = userInfos, selectedUserIndex = 0)
             runCurrent()
 
             val widgetContent by collectLastValue(underTest.widgetContent)
@@ -853,10 +850,7 @@
             // Work profile is set up.
             val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK)
             userRepository.setUserInfos(userInfos)
-            userTracker.set(
-                userInfos = userInfos,
-                selectedUserIndex = 0,
-            )
+            userTracker.set(userInfos = userInfos, selectedUserIndex = 0)
             runCurrent()
 
             // When work profile is paused.
@@ -899,10 +893,7 @@
 
             val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK)
             userRepository.setUserInfos(userInfos)
-            userTracker.set(
-                userInfos = userInfos,
-                selectedUserIndex = 0,
-            )
+            userTracker.set(userInfos = userInfos, selectedUserIndex = 0)
             userRepository.setSelectedUserInfo(MAIN_USER_INFO)
             runCurrent()
 
@@ -914,7 +905,7 @@
 
             setKeyguardFeaturesDisabled(
                 USER_INFO_WORK,
-                DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL
+                DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL,
             )
 
             // Widgets under work profile are filtered out. Only the regular widget remains.
@@ -932,10 +923,7 @@
 
             val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK)
             userRepository.setUserInfos(userInfos)
-            userTracker.set(
-                userInfos = userInfos,
-                selectedUserIndex = 0,
-            )
+            userTracker.set(userInfos = userInfos, selectedUserIndex = 0)
             userRepository.setSelectedUserInfo(MAIN_USER_INFO)
             runCurrent()
 
@@ -947,7 +935,7 @@
 
             setKeyguardFeaturesDisabled(
                 USER_INFO_WORK,
-                DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE
+                DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE,
             )
 
             // Widgets under work profile are available.
@@ -967,7 +955,7 @@
             kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
                 from = KeyguardState.GLANCEABLE_HUB,
                 to = KeyguardState.OCCLUDED,
-                testScope
+                testScope,
             )
 
             assertThat(showCommunalFromOccluded).isTrue()
@@ -983,7 +971,7 @@
             kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
                 from = KeyguardState.LOCKSCREEN,
                 to = KeyguardState.OCCLUDED,
-                testScope
+                testScope,
             )
 
             assertThat(showCommunalFromOccluded).isFalse()
@@ -999,7 +987,7 @@
             kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
                 from = KeyguardState.GLANCEABLE_HUB,
                 to = KeyguardState.OCCLUDED,
-                testScope
+                testScope,
             )
             runCurrent()
             kosmos.setCommunalAvailable(false)
@@ -1017,13 +1005,13 @@
             kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
                 from = KeyguardState.GLANCEABLE_HUB,
                 to = KeyguardState.OCCLUDED,
-                testScope
+                testScope,
             )
             runCurrent()
             kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
                 from = KeyguardState.OCCLUDED,
                 to = KeyguardState.PRIMARY_BOUNCER,
-                testScope
+                testScope,
             )
 
             assertThat(showCommunalFromOccluded).isTrue()
@@ -1039,7 +1027,7 @@
             kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
                 from = KeyguardState.DREAMING,
                 to = KeyguardState.OCCLUDED,
-                testScope
+                testScope,
             )
 
             assertThat(showCommunalFromOccluded).isTrue()
@@ -1049,7 +1037,7 @@
         return CommunalSmartspaceTimer(
             smartspaceTargetId = id,
             createdTimestampMillis = timestamp,
-            remoteViews = mock(RemoteViews::class.java)
+            remoteViews = mock(RemoteViews::class.java),
         )
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt
index b3a12a6..1e79112 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt
@@ -22,7 +22,6 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.widgets.CommunalAppWidgetHost
-import com.android.systemui.communal.widgets.CommunalAppWidgetHostView
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.kosmos.testScope
@@ -61,29 +60,11 @@
                 context = context,
                 backgroundScope = kosmos.applicationCoroutineScope,
                 hostId = 116,
-                interactionHandler = mock(),
-                looper = testableLooper.looper,
                 logBuffer = logcatLogBuffer("CommunalAppWidgetHostTest"),
             )
     }
 
     @Test
-    fun createViewForCommunal_returnCommunalAppWidgetView() {
-        val appWidgetId = 789
-        val view =
-            underTest.createViewForCommunal(
-                context = context,
-                appWidgetId = appWidgetId,
-                appWidget = null
-            )
-        testableLooper.processAllMessages()
-
-        assertThat(view).isInstanceOf(CommunalAppWidgetHostView::class.java)
-        assertThat(view).isNotNull()
-        assertThat(view.appWidgetId).isEqualTo(appWidgetId)
-    }
-
-    @Test
     fun appWidgetIdToRemove_emit() =
         testScope.runTest {
             val appWidgetIdToRemove by collectLastValue(underTest.appWidgetIdToRemove)
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 780d357..09daa51 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
@@ -43,6 +43,7 @@
 import com.android.systemui.communal.domain.interactor.communalTutorialInteractor
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.communal.shared.log.CommunalMetricsLogger
+import com.android.systemui.communal.shared.model.CommunalContentSize
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel.Companion.POPUP_AUTO_HIDE_TIMEOUT_MS
@@ -150,10 +151,7 @@
         kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
         mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
 
-        kosmos.fakeUserTracker.set(
-            userInfos = listOf(MAIN_USER_INFO),
-            selectedUserIndex = 0,
-        )
+        kosmos.fakeUserTracker.set(userInfos = listOf(MAIN_USER_INFO), selectedUserIndex = 0)
         whenever(mediaHost.visible).thenReturn(true)
 
         kosmos.powerInteractor.setAwakeForTest()
@@ -249,6 +247,87 @@
         }
 
     @Test
+    fun ongoingContent_umoAndOneTimer_sizedAppropriately() =
+        testScope.runTest {
+            // Widgets available.
+            widgetRepository.addWidget(appWidgetId = 0, rank = 30)
+            widgetRepository.addWidget(appWidgetId = 1, rank = 20)
+
+            // Smartspace available.
+            smartspaceRepository.setTimers(
+                listOf(
+                    CommunalSmartspaceTimer(
+                        smartspaceTargetId = "target",
+                        createdTimestampMillis = 0L,
+                        remoteViews = Mockito.mock(RemoteViews::class.java),
+                    )
+                )
+            )
+
+            // Media playing.
+            mediaRepository.mediaActive()
+
+            val communalContent by collectLastValue(underTest.communalContent)
+
+            // One timer, UMO, two widgets, and cta.
+            assertThat(communalContent?.size).isEqualTo(5)
+
+            val timer = communalContent?.get(0)
+            val umo = communalContent?.get(1)
+
+            assertThat(timer).isInstanceOf(CommunalContentModel.Smartspace::class.java)
+            assertThat(umo).isInstanceOf(CommunalContentModel.Umo::class.java)
+
+            assertThat(timer?.size).isEqualTo(CommunalContentSize.HALF)
+            assertThat(umo?.size).isEqualTo(CommunalContentSize.HALF)
+        }
+
+    @Test
+    fun ongoingContent_umoAndTwoTimers_sizedAppropriately() =
+        testScope.runTest {
+            // Widgets available.
+            widgetRepository.addWidget(appWidgetId = 0, rank = 30)
+            widgetRepository.addWidget(appWidgetId = 1, rank = 20)
+
+            // Smartspace available.
+            smartspaceRepository.setTimers(
+                listOf(
+                    CommunalSmartspaceTimer(
+                        smartspaceTargetId = "target",
+                        createdTimestampMillis = 0L,
+                        remoteViews = Mockito.mock(RemoteViews::class.java),
+                    ),
+                    CommunalSmartspaceTimer(
+                        smartspaceTargetId = "target",
+                        createdTimestampMillis = 0L,
+                        remoteViews = Mockito.mock(RemoteViews::class.java),
+                    ),
+                )
+            )
+
+            // Media playing.
+            mediaRepository.mediaActive()
+
+            val communalContent by collectLastValue(underTest.communalContent)
+
+            // Two timers, UMO, two widgets, and cta.
+            assertThat(communalContent?.size).isEqualTo(6)
+
+            val timer1 = communalContent?.get(0)
+            val timer2 = communalContent?.get(1)
+            val umo = communalContent?.get(2)
+
+            assertThat(timer1).isInstanceOf(CommunalContentModel.Smartspace::class.java)
+            assertThat(timer2).isInstanceOf(CommunalContentModel.Smartspace::class.java)
+            assertThat(umo).isInstanceOf(CommunalContentModel.Umo::class.java)
+
+            // One full-sized timer and a half-sized timer and half-sized UMO.
+            assertThat(timer1?.size).isEqualTo(CommunalContentSize.HALF)
+            assertThat(timer2?.size).isEqualTo(CommunalContentSize.HALF)
+            assertThat(umo?.size).isEqualTo(CommunalContentSize.FULL)
+        }
+
+    @Test
     fun communalContent_mediaHostVisible_umoIncluded() =
         testScope.runTest {
             // Media playing.
@@ -497,7 +576,7 @@
                     TransitionStep(
                         from = KeyguardState.LOCKSCREEN,
                         to = KeyguardState.GLANCEABLE_HUB,
-                    )
+                    ),
             )
             // Shade not expanded.
             if (!SceneContainerFlag.isEnabled) shadeTestUtil.setLockscreenShadeExpansion(0f)
@@ -550,8 +629,8 @@
                 stateTransition =
                     TransitionStep(
                         from = KeyguardState.DREAMING,
-                        to = KeyguardState.GLANCEABLE_HUB,
-                    )
+                        to = KeyguardState.GLANCEABLE_HUB
+                    ),
             )
 
             // Then flow is not frozen
@@ -570,8 +649,8 @@
                 stateTransition =
                     TransitionStep(
                         from = KeyguardState.GLANCEABLE_HUB,
-                        to = KeyguardState.OCCLUDED,
-                    )
+                        to = KeyguardState.OCCLUDED
+                    ),
             )
 
             // Then flow is not frozen
@@ -595,7 +674,7 @@
                     TransitionStep(
                         from = KeyguardState.LOCKSCREEN,
                         to = KeyguardState.GLANCEABLE_HUB,
-                    )
+                    ),
             )
 
             // Then flow is not frozen
@@ -614,7 +693,7 @@
                         to = KeyguardState.OCCLUDED,
                         transitionState = TransitionState.STARTED,
                         value = 0f,
-                    )
+                    ),
             )
 
             // Then flow is frozen
@@ -629,7 +708,7 @@
                         to = KeyguardState.OCCLUDED,
                         transitionState = TransitionState.FINISHED,
                         value = 1f,
-                    )
+                    ),
             )
 
             // Then flow is not frozen
@@ -658,8 +737,8 @@
                 stateTransition =
                     TransitionStep(
                         from = KeyguardState.DREAMING,
-                        to = KeyguardState.GLANCEABLE_HUB,
-                    )
+                        to = KeyguardState.GLANCEABLE_HUB
+                    ),
             )
 
             // Widgets available
diff --git a/packages/SystemUI/tests/src/com/android/systemui/decor/CutoutDecorProviderFactoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/decor/CutoutDecorProviderFactoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/decor/CutoutDecorProviderFactoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/decor/CutoutDecorProviderFactoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/decor/OverlayWindowTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/decor/OverlayWindowTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/decor/OverlayWindowTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/decor/OverlayWindowTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerDecorProviderFactoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/decor/RoundedCornerDecorProviderFactoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerDecorProviderFactoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/decor/RoundedCornerDecorProviderFactoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/demomode/DemoModeControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/demomode/DemoModeControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/demomode/DemoModeControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/demomode/DemoModeControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/FaceWakeUpTriggersConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/FaceWakeUpTriggersConfigTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/FaceWakeUpTriggersConfigTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/FaceWakeUpTriggersConfigTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricsAllowedInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricsAllowedInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricsAllowedInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricsAllowedInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthStatusInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthStatusInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthStatusInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthStatusInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/shared/FaceAuthReasonTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/shared/FaceAuthReasonTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/deviceentry/shared/FaceAuthReasonTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/shared/FaceAuthReasonTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/devicepolicy/DevicePolicyManagerExtTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/devicepolicy/DevicePolicyManagerExtTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/devicepolicy/DevicePolicyManagerExtTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/devicepolicy/DevicePolicyManagerExtTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DeviceStateRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DeviceStateRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DeviceStateRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DeviceStateRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayMetricsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayMetricsRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayMetricsRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayMetricsRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogDelegateTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogDelegateTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogDelegateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/AlwaysOnDisplayPolicyTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/doze/AlwaysOnDisplayPolicyTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/doze/AlwaysOnDisplayPolicyTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/doze/AlwaysOnDisplayPolicyTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/doze/DozeConfigurationTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/doze/DozeConfigurationTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/doze/DozeConfigurationUtil.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/doze/DozeConfigurationUtil.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/doze/DozeDockHandlerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/doze/DozeDockHandlerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStatePreventingAdapterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/doze/DozeScreenStatePreventingAdapterTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStatePreventingAdapterTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/doze/DozeScreenStatePreventingAdapterTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeServiceFake.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/doze/DozeServiceFake.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/doze/DozeServiceFake.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/doze/DozeServiceFake.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/doze/DozeSuppressorTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressorTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/doze/DozeSuppressorTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapterTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapterTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapterTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/doze/DozeUiTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/doze/DozeUiTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeWallpaperStateTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/doze/DozeWallpaperStateTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/doze/DozeWallpaperStateTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/doze/DozeWallpaperStateTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dump/DumpHandlerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/dump/DumpHandlerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dump/DumpManagerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/dump/DumpManagerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/dump/DumpManagerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpsysTableLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dump/DumpsysTableLoggerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/dump/DumpsysTableLoggerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/dump/DumpsysTableLoggerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferFreezerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dump/LogBufferFreezerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferFreezerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/dump/LogBufferFreezerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/LogEulogizerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dump/LogEulogizerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/dump/LogEulogizerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/dump/LogEulogizerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/emergency/EmergencyActivityTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/emergency/EmergencyActivityTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/emergency/EmergencyActivityTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/emergency/EmergencyActivityTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/ConditionalRestarterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/flags/ConditionalRestarterTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/flags/ConditionalRestarterTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/flags/ConditionalRestarterTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicReleaseTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/flags/FeatureFlagsClassicReleaseTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicReleaseTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/flags/FeatureFlagsClassicReleaseTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/flags/FlagCommandTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/flags/FlagCommandTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagDependenciesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/flags/FlagDependenciesTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/flags/FlagDependenciesTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/flags/FlagDependenciesTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/flags/FlagManagerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/flags/FlagManagerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/NotOccludedConditionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/flags/NotOccludedConditionTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/flags/NotOccludedConditionTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/flags/NotOccludedConditionTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/flags/PluggedInConditionTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/flags/PluggedInConditionTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/RestartDozeListenerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/flags/RestartDozeListenerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/flags/RestartDozeListenerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/flags/RestartDozeListenerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/fragments/FragmentServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/fragments/FragmentServiceTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/fragments/FragmentServiceTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/fragments/FragmentServiceTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsGridLayoutTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/globalactions/GlobalActionsGridLayoutTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsGridLayoutTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/globalactions/GlobalActionsGridLayoutTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsLayoutTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/globalactions/GlobalActionsLayoutTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsLayoutTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/globalactions/GlobalActionsLayoutTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/ListGridLayoutTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/globalactions/ListGridLayoutTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/globalactions/ListGridLayoutTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/globalactions/ListGridLayoutTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/ShutdownUiTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/globalactions/ShutdownUiTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/globalactions/ShutdownUiTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/globalactions/ShutdownUiTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/data/repository/GlobalActionsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/globalactions/data/repository/GlobalActionsRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/globalactions/data/repository/GlobalActionsRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/globalactions/data/repository/GlobalActionsRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/domain/interactor/GlobalActionsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/globalactions/domain/interactor/GlobalActionsInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/globalactions/domain/interactor/GlobalActionsInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/globalactions/domain/interactor/GlobalActionsInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/FakeSliderEventProducer.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/FakeSliderEventProducer.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/haptics/slider/FakeSliderEventProducer.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/FakeSliderEventProducer.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/inputdevice/data/repository/TutorialSchedulerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/data/repository/TutorialSchedulerRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/inputdevice/data/repository/TutorialSchedulerRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/data/repository/TutorialSchedulerRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/inputdevice/data/repository/UserInputDeviceRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/data/repository/UserInputDeviceRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/inputdevice/data/repository/UserInputDeviceRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/data/repository/UserInputDeviceRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartableTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartableTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartableTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialNotificationCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialNotificationCoordinatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialNotificationCoordinatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialNotificationCoordinatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/backlight/domain/interactor/KeyboardBacklightInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/backlight/domain/interactor/KeyboardBacklightInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyboard/backlight/domain/interactor/KeyboardBacklightInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/backlight/domain/interactor/KeyboardBacklightInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/backlight/ui/KeyboardBacklightDialogCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/backlight/ui/KeyboardBacklightDialogCoordinatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyboard/backlight/ui/KeyboardBacklightDialogCoordinatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/backlight/ui/KeyboardBacklightDialogCoordinatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperStateRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperStateRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperStateRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperStateRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/AppCategoriesShortcutsSourceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/AppCategoriesShortcutsSourceTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/AppCategoriesShortcutsSourceTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/AppCategoriesShortcutsSourceTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/CurrentAppShortcutsSourceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/CurrentAppShortcutsSourceTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/CurrentAppShortcutsSourceTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/CurrentAppShortcutsSourceTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/InputShortcutsSourceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/InputShortcutsSourceTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/InputShortcutsSourceTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/InputShortcutsSourceTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyevent/domain/interactor/SysUIKeyEventHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyevent/domain/interactor/SysUIKeyEventHandlerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyevent/domain/interactor/SysUIKeyEventHandlerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyevent/domain/interactor/SysUIKeyEventHandlerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/DismissCallbackRegistryTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/DismissCallbackRegistryTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/DismissCallbackRegistryTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/DismissCallbackRegistryTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/KeyguardIndicationTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/KeyguardIndicationTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ScreenLifecycleTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ScreenLifecycleTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/ScreenLifecycleTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ScreenLifecycleTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/WorkLockActivityTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/WorkLockActivityTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfigTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfigTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfigTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyEventRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyEventRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyEventRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyEventRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepositoryImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepositoryImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepositoryImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
similarity index 95%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
index 1981a2d..41cc953 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
@@ -25,7 +25,7 @@
 import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
 import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
 import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.shared.model.DismissAction
@@ -39,8 +39,6 @@
 import com.android.systemui.scene.data.repository.Transition
 import com.android.systemui.scene.data.repository.setSceneTransition
 import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.domain.resolver.notifShadeSceneFamilyResolver
-import com.android.systemui.scene.domain.resolver.quickSettingsSceneFamilyResolver
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.testKosmos
@@ -79,12 +77,9 @@
                 dismissInteractor = dismissInteractor,
                 applicationScope = testScope.backgroundScope,
                 sceneInteractor = { kosmos.sceneInteractor },
-                deviceEntryInteractor = { kosmos.deviceEntryInteractor },
-                quickSettingsSceneFamilyResolver = { kosmos.quickSettingsSceneFamilyResolver },
-                notifShadeSceneFamilyResolver = { kosmos.notifShadeSceneFamilyResolver },
+                deviceUnlockedInteractor = { kosmos.deviceUnlockedInteractor },
                 powerInteractor = kosmos.powerInteractor,
                 alternateBouncerInteractor = kosmos.alternateBouncerInteractor,
-                keyguardInteractor = { kosmos.keyguardInteractor },
                 shadeInteractor = { kosmos.shadeInteractor },
             )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/SwipeToDismissInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/SwipeToDismissInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/SwipeToDismissInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/SwipeToDismissInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/InWindowLauncherUnlockAnimationManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/InWindowLauncherUnlockAnimationManagerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/InWindowLauncherUnlockAnimationManagerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/InWindowLauncherUnlockAnimationManagerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplierTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplierTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplierTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplierTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListenerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListenerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListenerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListenerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index e4098ae..2fd94e2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -507,6 +507,46 @@
 
     @Test
     @DisableSceneContainer
+    fun alphaFromShadeExpansion_doesNotEmitWhenOccludedTransitionRunning() =
+        testScope.runTest {
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.AOD,
+                to = KeyguardState.LOCKSCREEN,
+                testScope,
+            )
+
+            val alpha by collectLastValue(underTest.alpha(viewState))
+            shadeTestUtil.setQsExpansion(0f)
+            runCurrent()
+            assertThat(alpha).isEqualTo(1f)
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                listOf(
+                    TransitionStep(
+                        from = KeyguardState.LOCKSCREEN,
+                        to = KeyguardState.OCCLUDED,
+                        transitionState = TransitionState.STARTED,
+                        value = 0f,
+                    ),
+                    TransitionStep(
+                        from = KeyguardState.LOCKSCREEN,
+                        to = KeyguardState.OCCLUDED,
+                        transitionState = TransitionState.RUNNING,
+                        value = 0.8f,
+                    ),
+                ),
+                testScope,
+            )
+            // Alpha should be 0f from the above transition
+            assertThat(alpha).isEqualTo(0f)
+
+            shadeTestUtil.setQsExpansion(0.5f)
+            // Alpha should remain unchanged
+            assertThat(alpha).isEqualTo(0f)
+        }
+
+    @Test
+    @DisableSceneContainer
     fun alphaFromShadeExpansion_doesNotEmitWhenLockscreenToDreamTransitionRunning() =
         testScope.runTest {
             keyguardTransitionRepository.sendTransitionSteps(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/ActivityManagerWrapperMock.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/util/ActivityManagerWrapperMock.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/util/ActivityManagerWrapperMock.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/util/ActivityManagerWrapperMock.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/IndicationHelperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/util/IndicationHelperTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/util/IndicationHelperTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/util/IndicationHelperTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/lifecycle/ActivatableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/ActivatableTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/lifecycle/ActivatableTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/ActivatableTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/log/SessionTrackerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/log/SessionTrackerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/core/LoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/log/core/LoggerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/log/core/LoggerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/log/core/LoggerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/echo/LogcatEchoSettingsFormatTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/log/echo/LogcatEchoSettingsFormatTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/log/echo/LogcatEchoSettingsFormatTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/log/echo/LogcatEchoSettingsFormatTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/echo/LogcatEchoTrackerDebugTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/log/echo/LogcatEchoTrackerDebugTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/log/echo/LogcatEchoTrackerDebugTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/log/echo/LogcatEchoTrackerDebugTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/FakeLogProxy.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/log/table/FakeLogProxy.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/log/table/FakeLogProxy.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/log/table/FakeLogProxy.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/log/table/TableChangeTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/log/table/TableChangeTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/MediaTestUtils.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/MediaTestUtils.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/media/controls/MediaTestUtils.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/MediaTestUtils.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/shared/SmartspaceMediaDataTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/shared/SmartspaceMediaDataTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/media/controls/shared/SmartspaceMediaDataTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/shared/SmartspaceMediaDataTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/binder/SeekBarObserverTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/binder/SeekBarObserverTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/binder/SeekBarObserverTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/binder/SeekBarObserverTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/view/MediaViewHolderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaViewHolderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/view/MediaViewHolderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaViewHolderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/util/MediaDataUtilsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/util/MediaDataUtilsTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/media/controls/util/MediaDataUtilsTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/util/MediaDataUtilsTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaComplicationViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dream/MediaComplicationViewControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaComplicationViewControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/media/dream/MediaComplicationViewControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/nearby/NearbyMediaDevicesManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/nearby/NearbyMediaDevicesManagerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/media/nearby/NearbyMediaDevicesManagerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/media/nearby/NearbyMediaDevicesManagerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/systemsounds/HomeSoundEffectControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/systemsounds/HomeSoundEffectControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/media/systemsounds/HomeSoundEffectControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/media/systemsounds/HomeSoundEffectControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtilsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtilsTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtilsTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtilsTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLoggerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLoggerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLoggerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverUiEventLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverUiEventLoggerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverUiEventLoggerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverUiEventLoggerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLoggerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLoggerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLoggerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderUiEventLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderUiEventLoggerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderUiEventLoggerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderUiEventLoggerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLoggerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLoggerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLoggerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/BasicPackageManagerAppIconLoaderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/data/BasicPackageManagerAppIconLoaderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/BasicPackageManagerAppIconLoaderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/data/BasicPackageManagerAppIconLoaderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/WindowMetricsProviderImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/view/WindowMetricsProviderImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/WindowMetricsProviderImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/view/WindowMetricsProviderImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolverTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolverTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolverTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolverTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/ShareToAppPermissionDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/permission/ShareToAppPermissionDialogDelegateTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/ShareToAppPermissionDialogDelegateTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/permission/ShareToAppPermissionDialogDelegateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/SystemCastPermissionDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/permission/SystemCastPermissionDialogDelegateTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/SystemCastPermissionDialogDelegateTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/permission/SystemCastPermissionDialogDelegateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeTasksRepository.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeTasksRepository.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeTasksRepository.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeTasksRepository.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateExtTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUiStateExtTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateExtTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUiStateExtTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUiStateTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUiStateTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/motion/ComposeMotionTestRuleHelper.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/motion/ComposeMotionTestRuleHelper.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/motion/ComposeMotionTestRuleHelper.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/motion/ComposeMotionTestRuleHelper.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModelTest.kt
index f6865f13..642d9a0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModelTest.kt
@@ -21,6 +21,7 @@
 import androidx.test.filters.SmallTest
 import com.android.compose.animation.scene.Back
 import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
 import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
@@ -28,6 +29,7 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.lifecycle.activateIn
 import com.android.systemui.scene.shared.model.Overlays
+import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge
 import com.android.systemui.shade.ui.viewmodel.notificationsShadeOverlayActionsViewModel
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
@@ -47,7 +49,7 @@
     private val underTest = kosmos.notificationsShadeOverlayActionsViewModel
 
     @Test
-    fun upTransitionSceneKey_hidesShade() =
+    fun up_hidesShade() =
         testScope.runTest {
             val actions by collectLastValue(underTest.actions)
             underTest.activateIn(this)
@@ -66,4 +68,22 @@
             assertThat((actions?.get(Back) as? UserActionResult.HideOverlay)?.overlay)
                 .isEqualTo(Overlays.NotificationsShade)
         }
+
+    @Test
+    fun downFromTopRight_switchesToQuickSettingsShade() =
+        testScope.runTest {
+            val actions by collectLastValue(underTest.actions)
+            underTest.activateIn(this)
+
+            assertThat(
+                    (actions?.get(
+                            Swipe(
+                                direction = SwipeDirection.Down,
+                                fromSource = SceneContainerEdge.TopRight,
+                            )
+                        ) as? UserActionResult.ReplaceByOverlay)
+                        ?.overlay
+                )
+                .isEqualTo(Overlays.QuickSettingsShade)
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiStateTest.kt
new file mode 100644
index 0000000..b144f06
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiStateTest.kt
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.ui.viewmodel
+
+import android.content.res.Resources
+import android.content.res.mainResources
+import android.service.quicksettings.Tile
+import android.widget.Button
+import android.widget.Switch
+import androidx.compose.ui.semantics.Role
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.res.R
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class TileUiStateTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val resources: Resources
+        get() = kosmos.mainResources
+
+    @Test
+    fun stateUnavailable_secondaryLabelNotmodified() {
+        val testString = "TEST STRING"
+        val state =
+            QSTile.State().apply {
+                state = Tile.STATE_UNAVAILABLE
+                secondaryLabel = testString
+            }
+
+        val uiState = state.toUiState()
+
+        assertThat(uiState.state).isEqualTo(Tile.STATE_UNAVAILABLE)
+    }
+
+    @Test
+    fun accessibilityRole_switch() {
+        val stateSwitch =
+            QSTile.State().apply { expandedAccessibilityClassName = Switch::class.java.name }
+        val uiState = stateSwitch.toUiState()
+        assertThat(uiState.accessibilityRole).isEqualTo(Role.Switch)
+    }
+
+    @Test
+    fun accessibilityRole_button() {
+        val stateButton =
+            QSTile.State().apply { expandedAccessibilityClassName = Button::class.java.name }
+        val uiState = stateButton.toUiState()
+        assertThat(uiState.accessibilityRole).isEqualTo(Role.Button)
+    }
+
+    @Test
+    fun accessibilityRole_switchWithSecondaryClick() {
+        val stateSwitchWithSecondaryClick =
+            QSTile.State().apply {
+                expandedAccessibilityClassName = Switch::class.java.name
+                handlesSecondaryClick = true
+            }
+        val uiState = stateSwitchWithSecondaryClick.toUiState()
+        assertThat(uiState.accessibilityRole).isEqualTo(Role.Button)
+    }
+
+    @Test
+    fun switchInactive_secondaryLabelNotModified() {
+        val testString = "TEST STRING"
+        val state =
+            QSTile.State().apply {
+                expandedAccessibilityClassName = Switch::class.java.name
+                state = Tile.STATE_INACTIVE
+                secondaryLabel = testString
+            }
+
+        val uiState = state.toUiState()
+
+        assertThat(uiState.secondaryLabel).isEqualTo(testString)
+    }
+
+    @Test
+    fun switchActive_secondaryLabelNotModified() {
+        val testString = "TEST STRING"
+        val state =
+            QSTile.State().apply {
+                expandedAccessibilityClassName = Switch::class.java.name
+                state = Tile.STATE_ACTIVE
+                secondaryLabel = testString
+            }
+
+        val uiState = state.toUiState()
+
+        assertThat(uiState.secondaryLabel).isEqualTo(testString)
+    }
+
+    @Test
+    fun buttonInactive_secondaryLabelNotModifiedWhenEmpty() {
+        val state =
+            QSTile.State().apply {
+                expandedAccessibilityClassName = Button::class.java.name
+                state = Tile.STATE_INACTIVE
+                secondaryLabel = ""
+            }
+
+        val uiState = state.toUiState()
+
+        assertThat(uiState.secondaryLabel).isEmpty()
+    }
+
+    @Test
+    fun buttonActive_secondaryLabelNotModifiedWhenEmpty() {
+        val state =
+            QSTile.State().apply {
+                expandedAccessibilityClassName = Button::class.java.name
+                state = Tile.STATE_ACTIVE
+                secondaryLabel = ""
+            }
+
+        val uiState = state.toUiState()
+
+        assertThat(uiState.secondaryLabel).isEmpty()
+    }
+
+    @Test
+    fun buttonUnavailable_emptySecondaryLabel_default() {
+        val state =
+            QSTile.State().apply {
+                expandedAccessibilityClassName = Button::class.java.name
+                state = Tile.STATE_UNAVAILABLE
+                secondaryLabel = ""
+            }
+
+        val uiState = state.toUiState()
+
+        assertThat(uiState.secondaryLabel).isEqualTo(resources.getString(R.string.tile_unavailable))
+    }
+
+    @Test
+    fun switchUnavailable_emptySecondaryLabel_defaultUnavailable() {
+        val state =
+            QSTile.State().apply {
+                expandedAccessibilityClassName = Switch::class.java.name
+                state = Tile.STATE_UNAVAILABLE
+                secondaryLabel = ""
+            }
+
+        val uiState = state.toUiState()
+
+        assertThat(uiState.secondaryLabel).isEqualTo(resources.getString(R.string.tile_unavailable))
+    }
+
+    @Test
+    fun switchInactive_emptySecondaryLabel_defaultOff() {
+        val state =
+            QSTile.State().apply {
+                expandedAccessibilityClassName = Switch::class.java.name
+                state = Tile.STATE_INACTIVE
+                secondaryLabel = ""
+            }
+
+        val uiState = state.toUiState()
+
+        assertThat(uiState.secondaryLabel).isEqualTo(resources.getString(R.string.switch_bar_off))
+    }
+
+    @Test
+    fun switchActive_emptySecondaryLabel_defaultOn() {
+        val state =
+            QSTile.State().apply {
+                expandedAccessibilityClassName = Switch::class.java.name
+                state = Tile.STATE_ACTIVE
+                secondaryLabel = ""
+            }
+
+        val uiState = state.toUiState()
+
+        assertThat(uiState.secondaryLabel).isEqualTo(resources.getString(R.string.switch_bar_on))
+    }
+
+    @Test
+    fun disabledByPolicy_inactive_appearsAsUnavailable() {
+        val stateDisabledByPolicy =
+            QSTile.State().apply {
+                state = Tile.STATE_INACTIVE
+                disabledByPolicy = true
+            }
+
+        val uiState = stateDisabledByPolicy.toUiState()
+
+        assertThat(uiState.state).isEqualTo(Tile.STATE_UNAVAILABLE)
+    }
+
+    @Test
+    fun disabledByPolicy_active_appearsAsUnavailable() {
+        val stateDisabledByPolicy =
+            QSTile.State().apply {
+                state = Tile.STATE_ACTIVE
+                disabledByPolicy = true
+            }
+
+        val uiState = stateDisabledByPolicy.toUiState()
+
+        assertThat(uiState.state).isEqualTo(Tile.STATE_UNAVAILABLE)
+    }
+
+    @Test
+    fun disabledByPolicy_clickLabel() {
+        val stateDisabledByPolicy =
+            QSTile.State().apply {
+                state = Tile.STATE_INACTIVE
+                disabledByPolicy = true
+            }
+
+        val uiState = stateDisabledByPolicy.toUiState()
+        assertThat(uiState.accessibilityUiState.clickLabel)
+            .isEqualTo(
+                resources.getString(
+                    R.string.accessibility_tile_disabled_by_policy_action_description
+                )
+            )
+    }
+
+    @Test
+    fun notDisabledByPolicy_clickLabel_null() {
+        val stateDisabledByPolicy =
+            QSTile.State().apply {
+                state = Tile.STATE_INACTIVE
+                disabledByPolicy = false
+            }
+
+        val uiState = stateDisabledByPolicy.toUiState()
+        assertThat(uiState.accessibilityUiState.clickLabel).isNull()
+    }
+
+    @Test
+    fun disabledByPolicy_unavailableInStateDescription() {
+        val state =
+            QSTile.State().apply {
+                disabledByPolicy = true
+                state = Tile.STATE_INACTIVE
+            }
+
+        val uiState = state.toUiState()
+        assertThat(uiState.accessibilityUiState.stateDescription)
+            .contains(resources.getString(R.string.tile_unavailable))
+    }
+
+    private fun QSTile.State.toUiState() = toUiState(resources)
+}
+
+private val TileUiState.accessibilityRole: Role
+    get() = accessibilityUiState.accessibilityRole
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelTest.kt
index 762941d..fd1c043 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelTest.kt
@@ -21,6 +21,7 @@
 import androidx.test.filters.SmallTest
 import com.android.compose.animation.scene.Back
 import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
 import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
@@ -28,6 +29,7 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.lifecycle.activateIn
 import com.android.systemui.scene.shared.model.Overlays
+import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.test.runTest
@@ -46,7 +48,7 @@
     private val underTest = kosmos.quickSettingsShadeOverlayActionsViewModel
 
     @Test
-    fun upTransitionSceneKey_hidesShade() =
+    fun up_hidesShade() =
         testScope.runTest {
             val actions by collectLastValue(underTest.actions)
             underTest.activateIn(this)
@@ -57,12 +59,44 @@
         }
 
     @Test
-    fun back_hidesShade() =
+    fun back_notEditing_hidesShade() =
+        testScope.runTest {
+            val actions by collectLastValue(underTest.actions)
+            val isEditing by
+                collectLastValue(kosmos.quickSettingsContainerViewModel.editModeViewModel.isEditing)
+            underTest.activateIn(this)
+            assertThat(isEditing).isFalse()
+
+            assertThat((actions?.get(Back) as? UserActionResult.HideOverlay)?.overlay)
+                .isEqualTo(Overlays.QuickSettingsShade)
+        }
+
+    @Test
+    fun back_whileEditing_doesNotHideShade() =
         testScope.runTest {
             val actions by collectLastValue(underTest.actions)
             underTest.activateIn(this)
 
-            assertThat((actions?.get(Back) as? UserActionResult.HideOverlay)?.overlay)
-                .isEqualTo(Overlays.QuickSettingsShade)
+            kosmos.quickSettingsContainerViewModel.editModeViewModel.startEditing()
+
+            assertThat(actions?.get(Back)).isNull()
+        }
+
+    @Test
+    fun downFromTopLeft_switchesToNotificationsShade() =
+        testScope.runTest {
+            val actions by collectLastValue(underTest.actions)
+            underTest.activateIn(this)
+
+            assertThat(
+                    (actions?.get(
+                            Swipe(
+                                direction = SwipeDirection.Down,
+                                fromSource = SceneContainerEdge.TopLeft,
+                            )
+                        ) as? UserActionResult.ReplaceByOverlay)
+                        ?.overlay
+                )
+                .isEqualTo(Overlays.NotificationsShade)
         }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsUserActionsViewModelTest.kt
index 6986cf8e..62b6391 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsUserActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsUserActionsViewModelTest.kt
@@ -37,7 +37,6 @@
 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.sceneBackInteractor
 import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -46,7 +45,6 @@
 import com.android.systemui.scene.shared.model.SceneFamilies
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -61,7 +59,7 @@
 
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
-    private val qsFlexiglassAdapter = FakeQSSceneAdapter({ mock() })
+    private val qsFlexiglassAdapter = kosmos.fakeQsSceneAdapter
 
     private val sceneInteractor = kosmos.sceneInteractor
     private val sceneBackInteractor = kosmos.sceneBackInteractor
@@ -101,10 +99,8 @@
                     mapOf(
                         Back to UserActionResult(Scenes.Shade),
                         Swipe(SwipeDirection.Up) to UserActionResult(Scenes.Shade),
-                        Swipe(
-                            fromSource = Edge.Bottom,
-                            direction = SwipeDirection.Up,
-                        ) to UserActionResult(SceneFamilies.Home)
+                        Swipe(fromSource = Edge.Bottom, direction = SwipeDirection.Up) to
+                            UserActionResult(SceneFamilies.Home),
                     )
                 )
             assertThat(homeScene).isEqualTo(Scenes.Gone)
@@ -130,10 +126,8 @@
                     mapOf(
                         Back to UserActionResult(Scenes.Lockscreen),
                         Swipe(SwipeDirection.Up) to UserActionResult(Scenes.Lockscreen),
-                        Swipe(
-                            fromSource = Edge.Bottom,
-                            direction = SwipeDirection.Up,
-                        ) to UserActionResult(SceneFamilies.Home)
+                        Swipe(fromSource = Edge.Bottom, direction = SwipeDirection.Up) to
+                            UserActionResult(SceneFamilies.Home),
                     )
                 )
             assertThat(homeScene).isEqualTo(Scenes.Lockscreen)
@@ -161,10 +155,8 @@
                     mapOf(
                         Back to UserActionResult(Scenes.Shade),
                         Swipe(SwipeDirection.Up) to UserActionResult(Scenes.Shade),
-                        Swipe(
-                            fromSource = Edge.Bottom,
-                            direction = SwipeDirection.Up,
-                        ) to UserActionResult(SceneFamilies.Home)
+                        Swipe(fromSource = Edge.Bottom, direction = SwipeDirection.Up) to
+                            UserActionResult(SceneFamilies.Home),
                     )
                 )
             assertThat(homeScene).isEqualTo(Scenes.Gone)
@@ -187,10 +179,8 @@
                     mapOf(
                         Back to UserActionResult(Scenes.Shade),
                         Swipe(SwipeDirection.Up) to UserActionResult(Scenes.Shade),
-                        Swipe(
-                            fromSource = Edge.Bottom,
-                            direction = SwipeDirection.Up,
-                        ) to UserActionResult(SceneFamilies.Home)
+                        Swipe(fromSource = Edge.Bottom, direction = SwipeDirection.Up) to
+                            UserActionResult(SceneFamilies.Home),
                     )
                 )
             assertThat(homeScene).isEqualTo(Scenes.Lockscreen)
@@ -225,10 +215,8 @@
                     mapOf(
                         Back to UserActionResult(Scenes.Shade),
                         Swipe(SwipeDirection.Up) to UserActionResult(Scenes.Shade),
-                        Swipe(
-                            fromSource = Edge.Bottom,
-                            direction = SwipeDirection.Up,
-                        ) to UserActionResult(SceneFamilies.Home)
+                        Swipe(fromSource = Edge.Bottom, direction = SwipeDirection.Up) to
+                            UserActionResult(SceneFamilies.Home),
                     )
                 )
             assertThat(homeScene).isEqualTo(Scenes.Gone)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractorTest.kt
index edaa3d3..bf97afe 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractorTest.kt
@@ -18,9 +18,12 @@
 
 package com.android.systemui.scene.domain.interactor
 
+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.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.ObservableTransitionState.Transition.ShowOrHideOverlay
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -29,8 +32,10 @@
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.shared.model.Overlays
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.shared.model.sceneDataSource
+import com.android.systemui.shade.shared.flag.DualShade
 import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.mock
@@ -63,10 +68,11 @@
     private val sceneDataSource =
         kosmos.sceneDataSource.apply { changeScene(toScene = Scenes.Lockscreen) }
 
-    private val underTest = kosmos.sceneContainerOcclusionInteractor
+    private val underTest by lazy { kosmos.sceneContainerOcclusionInteractor }
 
     @Test
-    fun invisibleDueToOcclusion() =
+    @DisableFlags(DualShade.FLAG_NAME)
+    fun invisibleDueToOcclusion_dualShadeDisabled() =
         testScope.runTest {
             val invisibleDueToOcclusion by collectLastValue(underTest.invisibleDueToOcclusion)
             val keyguardState by collectLastValue(keyguardTransitionInteractor.currentKeyguardState)
@@ -126,6 +132,68 @@
                 .isFalse()
         }
 
+    @Test
+    @EnableFlags(DualShade.FLAG_NAME)
+    fun invisibleDueToOcclusion_dualShadeEnabled() =
+        testScope.runTest {
+            val invisibleDueToOcclusion by collectLastValue(underTest.invisibleDueToOcclusion)
+            val keyguardState by collectLastValue(keyguardTransitionInteractor.currentKeyguardState)
+
+            // Assert that we have the desired preconditions:
+            assertThat(keyguardState).isEqualTo(KeyguardState.LOCKSCREEN)
+            assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Lockscreen)
+            assertThat(sceneInteractor.transitionState.value)
+                .isEqualTo(ObservableTransitionState.Idle(Scenes.Lockscreen))
+            assertWithMessage("Should start unoccluded").that(invisibleDueToOcclusion).isFalse()
+
+            // Actual testing starts here:
+            showOccludingActivity()
+            assertWithMessage("Should become occluded when occluding activity is shown")
+                .that(invisibleDueToOcclusion)
+                .isTrue()
+
+            transitionIntoAod {
+                assertWithMessage("Should become unoccluded when transitioning into AOD")
+                    .that(invisibleDueToOcclusion)
+                    .isFalse()
+            }
+            assertWithMessage("Should stay unoccluded when in AOD")
+                .that(invisibleDueToOcclusion)
+                .isFalse()
+
+            transitionOutOfAod {
+                assertWithMessage("Should remain unoccluded while transitioning away from AOD")
+                    .that(invisibleDueToOcclusion)
+                    .isFalse()
+            }
+            assertWithMessage("Should become occluded now that no longer in AOD")
+                .that(invisibleDueToOcclusion)
+                .isTrue()
+
+            expandDualShade {
+                assertWithMessage("Should become unoccluded once shade begins to expand")
+                    .that(invisibleDueToOcclusion)
+                    .isFalse()
+            }
+            assertWithMessage("Should be unoccluded when shade is fully expanded")
+                .that(invisibleDueToOcclusion)
+                .isFalse()
+
+            collapseDualShade {
+                assertWithMessage("Should remain unoccluded while shade is collapsing")
+                    .that(invisibleDueToOcclusion)
+                    .isFalse()
+            }
+            assertWithMessage("Should become occluded now that shade is fully collapsed")
+                .that(invisibleDueToOcclusion)
+                .isTrue()
+
+            hideOccludingActivity()
+            assertWithMessage("Should become unoccluded once the occluding activity is hidden")
+                .that(invisibleDueToOcclusion)
+                .isFalse()
+        }
+
     /** Simulates the appearance of a show-when-locked `Activity` in the foreground. */
     private fun TestScope.showOccludingActivity() {
         keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop(
@@ -138,15 +206,13 @@
     /** Simulates the disappearance of a show-when-locked `Activity` from the foreground. */
     private fun TestScope.hideOccludingActivity() {
         keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop(
-            showWhenLockedActivityOnTop = false,
+            showWhenLockedActivityOnTop = false
         )
         runCurrent()
     }
 
     /** Simulates a user-driven gradual expansion of the shade. */
-    private fun TestScope.expandShade(
-        assertMidTransition: () -> Unit = {},
-    ) {
+    private fun TestScope.expandShade(assertMidTransition: () -> Unit = {}) {
         val progress = MutableStateFlow(0f)
         mutableTransitionState.value =
             ObservableTransitionState.Transition(
@@ -170,10 +236,41 @@
         runCurrent()
     }
 
+    /** Simulates a user-driven gradual expansion of the dual shade (notifications). */
+    private fun TestScope.expandDualShade(assertMidTransition: () -> Unit = {}) {
+        val progress = MutableStateFlow(0f)
+        mutableTransitionState.value =
+            ShowOrHideOverlay(
+                overlay = Overlays.NotificationsShade,
+                fromContent = sceneDataSource.currentScene.value,
+                toContent = Overlays.NotificationsShade,
+                currentScene = sceneDataSource.currentScene.value,
+                currentOverlays = sceneDataSource.currentOverlays,
+                progress = progress,
+                isInitiatedByUserInput = true,
+                isUserInputOngoing = flowOf(true),
+                previewProgress = flowOf(0f),
+                isInPreviewStage = flowOf(false),
+            )
+        runCurrent()
+
+        progress.value = 0.5f
+        runCurrent()
+        assertMidTransition()
+
+        progress.value = 1f
+        runCurrent()
+
+        mutableTransitionState.value =
+            ObservableTransitionState.Idle(
+                sceneDataSource.currentScene.value,
+                setOf(Overlays.NotificationsShade),
+            )
+        runCurrent()
+    }
+
     /** Simulates a user-driven gradual collapse of the shade. */
-    private fun TestScope.collapseShade(
-        assertMidTransition: () -> Unit = {},
-    ) {
+    private fun TestScope.collapseShade(assertMidTransition: () -> Unit = {}) {
         val progress = MutableStateFlow(0f)
         mutableTransitionState.value =
             ObservableTransitionState.Transition(
@@ -197,10 +294,37 @@
         runCurrent()
     }
 
+    /** Simulates a user-driven gradual collapse of the dual shade (notifications). */
+    private fun TestScope.collapseDualShade(assertMidTransition: () -> Unit = {}) {
+        val progress = MutableStateFlow(0f)
+        mutableTransitionState.value =
+            ShowOrHideOverlay(
+                overlay = Overlays.NotificationsShade,
+                fromContent = Overlays.NotificationsShade,
+                toContent = Scenes.Lockscreen,
+                currentScene = Scenes.Lockscreen,
+                currentOverlays = flowOf(setOf(Overlays.NotificationsShade)),
+                progress = progress,
+                isInitiatedByUserInput = true,
+                isUserInputOngoing = flowOf(true),
+                previewProgress = flowOf(0f),
+                isInPreviewStage = flowOf(false),
+            )
+        runCurrent()
+
+        progress.value = 0.5f
+        runCurrent()
+        assertMidTransition()
+
+        progress.value = 1f
+        runCurrent()
+
+        mutableTransitionState.value = ObservableTransitionState.Idle(Scenes.Lockscreen)
+        runCurrent()
+    }
+
     /** Simulates a transition into AOD. */
-    private suspend fun TestScope.transitionIntoAod(
-        assertMidTransition: () -> Unit = {},
-    ) {
+    private suspend fun TestScope.transitionIntoAod(assertMidTransition: () -> Unit = {}) {
         val currentKeyguardState = keyguardTransitionInteractor.getCurrentState()
         keyguardTransitionRepository.sendTransitionStep(
             TransitionStep(
@@ -235,9 +359,7 @@
     }
 
     /** Simulates a transition away from AOD. */
-    private suspend fun TestScope.transitionOutOfAod(
-        assertMidTransition: () -> Unit = {},
-    ) {
+    private suspend fun TestScope.transitionOutOfAod(assertMidTransition: () -> Unit = {}) {
         keyguardTransitionRepository.sendTransitionStep(
             TransitionStep(
                 from = KeyguardState.AOD,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index 4a7d8b0..7fe3d8d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -21,6 +21,7 @@
 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.ObservableTransitionState.Transition.ShowOrHideOverlay
 import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
@@ -38,6 +39,7 @@
 import com.android.systemui.scene.overlayKeys
 import com.android.systemui.scene.sceneContainerConfig
 import com.android.systemui.scene.sceneKeys
+import com.android.systemui.scene.shared.model.Overlays
 import com.android.systemui.scene.shared.model.SceneFamilies
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.shared.model.fakeSceneDataSource
@@ -256,7 +258,7 @@
         }
 
     @Test
-    fun transitioningTo() =
+    fun transitioningTo_sceneChange() =
         testScope.runTest {
             val transitionState =
                 MutableStateFlow<ObservableTransitionState>(
@@ -293,6 +295,51 @@
         }
 
     @Test
+    fun transitioningTo_overlayChange() =
+        testScope.runTest {
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Idle(underTest.currentScene.value)
+                )
+            underTest.setTransitionState(transitionState)
+
+            val transitionTo by collectLastValue(underTest.transitioningTo)
+            assertThat(transitionTo).isNull()
+
+            underTest.showOverlay(Overlays.NotificationsShade, "reason")
+            assertThat(transitionTo).isNull()
+
+            val progress = MutableStateFlow(0f)
+            transitionState.value =
+                ShowOrHideOverlay(
+                    overlay = Overlays.NotificationsShade,
+                    fromContent = underTest.currentScene.value,
+                    toContent = Overlays.NotificationsShade,
+                    currentScene = underTest.currentScene.value,
+                    currentOverlays = underTest.currentOverlays,
+                    progress = progress,
+                    isInitiatedByUserInput = true,
+                    isUserInputOngoing = flowOf(true),
+                    previewProgress = flowOf(0f),
+                    isInPreviewStage = flowOf(false),
+                )
+            assertThat(transitionTo).isEqualTo(Overlays.NotificationsShade)
+
+            progress.value = 0.5f
+            assertThat(transitionTo).isEqualTo(Overlays.NotificationsShade)
+
+            progress.value = 1f
+            assertThat(transitionTo).isEqualTo(Overlays.NotificationsShade)
+
+            transitionState.value =
+                ObservableTransitionState.Idle(
+                    currentScene = underTest.currentScene.value,
+                    currentOverlays = setOf(Overlays.NotificationsShade),
+                )
+            assertThat(transitionTo).isNull()
+        }
+
+    @Test
     fun isTransitionUserInputOngoing_idle_false() =
         testScope.runTest {
             val transitionState =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModelTest.kt
index e6a24e3..5c47f55 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModelTest.kt
@@ -21,13 +21,17 @@
 import android.testing.TestableLooper
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.Swipe
 import com.android.compose.animation.scene.SwipeDirection
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
 import com.android.systemui.shade.data.repository.shadeRepository
 import com.android.systemui.shade.domain.interactor.shadeInteractor
@@ -92,4 +96,42 @@
 
             assertThat(userActions?.get(Swipe(SwipeDirection.Down))?.transitionKey).isNull()
         }
+
+    @Test
+    @DisableFlags(DualShade.FLAG_NAME)
+    fun swipeDownWithTwoFingers_singleShade_goesToQuickSettings() =
+        testScope.runTest {
+            val userActions by collectLastValue(underTest.actions)
+            shadeRepository.setShadeLayoutWide(false)
+            runCurrent()
+
+            assertThat(userActions?.get(swipeDownFromTopWithTwoFingers()))
+                .isEqualTo(UserActionResult(Scenes.QuickSettings))
+        }
+
+    @Test
+    @DisableFlags(DualShade.FLAG_NAME)
+    fun swipeDownWithTwoFingers_splitShade_goesToShade() =
+        testScope.runTest {
+            val userActions by collectLastValue(underTest.actions)
+            shadeRepository.setShadeLayoutWide(true)
+            runCurrent()
+
+            assertThat(userActions?.get(swipeDownFromTopWithTwoFingers()))
+                .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade))
+        }
+
+    @Test
+    @EnableFlags(DualShade.FLAG_NAME)
+    fun swipeDownWithTwoFingers_dualShadeEnabled_isNull() =
+        testScope.runTest {
+            val userActions by collectLastValue(underTest.actions)
+            runCurrent()
+
+            assertThat(userActions?.get(swipeDownFromTopWithTwoFingers())).isNull()
+        }
+
+    private fun swipeDownFromTopWithTwoFingers(): UserAction {
+        return Swipe(direction = SwipeDirection.Down, pointerCount = 2, fromSource = Edge.Top)
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt
index 851b7b9..ba559b5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt
@@ -21,6 +21,8 @@
 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.ObservableTransitionState.Transition.ShowOrHideOverlay
+import com.android.compose.animation.scene.OverlayKey
 import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
@@ -32,6 +34,7 @@
 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Overlays
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.shared.model.fakeSceneDataSource
 import com.android.systemui.testKosmos
@@ -125,6 +128,63 @@
 
     @Test
     @EnableSceneContainer
+    fun legacyPanelExpansion_dualShade_whenIdle_whenLocked() =
+        testScope.runTest {
+            underTest = kosmos.panelExpansionInteractorImpl
+            val panelExpansion by collectLastValue(underTest.legacyPanelExpansion)
+
+            changeScene(Scenes.Lockscreen) { assertThat(panelExpansion).isEqualTo(1f) }
+            assertThat(panelExpansion).isEqualTo(1f)
+
+            changeScene(Scenes.Bouncer) { assertThat(panelExpansion).isEqualTo(1f) }
+            assertThat(panelExpansion).isEqualTo(1f)
+
+            showOverlay(Overlays.NotificationsShade) { assertThat(panelExpansion).isEqualTo(1f) }
+            assertThat(panelExpansion).isEqualTo(1f)
+
+            showOverlay(Overlays.QuickSettingsShade) { assertThat(panelExpansion).isEqualTo(1f) }
+            assertThat(panelExpansion).isEqualTo(1f)
+
+            changeScene(Scenes.Communal) { assertThat(panelExpansion).isEqualTo(1f) }
+            assertThat(panelExpansion).isEqualTo(1f)
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun legacyPanelExpansion_dualShade_whenIdle_whenUnlocked() =
+        testScope.runTest {
+            underTest = kosmos.panelExpansionInteractorImpl
+            val unlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus)
+            kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+                SuccessFingerprintAuthenticationStatus(0, true)
+            )
+            runCurrent()
+
+            assertThat(unlockStatus)
+                .isEqualTo(DeviceUnlockStatus(true, DeviceUnlockSource.Fingerprint))
+
+            val panelExpansion by collectLastValue(underTest.legacyPanelExpansion)
+
+            changeScene(Scenes.Gone) { assertThat(panelExpansion).isEqualTo(0f) }
+            assertThat(panelExpansion).isEqualTo(0f)
+
+            showOverlay(Overlays.NotificationsShade) { progress ->
+                assertThat(panelExpansion).isEqualTo(progress)
+            }
+            assertThat(panelExpansion).isEqualTo(1f)
+
+            showOverlay(Overlays.QuickSettingsShade) {
+                // Notification shade is already expanded, so moving to QS shade should also be 1f.
+                assertThat(panelExpansion).isEqualTo(1f)
+            }
+            assertThat(panelExpansion).isEqualTo(1f)
+
+            changeScene(Scenes.Communal) { assertThat(panelExpansion).isEqualTo(1f) }
+            assertThat(panelExpansion).isEqualTo(1f)
+        }
+
+    @Test
+    @EnableSceneContainer
     fun shouldHideStatusBarIconsWhenExpanded_goneScene() =
         testScope.runTest {
             underTest = kosmos.panelExpansionInteractorImpl
@@ -193,4 +253,72 @@
 
         assertThat(currentScene).isEqualTo(toScene)
     }
+
+    private fun TestScope.showOverlay(
+        toOverlay: OverlayKey,
+        assertDuringProgress: ((progress: Float) -> Unit) = {},
+    ) {
+        val currentScene by collectLastValue(sceneInteractor.currentScene)
+        val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+        val progressFlow = MutableStateFlow(0f)
+        transitionState.value =
+            if (checkNotNull(currentOverlays).isEmpty()) {
+                ShowOrHideOverlay(
+                    overlay = toOverlay,
+                    fromContent = checkNotNull(currentScene),
+                    toContent = toOverlay,
+                    currentScene = checkNotNull(currentScene),
+                    currentOverlays = flowOf(emptySet()),
+                    progress = progressFlow,
+                    isInitiatedByUserInput = true,
+                    isUserInputOngoing = flowOf(true),
+                    previewProgress = flowOf(0f),
+                    isInPreviewStage = flowOf(false),
+                )
+            } else {
+                ObservableTransitionState.Transition.ReplaceOverlay(
+                    fromOverlay = checkNotNull(currentOverlays).first(),
+                    toOverlay = toOverlay,
+                    currentScene = checkNotNull(currentScene),
+                    currentOverlays = flowOf(emptySet()),
+                    progress = progressFlow,
+                    isInitiatedByUserInput = true,
+                    isUserInputOngoing = flowOf(true),
+                    previewProgress = flowOf(0f),
+                    isInPreviewStage = flowOf(false),
+                )
+            }
+        runCurrent()
+        assertDuringProgress(progressFlow.value)
+
+        progressFlow.value = 0.2f
+        runCurrent()
+        assertDuringProgress(progressFlow.value)
+
+        progressFlow.value = 0.6f
+        runCurrent()
+        assertDuringProgress(progressFlow.value)
+
+        progressFlow.value = 1f
+        runCurrent()
+        assertDuringProgress(progressFlow.value)
+
+        transitionState.value =
+            ObservableTransitionState.Idle(
+                currentScene = checkNotNull(currentScene),
+                currentOverlays = setOf(toOverlay),
+            )
+        if (checkNotNull(currentOverlays).isEmpty()) {
+            fakeSceneDataSource.showOverlay(toOverlay)
+        } else {
+            fakeSceneDataSource.replaceOverlay(
+                from = checkNotNull(currentOverlays).first(),
+                to = toOverlay,
+            )
+        }
+        runCurrent()
+        assertDuringProgress(progressFlow.value)
+
+        assertThat(currentOverlays).containsExactly(toOverlay)
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorImplTest.kt
index 2a2817b..ad2b23e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorImplTest.kt
@@ -88,6 +88,26 @@
         }
 
     @Test
+    @EnableFlags(DualShade.FLAG_NAME)
+    fun isDualShade_flagEnabled_true() =
+        testScope.runTest {
+            // Initiate collection.
+            val shadeMode by collectLastValue(underTest.shadeMode)
+
+            assertThat(underTest.isDualShade).isTrue()
+        }
+
+    @Test
+    @DisableFlags(DualShade.FLAG_NAME)
+    fun isDualShade_flagDisabled_false() =
+        testScope.runTest {
+            // Initiate collection.
+            val shadeMode by collectLastValue(underTest.shadeMode)
+
+            assertThat(underTest.isDualShade).isFalse()
+        }
+
+    @Test
     fun getTopEdgeSplitFraction_narrowScreen_splitInHalf() =
         testScope.runTest {
             // Ensure isShadeLayoutWide is collected.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
index 469a7bc..3053672 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
@@ -38,7 +38,7 @@
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
 import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
-import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
+import com.android.systemui.statusbar.notification.HeadsUpManagerPhone
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.testKosmos
 import com.android.systemui.util.concurrency.DelayableExecutor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/telephony/TelephonyCallbackTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/telephony/TelephonyCallbackTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/telephony/TelephonyCallbackTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/telephony/TelephonyCallbackTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/telephony/TelephonyListenerManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/telephony/TelephonyListenerManagerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/telephony/TelephonyListenerManagerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/telephony/TelephonyListenerManagerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewUiEventLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/temporarydisplay/TemporaryViewUiEventLoggerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewUiEventLoggerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/temporarydisplay/TemporaryViewUiEventLoggerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/touchpad/data/repository/TouchpadRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/data/repository/TouchpadRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/touchpad/data/repository/TouchpadRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/data/repository/TouchpadRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/FakeMotionEvent.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/FakeMotionEvent.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/FakeMotionEvent.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/FakeMotionEvent.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitorTest.kt
similarity index 97%
rename from packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitorTest.kt
index cafebb9..d059c14 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitorTest.kt
@@ -38,8 +38,7 @@
 
     companion object {
         const val THRESHOLD_VELOCITY_PX_PER_MS = 0.1f
-        // multiply by 1000 to get px per ms instead of px per s which is unit used by velocity
-        // tracker
+        // multiply by 1000 to get px/ms instead of px/s which is unit used by velocity tracker
         const val SLOW = THRESHOLD_VELOCITY_PX_PER_MS * 1000 - 1
         const val FAST = THRESHOLD_VELOCITY_PX_PER_MS * 1000 + 1
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureBuilder.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureBuilder.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureBuilder.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureBuilder.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureBuilderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureBuilderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureBuilderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureBuilderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/tracing/TraceUtilsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/tracing/TraceUtilsTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/tracing/TraceUtilsTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/tracing/TraceUtilsTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimationTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimationTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimationTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldStateLoggingProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/FoldStateLoggingProviderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/unfold/FoldStateLoggingProviderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/FoldStateLoggingProviderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldStateRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/FoldStateRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/unfold/FoldStateRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/FoldStateRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTestUtilsKosmos.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/UnfoldTestUtilsKosmos.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTestUtilsKosmos.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/UnfoldTestUtilsKosmos.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfigTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfigTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfigTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/config/TestUnfoldTransitionConfig.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/config/TestUnfoldTransitionConfig.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/unfold/config/TestUnfoldTransitionConfig.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/config/TestUnfoldTransitionConfig.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/RemoteUnfoldTransitionReceiverTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/progress/RemoteUnfoldTransitionReceiverTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/unfold/progress/RemoteUnfoldTransitionReceiverTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/progress/RemoteUnfoldTransitionReceiverTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceStateRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/updates/DeviceStateRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceStateRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/updates/DeviceStateRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/RotationChangeProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/updates/RotationChangeProviderTest.kt
similarity index 85%
rename from packages/SystemUI/tests/src/com/android/systemui/unfold/updates/RotationChangeProviderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/updates/RotationChangeProviderTest.kt
index f5bcc21..feee0a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/RotationChangeProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/updates/RotationChangeProviderTest.kt
@@ -38,6 +38,7 @@
 import org.mockito.Mock
 import org.mockito.Mockito.spy
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.inOrder
 import org.mockito.Mockito.verifyNoMoreInteractions
 import org.mockito.MockitoAnnotations
 
@@ -79,6 +80,26 @@
     }
 
     @Test
+    fun onRotationChanged_rotationSentMultipleWithTheSameValue_listenerReceivesUpdateOnce() {
+        sendRotationUpdate(42)
+        sendRotationUpdate(42)
+        sendRotationUpdate(42)
+
+        verify(listener).onRotationChanged(42)
+    }
+
+    @Test
+    fun onRotationChanged_rotationSentMultipleTimesWithDifferentValues_listenerReceivesUpdates() {
+        sendRotationUpdate(0)
+        sendRotationUpdate(1)
+
+        with(inOrder(listener)) {
+            verify(listener).onRotationChanged(0)
+            verify(listener).onRotationChanged(1)
+        }
+    }
+
+    @Test
     fun onRotationChanged_subscribersRemoved_noRotationChangeReceived() {
         sendRotationUpdate(42)
         verify(listener).onRotationChanged(42)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/FoldableTestUtils.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/util/FoldableTestUtils.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/unfold/util/FoldableTestUtils.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/util/FoldableTestUtils.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/TestFoldProvider.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/util/TestFoldProvider.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/unfold/util/TestFoldProvider.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/util/TestFoldProvider.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/TestFoldStateProvider.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/util/TestFoldStateProvider.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/unfold/util/TestFoldStateProvider.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/util/TestFoldStateProvider.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProviderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProviderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProviderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/DeviceConfigProxyFakeTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/DeviceConfigProxyFakeTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/DeviceConfigProxyFakeTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/DeviceConfigProxyFakeTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/FakeSharedPreferencesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/FakeSharedPreferencesTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/FakeSharedPreferencesTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/FakeSharedPreferencesTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/RingerModeLiveDataTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/RingerModeLiveDataTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/RingerModeLiveDataTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/RingerModeLiveDataTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/WallpaperControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/WallpaperControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/animation/AnimationUtilTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/animation/AnimationUtilTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/animation/AnimationUtilTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/animation/AnimationUtilTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MessageRouterImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/concurrency/MessageRouterImplTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MessageRouterImplTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/concurrency/MessageRouterImplTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MockExecutorHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/concurrency/MockExecutorHandlerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MockExecutorHandlerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/concurrency/MockExecutorHandlerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/RepeatableExecutorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/concurrency/RepeatableExecutorTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/concurrency/RepeatableExecutorTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/concurrency/RepeatableExecutorTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionalCoreStartableTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/condition/ConditionalCoreStartableTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionalCoreStartableTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/condition/ConditionalCoreStartableTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/drawable/DrawableSizeTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/drawable/DrawableSizeTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/drawable/DrawableSizeTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/drawable/DrawableSizeTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/drawable/LoopedAnimatable2DrawableWrapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/drawable/LoopedAnimatable2DrawableWrapperTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/drawable/LoopedAnimatable2DrawableWrapperTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/drawable/LoopedAnimatable2DrawableWrapperTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/icons/AppCategoryIconProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/icons/AppCategoryIconProviderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/icons/AppCategoryIconProviderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/icons/AppCategoryIconProviderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowProvider.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/FlowProvider.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowProvider.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/FlowProvider.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/SuspendUtilTests.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/SuspendUtilTests.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/kotlin/SuspendUtilTests.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/SuspendUtilTests.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/leak/LeakReporterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/leak/LeakReporterTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/leak/LeakReporterTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/leak/LeakReporterTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/ref/GcWeakReference.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/ref/GcWeakReference.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/ref/GcWeakReference.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/ref/GcWeakReference.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/PostureDependentProximitySensorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/sensors/PostureDependentProximitySensorTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/sensors/PostureDependentProximitySensorTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/sensors/PostureDependentProximitySensorTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplDualTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/sensors/ProximitySensorImplDualTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplDualTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/sensors/ProximitySensorImplDualTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplSingleTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/sensors/ProximitySensorImplSingleTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplSingleTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/sensors/ProximitySensorImplSingleTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/service/PackageObserverTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/service/PackageObserverTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/service/PackageObserverTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/service/PackageObserverTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/FakeSettingsTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/FakeSettingsTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/SettingsProxyTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/SettingsProxyTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/ui/AnimatedValueTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/ui/AnimatedValueTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/view/ViewUtilTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/view/ViewUtilTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/view/ViewUtilTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/view/ViewUtilTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/SettableWakeLockTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/wakelock/SettableWakeLockTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/wakelock/SettableWakeLockTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/wakelock/SettableWakeLockTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/wakelock/WakeLockTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/wakelock/WakeLockTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeControllerAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/VolumeControllerAdapterTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/volume/VolumeControllerAdapterTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/volume/VolumeControllerAdapterTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/data/repository/VolumeDialogRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/data/repository/VolumeDialogRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/volume/data/repository/VolumeDialogRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/volume/data/repository/VolumeDialogRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/domain/interactor/VolumeDialogInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/VolumeDialogInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/volume/domain/interactor/VolumeDialogInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/VolumeDialogInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualLocationsServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallet/controller/WalletContextualLocationsServiceTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualLocationsServiceTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/wallet/controller/WalletContextualLocationsServiceTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/util/WalletCardUtilsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallet/util/WalletCardUtilsTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/wallet/util/WalletCardUtilsTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/wallet/util/WalletCardUtilsTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractorTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractorTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractorTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTestActivity.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/BubblesTestActivity.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTestActivity.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/BubblesTestActivity.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/SyncExecutor.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/SyncExecutor.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/wmshell/SyncExecutor.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/SyncExecutor.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/TestableBubbleController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/TestableBubbleController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubblePositioner.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/TestableBubblePositioner.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubblePositioner.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/TestableBubblePositioner.java
diff --git a/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml b/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml
index 6c8db91..84f7a51 100644
--- a/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml
@@ -28,4 +28,8 @@
     <!-- Overload default clock widget parameters -->
     <dimen name="widget_big_font_size">100dp</dimen>
     <dimen name="widget_label_font_size">18sp</dimen>
+
+    <!-- New keyboard shortcut helper -->
+    <dimen name="shortcut_helper_width">704dp</dimen>
+    <dimen name="shortcut_helper_height">1208dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/layout/activity_keyboard_shortcut_helper.xml b/packages/SystemUI/res/layout/activity_keyboard_shortcut_helper.xml
index 06d1bf4..a15532f 100644
--- a/packages/SystemUI/res/layout/activity_keyboard_shortcut_helper.xml
+++ b/packages/SystemUI/res/layout/activity_keyboard_shortcut_helper.xml
@@ -2,14 +2,15 @@
 <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:id="@+id/shortcut_helper_sheet_container"
+    android:layout_gravity="center_horizontal|bottom"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
 
     <FrameLayout
         android:id="@+id/shortcut_helper_sheet"
         style="@style/ShortcutHelperBottomSheet"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
+        android:layout_width="@dimen/shortcut_helper_width"
+        android:layout_height="@dimen/shortcut_helper_height"
         android:orientation="vertical"
         app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
 
diff --git a/packages/SystemUI/res/layout/ambient_status_bar_view.xml b/packages/SystemUI/res/layout/ambient_status_bar_view.xml
index 7d765ce..825824a 100644
--- a/packages/SystemUI/res/layout/ambient_status_bar_view.xml
+++ b/packages/SystemUI/res/layout/ambient_status_bar_view.xml
@@ -53,6 +53,15 @@
         app:layout_constraintEnd_toEndOf="parent">
 
         <com.android.systemui.statusbar.AlphaOptimizedImageView
+            android:id="@+id/dream_overlay_location_active"
+            android:layout_width="@dimen/dream_overlay_status_bar_icon_size"
+            android:layout_height="match_parent"
+            android:layout_marginStart="@dimen/dream_overlay_status_icon_margin"
+            android:src="@drawable/ic_location"
+            android:visibility="gone"
+            android:contentDescription="@string/location_active_dream_overlay_content_description" />
+
+        <com.android.systemui.statusbar.AlphaOptimizedImageView
             android:id="@+id/dream_overlay_alarm_set"
             android:layout_width="@dimen/dream_overlay_status_bar_icon_size"
             android:layout_height="match_parent"
diff --git a/packages/SystemUI/res/raw/trackpad_recent_apps_edu.json b/packages/SystemUI/res/raw/trackpad_recent_apps_edu.json
new file mode 100644
index 0000000..c2e945d
--- /dev/null
+++ b/packages/SystemUI/res/raw/trackpad_recent_apps_edu.json
@@ -0,0 +1 @@
+{"v":"5.12.1","fr":60,"ip":0,"op":511,"w":554,"h":564,"nm":"Trackpad-JSON_Recents-EDU","ddd":0,"assets":[{"id":"comp_0","nm":"Recents_EDU Loop","fr":60,"layers":[{"ddd":0,"ind":2,"ty":4,"nm":"CNTL || playback","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"s":true,"x":{"a":0,"k":0},"y":{"a":0,"k":0}},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":5,"nm":"Picker","np":3,"mn":"Pseudo/@@WcSiov6sT3a4/s0XPKYEOQ","ix":1,"en":1,"ef":[{"ty":7,"nm":"Menu","mn":"Pseudo/@@WcSiov6sT3a4/s0XPKYEOQ-0001","ix":1,"v":{"a":0,"k":2}}]},{"ty":5,"nm":"OUTPUT","np":3,"mn":"ADBE Slider Control","ix":2,"en":1,"ef":[{"ty":0,"nm":"Slider","mn":"ADBE Slider Control-0001","ix":1,"v":{"k":[{"s":[0],"t":142,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.001],"t":143,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.002],"t":144,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.003],"t":145,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.004],"t":146,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.006],"t":147,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.008],"t":148,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.01],"t":149,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.012],"t":150,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.016],"t":151,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.02],"t":152,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.025],"t":153,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.031],"t":154,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.038],"t":155,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.047],"t":156,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.059],"t":157,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.073],"t":158,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.091],"t":159,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.116],"t":160,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.15],"t":161,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.196],"t":162,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.249],"t":163,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.306],"t":164,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.366],"t":165,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.425],"t":166,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.481],"t":167,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.53],"t":168,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.575],"t":169,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.614],"t":170,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.648],"t":171,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.678],"t":172,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.706],"t":173,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.73],"t":174,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.752],"t":175,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.772],"t":176,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.79],"t":177,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.807],"t":178,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.822],"t":179,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.836],"t":180,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.849],"t":181,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.861],"t":182,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.873],"t":183,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.883],"t":184,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.892],"t":185,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.901],"t":186,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.91],"t":187,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.917],"t":188,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.925],"t":189,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.931],"t":190,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.937],"t":191,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.943],"t":192,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.949],"t":193,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.954],"t":194,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.958],"t":195,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.963],"t":196,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.967],"t":197,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.97],"t":198,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.974],"t":199,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.977],"t":200,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.98],"t":201,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.983],"t":202,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.985],"t":203,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.987],"t":204,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.989],"t":205,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.991],"t":206,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.993],"t":207,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.994],"t":208,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.996],"t":209,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.997],"t":210,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.998],"t":211,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.998],"t":212,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.999],"t":213,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1],"t":215,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1],"t":250,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.009],"t":251,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.038],"t":252,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.093],"t":253,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.193],"t":254,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.4],"t":255,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.636],"t":256,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.739],"t":257,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.8],"t":258,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.84],"t":259,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.871],"t":260,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.894],"t":261,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.912],"t":262,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.928],"t":263,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.94],"t":264,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.951],"t":265,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.959],"t":266,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.967],"t":267,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.973],"t":268,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.979],"t":269,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.983],"t":270,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.987],"t":271,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.99],"t":272,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.993],"t":273,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.995],"t":274,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.997],"t":275,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.998],"t":276,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.999],"t":278,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2],"t":380,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.009],"t":381,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.038],"t":382,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.093],"t":383,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.193],"t":384,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.4],"t":385,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.636],"t":386,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.739],"t":387,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.8],"t":388,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.84],"t":389,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.871],"t":390,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.894],"t":391,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.912],"t":392,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.928],"t":393,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.94],"t":394,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.951],"t":395,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.959],"t":396,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.967],"t":397,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.973],"t":398,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.979],"t":399,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.983],"t":400,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.987],"t":401,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.99],"t":402,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.993],"t":403,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.995],"t":404,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.997],"t":405,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.999],"t":408,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]}}]},{"ty":5,"nm":"Keys","np":3,"mn":"ADBE Slider Control","ix":3,"en":1,"ef":[{"ty":0,"nm":"Slider","mn":"ADBE Slider Control-0001","ix":1,"v":{"a":1,"k":[{"i":{"x":[0.831],"y":[0.109]},"o":{"x":[0.458],"y":[0.053]},"t":142,"s":[0]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.15],"y":[0.43]},"t":161,"s":[0.15]},{"t":217,"s":[1],"h":1},{"i":{"x":[0.8],"y":[0.15]},"o":{"x":[0.3],"y":[0]},"t":250,"s":[1]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.05],"y":[0.7]},"t":255,"s":[1.4]},{"t":280,"s":[2],"h":1},{"i":{"x":[0.8],"y":[0.15]},"o":{"x":[0.3],"y":[0]},"t":380,"s":[2]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.05],"y":[0.7]},"t":385,"s":[2.4]},{"t":410,"s":[3]}]}}]},{"ty":5,"nm":"State (holds)","np":3,"mn":"ADBE Slider Control","ix":4,"en":1,"ef":[{"ty":0,"nm":"Slider","mn":"ADBE Slider Control-0001","ix":1,"v":{"a":0,"k":0}}]}],"shapes":[],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":3,"nm":"Null :: Taskbar drop","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"k":[{"s":[252,278,0],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,278.45,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,279.615,0],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,281.252,0],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,283.166,0],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,287.233,0],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,289.181,0],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,290.982,0],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,292.599,0],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,294.012,0],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,295.216,0],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,296.216,0],"t":198,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,297.023,0],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,297.655,0],"t":200,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,298.131,0],"t":201,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,298.474,0],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,298.705,0],"t":203,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,298.465,0],"t":212,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,298.226,0],"t":215,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,298,0],"t":377,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,298.382,0],"t":378,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,299.372,0],"t":379,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,300.764,0],"t":380,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,302.391,0],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,305.848,0],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,307.504,0],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,309.035,0],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,310.409,0],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,311.611,0],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,312.634,0],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,313.483,0],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,314.169,0],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,314.706,0],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,315.112,0],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,315.403,0],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,315.717,0],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,315.474,0],"t":402,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,315.192,0],"t":406,"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,"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":0,"nm":"Taskbar Lofi","parent":3,"refId":"comp_1","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":134,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":143,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":432,"s":[100]},{"t":444,"s":[0]}]},"r":{"a":0,"k":0},"p":{"s":true,"x":{"a":0,"k":0},"y":{"k":[{"s":[26.984],"t":127,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.971],"t":128,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.95],"t":129,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.921],"t":130,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.882],"t":131,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.83],"t":132,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.765],"t":133,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.685],"t":134,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.589],"t":135,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.478],"t":136,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.349],"t":137,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.205],"t":138,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.072],"t":139,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[25.926],"t":140,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[25.764],"t":141,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[25.589],"t":142,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[25.397],"t":143,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[25.187],"t":144,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[24.959],"t":145,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[24.711],"t":146,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[24.44],"t":147,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[24.146],"t":148,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[23.826],"t":149,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[23.479],"t":150,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[23.1],"t":151,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[22.686],"t":152,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[22.236],"t":153,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[21.745],"t":154,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[21.207],"t":155,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[20.616],"t":156,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[19.967],"t":157,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[19.248],"t":158,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[18.457],"t":159,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[17.578],"t":160,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[16.602],"t":161,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.514],"t":162,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.303],"t":163,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[12.954],"t":164,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[11.477],"t":165,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[9.885],"t":166,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[8.215],"t":167,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[6.526],"t":168,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[4.878],"t":169,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[3.338],"t":170,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.928],"t":171,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.659],"t":172,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-0.475],"t":173,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-1.485],"t":174,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-2.388],"t":175,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-3.192],"t":176,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-3.911],"t":177,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-4.556],"t":178,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-5.136],"t":179,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-5.662],"t":180,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-6.135],"t":181,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-6.563],"t":182,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-6.951],"t":183,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-7.303],"t":184,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-7.622],"t":185,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-7.913],"t":186,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-8.175],"t":187,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-8.413],"t":188,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-8.628],"t":189,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-8.823],"t":190,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-8.998],"t":191,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.155],"t":192,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.296],"t":193,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.42],"t":194,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.531],"t":195,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.627],"t":196,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.711],"t":197,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.783],"t":198,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.843],"t":199,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.893],"t":200,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.933],"t":201,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.963],"t":202,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.984],"t":203,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.996],"t":204,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]}},"a":{"a":0,"k":[91,15,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]}]}}]}],"w":182,"h":30,"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":3,"nm":"Focus Task :: Lift & Drop","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"s":true,"x":{"a":0,"k":252},"y":{"k":[{"s":[157.385],"t":145,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[157.28],"t":147,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[157.128],"t":149,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[157.026],"t":150,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[156.901],"t":151,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[156.75],"t":152,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[156.564],"t":153,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[156.335],"t":154,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[156.054],"t":155,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[155.706],"t":156,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[155.275],"t":157,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[154.73],"t":158,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[154.03],"t":159,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[153.103],"t":160,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[151.8],"t":161,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[150.035],"t":162,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[148.047],"t":163,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[145.867],"t":164,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[143.589],"t":165,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[141.341],"t":166,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[139.241],"t":167,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[137.346],"t":168,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[135.666],"t":169,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[134.185],"t":170,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[132.878],"t":171,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[131.718],"t":172,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[130.684],"t":173,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[129.755],"t":174,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[128.916],"t":175,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[128.155],"t":176,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[127.462],"t":177,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[126.829],"t":178,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[126.249],"t":179,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[125.715],"t":180,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[125.221],"t":181,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[124.765],"t":182,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[124.343],"t":183,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[123.951],"t":184,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[123.587],"t":185,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[123.249],"t":186,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[122.934],"t":187,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[122.641],"t":188,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[122.369],"t":189,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[122.114],"t":190,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[121.877],"t":191,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[121.657],"t":192,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[121.452],"t":193,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[121.26],"t":194,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[121.082],"t":195,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[120.918],"t":196,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[120.764],"t":197,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[120.623],"t":198,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[120.492],"t":199,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[120.371],"t":200,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[120.261],"t":201,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[120.158],"t":202,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[120.065],"t":203,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[119.98],"t":204,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[119.903],"t":205,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[119.835],"t":206,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[119.718],"t":208,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[119.629],"t":210,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[119.51],"t":215,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[119.5],"t":250,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[119.746],"t":251,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[120.54],"t":252,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[122.071],"t":253,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[124.808],"t":254,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[130.5],"t":255,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[136.982],"t":256,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[139.835],"t":257,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[141.489],"t":258,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[142.613],"t":259,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[143.442],"t":260,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[144.082],"t":261,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[144.593],"t":262,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[145.01],"t":263,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[145.354],"t":264,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[145.642],"t":265,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[145.884],"t":266,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[146.089],"t":267,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[146.262],"t":268,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[146.409],"t":269,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[146.534],"t":270,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[146.638],"t":271,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[146.725],"t":272,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[146.857],"t":274,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[147],"t":380,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[147.094],"t":381,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[147.397],"t":382,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[147.982],"t":383,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[149.027],"t":384,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[151.2],"t":385,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[153.675],"t":386,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[154.764],"t":387,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[155.396],"t":388,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[155.825],"t":389,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[156.141],"t":390,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[156.386],"t":391,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[156.581],"t":392,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[156.74],"t":393,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[156.871],"t":394,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[156.981],"t":395,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[157.074],"t":396,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[157.218],"t":398,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[157.362],"t":401,"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,"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"matte","parent":5,"td":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":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"k":[{"s":[503.613,314.758],"t":144,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[503.134,314.459],"t":146,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[502.832,314.27],"t":147,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[502.464,314.04],"t":148,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[502.025,313.765],"t":149,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[501.487,313.429],"t":150,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[500.824,313.015],"t":151,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[500.023,312.514],"t":152,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[499.032,311.895],"t":153,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[497.818,311.136],"t":154,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[496.328,310.205],"t":155,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[494.484,309.053],"t":156,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[492.194,307.621],"t":157,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[489.307,305.817],"t":158,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[485.592,303.495],"t":159,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[480.67,300.419],"t":160,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[473.76,296.1],"t":161,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[464.395,290.247],"t":162,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[453.849,283.656],"t":163,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[442.286,276.429],"t":164,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[430.198,268.874],"t":165,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[418.274,261.421],"t":166,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[407.131,254.457],"t":167,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[397.077,248.173],"t":168,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[388.165,242.603],"t":169,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[380.31,237.694],"t":170,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[373.373,233.358],"t":171,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[367.218,229.511],"t":172,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[361.732,226.082],"t":173,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[356.803,223.002],"t":174,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[352.354,220.221],"t":175,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[348.318,217.699],"t":176,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[344.643,215.402],"t":177,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[341.283,213.302],"t":178,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[338.205,211.378],"t":179,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[335.37,209.606],"t":180,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[332.752,207.97],"t":181,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[330.33,206.456],"t":182,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[328.092,205.058],"t":183,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[326.012,203.757],"t":184,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[324.082,202.552],"t":185,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[322.291,201.432],"t":186,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[320.617,200.386],"t":187,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[319.062,199.414],"t":188,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[317.618,198.512],"t":189,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[316.267,197.667],"t":190,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[315.013,196.883],"t":191,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[313.845,196.153],"t":192,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[312.756,195.472],"t":193,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[311.738,194.837],"t":194,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[310.793,194.246],"t":195,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[309.921,193.7],"t":196,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[309.107,193.192],"t":197,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[308.359,192.724],"t":198,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[307.663,192.289],"t":199,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[307.02,191.888],"t":200,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[306.436,191.522],"t":201,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[305.891,191.182],"t":202,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[305.399,190.874],"t":203,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[304.946,190.591],"t":204,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[304.539,190.337],"t":205,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[304.178,190.112],"t":206,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[303.85,189.906],"t":207,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[303.555,189.722],"t":208,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[303.306,189.566],"t":209,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[303.082,189.427],"t":210,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[302.892,189.308],"t":211,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[302.617,189.135],"t":213,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[302.4,189],"t":250,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[302.247,188.904],"t":251,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[301.752,188.595],"t":252,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[300.798,187.999],"t":253,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[299.093,186.933],"t":254,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[295.546,184.716],"t":255,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[291.506,182.192],"t":256,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[289.729,181.08],"t":257,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[288.698,180.436],"t":258,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[287.998,179.999],"t":259,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[287.481,179.676],"t":260,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[287.082,179.427],"t":261,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[286.764,179.227],"t":262,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[286.504,179.065],"t":263,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[286.29,178.931],"t":264,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[286.11,178.819],"t":265,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[285.832,178.645],"t":267,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[285.555,178.472],"t":270,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[285.272,178.295],"t":278,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[285.264,178.29],"t":380,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[287.222,179.514],"t":381,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[293.538,183.461],"t":382,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[305.714,191.071],"t":383,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[327.48,204.675],"t":384,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[372.758,232.974],"t":385,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[424.317,265.198],"t":386,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[447.009,279.381],"t":387,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[460.167,287.605],"t":388,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[469.103,293.19],"t":389,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[475.697,297.31],"t":390,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[480.788,300.492],"t":391,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[484.853,303.033],"t":392,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[488.172,305.107],"t":393,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[490.906,306.816],"t":394,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[493.198,308.249],"t":395,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[495.121,309.451],"t":396,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[496.752,310.47],"t":397,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[498.133,311.333],"t":398,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[499.301,312.063],"t":399,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[500.29,312.681],"t":400,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[501.123,313.202],"t":401,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[501.814,313.634],"t":402,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[502.391,313.994],"t":403,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[502.861,314.288],"t":404,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[503.238,314.524],"t":405,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[503.53,314.706],"t":406,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}}]},"p":{"a":0,"k":[0,0]},"r":{"k":[{"s":[27.974],"t":144,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.959],"t":145,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.942],"t":146,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.922],"t":147,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.898],"t":148,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.869],"t":149,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.833],"t":150,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.789],"t":151,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.736],"t":152,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.67],"t":153,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.589],"t":154,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.49],"t":155,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.368],"t":156,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.216],"t":157,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.024],"t":158,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.777],"t":159,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.45],"t":160,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[25.991],"t":161,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[25.37],"t":162,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[24.669],"t":163,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[23.901],"t":164,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[23.098],"t":165,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[22.306],"t":166,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[21.566],"t":167,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[20.898],"t":168,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[20.306],"t":169,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[19.785],"t":170,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[19.324],"t":171,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[18.915],"t":172,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[18.551],"t":173,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[18.223],"t":174,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[17.928],"t":175,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[17.66],"t":176,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[17.416],"t":177,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[17.193],"t":178,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[16.988],"t":179,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[16.8],"t":180,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[16.626],"t":181,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[16.465],"t":182,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[16.316],"t":183,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[16.178],"t":184,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[16.05],"t":185,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.931],"t":186,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.82],"t":187,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.717],"t":188,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.621],"t":189,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.531],"t":190,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.448],"t":191,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.37],"t":192,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.298],"t":193,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.23],"t":194,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.167],"t":195,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.11],"t":196,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.055],"t":197,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.006],"t":198,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.96],"t":199,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.917],"t":200,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.878],"t":201,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.842],"t":202,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.809],"t":203,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.779],"t":204,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.752],"t":205,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.728],"t":206,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.706],"t":207,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.687],"t":208,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.67],"t":209,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.655],"t":210,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.643],"t":211,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.633],"t":212,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.624],"t":213,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.61],"t":250,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.603],"t":251,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.579],"t":252,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.532],"t":253,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.45],"t":254,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.278],"t":255,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.082],"t":256,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.996],"t":257,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.946],"t":258,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.912],"t":259,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.887],"t":260,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.868],"t":261,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.853],"t":262,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.84],"t":263,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.83],"t":264,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.821],"t":265,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.808],"t":267,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.794],"t":270,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.78],"t":278,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.78],"t":380,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.907],"t":381,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.318],"t":382,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.109],"t":383,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[16.524],"t":384,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[19.468],"t":385,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[22.82],"t":386,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[24.295],"t":387,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[25.15],"t":388,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[25.731],"t":389,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.16],"t":390,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.491],"t":391,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.755],"t":392,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.971],"t":393,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.149],"t":394,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.298],"t":395,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.423],"t":396,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.529],"t":397,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.619],"t":398,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.694],"t":399,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.759],"t":400,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.813],"t":401,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.858],"t":402,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.895],"t":403,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.926],"t":404,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.95],"t":405,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.969],"t":406,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.993],"t":408,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"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":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":0,"nm":"Recents_LofiApp","parent":6,"tt":1,"tp":6,"refId":"comp_2","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":{"k":[{"s":[99.923,99.923,100],"t":144,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.828,99.828,100],"t":146,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.768,99.768,100],"t":147,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.695,99.695,100],"t":148,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.608,99.608,100],"t":149,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.501,99.501,100],"t":150,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.37,99.37,100],"t":151,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.211,99.211,100],"t":152,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.014,99.014,100],"t":153,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.773,98.773,100],"t":154,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.478,98.478,100],"t":155,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.112,98.112,100],"t":156,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.658,97.658,100],"t":157,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.085,97.085,100],"t":158,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.348,96.348,100],"t":159,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.371,95.371,100],"t":160,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[94,94,100],"t":161,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.142,92.142,100],"t":162,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.049,90.049,100],"t":163,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[87.755,87.755,100],"t":164,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.357,85.357,100],"t":165,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.991,82.991,100],"t":166,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.78,80.78,100],"t":167,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[78.785,78.785,100],"t":168,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[77.017,77.017,100],"t":169,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[75.458,75.458,100],"t":170,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[74.082,74.082,100],"t":171,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[72.861,72.861,100],"t":172,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[71.772,71.772,100],"t":173,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[70.794,70.794,100],"t":174,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[69.911,69.911,100],"t":175,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[69.111,69.111,100],"t":176,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[68.382,68.382,100],"t":177,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[67.715,67.715,100],"t":178,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[67.104,67.104,100],"t":179,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[66.542,66.542,100],"t":180,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[66.022,66.022,100],"t":181,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[65.542,65.542,100],"t":182,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[65.098,65.098,100],"t":183,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[64.685,64.685,100],"t":184,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[64.302,64.302,100],"t":185,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[63.947,63.947,100],"t":186,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[63.615,63.615,100],"t":187,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[63.306,63.306,100],"t":188,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[63.02,63.02,100],"t":189,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[62.751,62.751,100],"t":190,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[62.503,62.503,100],"t":191,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[62.271,62.271,100],"t":192,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[62.055,62.055,100],"t":193,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[61.853,61.853,100],"t":194,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[61.665,61.665,100],"t":195,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[61.492,61.492,100],"t":196,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[61.331,61.331,100],"t":197,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[61.182,61.182,100],"t":198,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[61.044,61.044,100],"t":199,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.917,60.917,100],"t":200,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.801,60.801,100],"t":201,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.693,60.693,100],"t":202,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.595,60.595,100],"t":203,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.505,60.505,100],"t":204,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.424,60.424,100],"t":205,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.353,60.353,100],"t":206,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.288,60.288,100],"t":207,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.229,60.229,100],"t":208,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.18,60.18,100],"t":209,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.135,60.135,100],"t":210,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.098,60.098,100],"t":211,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.043,60.043,100],"t":213,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60,60,100],"t":250,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[59.97,59.97,100],"t":251,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[59.871,59.871,100],"t":252,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[59.682,59.682,100],"t":253,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[59.344,59.344,100],"t":254,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[58.64,58.64,100],"t":255,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.839,57.839,100],"t":256,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.486,57.486,100],"t":257,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.281,57.281,100],"t":258,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.142,57.142,100],"t":259,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.04,57.04,100],"t":260,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.961,56.961,100],"t":261,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.898,56.898,100],"t":262,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.846,56.846,100],"t":263,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.804,56.804,100],"t":264,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.768,56.768,100],"t":265,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.713,56.713,100],"t":267,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.658,56.658,100],"t":270,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.602,56.602,100],"t":278,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.6,56.6,100],"t":380,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.989,56.989,100],"t":381,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[58.242,58.242,100],"t":382,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.657,60.657,100],"t":383,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[64.976,64.976,100],"t":384,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[73.96,73.96,100],"t":385,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[84.19,84.19,100],"t":386,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88.692,88.692,100],"t":387,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[91.303,91.303,100],"t":388,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[93.076,93.076,100],"t":389,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[94.384,94.384,100],"t":390,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.394,95.394,100],"t":391,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.201,96.201,100],"t":392,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.859,96.859,100],"t":393,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.402,97.402,100],"t":394,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.857,97.857,100],"t":395,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.238,98.238,100],"t":396,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.562,98.562,100],"t":397,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.836,98.836,100],"t":398,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.068,99.068,100],"t":399,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.264,99.264,100],"t":400,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.429,99.429,100],"t":401,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.566,99.566,100],"t":402,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.681,99.681,100],"t":403,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.774,99.774,100],"t":404,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.849,99.849,100],"t":405,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.907,99.907,100],"t":406,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}]}},"ao":0,"w":504,"h":315,"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":7,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":268,"s":[0]},{"t":277,"s":[100]}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[252,-30.035,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[176.678,176.678,100]}},"ao":0,"shapes":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18]},"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}],"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"second Tasks Zoom back","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":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":385,"s":[98,98,100]},{"t":410,"s":[95,95,100]}]}},"ao":0,"shapes":[],"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Null :: Reposition Side Task","parent":9,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":1,"k":[{"i":{"x":0.8,"y":0.15},"o":{"x":0.3,"y":0},"t":250,"s":[-318.4,-38,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":255,"s":[-277.34,-48.1,0],"to":[0,0,0],"ti":[0,0,0]},{"t":280,"s":[-215.75,-63.25,0]}]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[],"ip":197,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":12,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":268,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":277,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":383,"s":[100]},{"t":392,"s":[0]}]},"r":{"a":0,"k":0},"p":{"k":[{"s":[0,-111.72,0],"t":250,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-111.197,0],"t":251,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-109.514,0],"t":252,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-106.268,0],"t":253,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-100.462,0],"t":254,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-88.39,0],"t":255,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-74.643,0],"t":256,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-68.591,0],"t":257,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-65.083,0],"t":258,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-62.7,0],"t":259,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-60.943,0],"t":260,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-59.584,0],"t":261,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-58.5,0],"t":262,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-57.616,0],"t":263,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-56.886,0],"t":264,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-56.276,0],"t":265,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-55.762,0],"t":266,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-55.328,0],"t":267,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-54.96,0],"t":268,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-54.648,0],"t":269,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-54.385,0],"t":270,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-54.163,0],"t":271,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.977,0],"t":272,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.824,0],"t":273,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.698,0],"t":274,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.598,0],"t":275,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.521,0],"t":276,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.463,0],"t":277,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.424,0],"t":278,"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,"shapes":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18]},"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}],"ip":197,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":10,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":383,"s":[100]},{"t":392,"s":[0]}]},"r":{"a":0,"k":0},"p":{"a":1,"k":[{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.963},"t":217,"s":[-84.8,0,0],"to":[0,0,0],"ti":[0,0,0]},{"t":250,"s":[0,0,0]}]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.8,0.8],"y":[0.15,0.15]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":250,"s":[302.4,189]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.05,0.05],"y":[0.7,0.7]},"t":255,"s":[227.56,142.34]},{"t":280,"s":[115.3,72.35]}]},"p":{"a":0,"k":[0,0]},"r":{"a":1,"k":[{"i":{"x":[0.8],"y":[0.15]},"o":{"x":[0.3],"y":[0]},"t":250,"s":[14.6]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.05],"y":[0.7]},"t":255,"s":[14.272]},{"t":280,"s":[13.78]}]},"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}],"ip":197,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":268,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":277,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":383,"s":[100]},{"t":392,"s":[0]}]},"r":{"a":0,"k":0},"p":{"k":[{"s":[0,-53.175,0],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.175,0],"t":510,"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,"shapes":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18]},"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}],"ip":197,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":9,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":383,"s":[100]},{"t":392,"s":[0]}]},"r":{"a":0,"k":0},"p":{"a":1,"k":[{"i":{"x":0.8,"y":0.15},"o":{"x":0.3,"y":0},"t":250,"s":[-411.95,20.325,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":255,"s":[-333.47,29.183,0],"to":[0,0,0],"ti":[0,0,0]},{"t":280,"s":[-215.75,42.47,0]}]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"a":0,"k":[115.3,72.35]},"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.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false}],"ip":197,"op":511,"st":0,"ct":1,"bm":0}]},{"id":"comp_1","nm":"Taskbar Lofi","fr":60,"layers":[{"ddd":0,"ind":2,"ty":4,"nm":"app - 5","parent":9,"sr":1,"ks":{"o":{"k":[{"s":[0],"t":161,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[54.85],"t":162,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":163,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":384,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":386,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0},"p":{"k":[{"s":[51.5,0,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.652,0,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.136,0,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[53.013,0,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[54.449,0,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.806,0,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[61.3,0,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[66.437,0,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[68.94,0,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[70.432,0,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[71.462,0,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[72.229,0,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[72.83,0,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[73.314,0,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[73.714,0,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[74.048,0,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[74.334,0,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[74.578,0,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[74.789,0,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[74.971,0,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.131,0,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.269,0,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.389,0,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.493,0,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.584,0,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.663,0,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.731,0,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.789,0,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.839,0,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.915,0,0],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.982,0,0],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[76,0,0],"t":380,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.779,0,0],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.066,0,0],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[73.709,0,0],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[71.271,0,0],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[66.2,0,0],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[60.425,0,0],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[57.886,0,0],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.41,0,0],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.409,0,0],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[54.67,0,0],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[54.1,0,0],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[53.646,0,0],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[53.275,0,0],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.968,0,0],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.711,0,0],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.495,0,0],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.312,0,0],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.157,0,0],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.026,0,0],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.916,0,0],"t":400,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.822,0,0],"t":401,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.745,0,0],"t":402,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.68,0,0],"t":403,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.628,0,0],"t":404,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.585,0,0],"t":405,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.552,0,0],"t":406,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.501,0,0],"t":409,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"a":0,"k":[167,15,0]},"s":{"k":[{"s":[50,50,100],"t":155,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.309,50.309,100],"t":156,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.284,51.284,100],"t":157,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.079,53.079,100],"t":158,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.019,56.019,100],"t":159,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.828,60.828,100],"t":160,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[70,70,100],"t":161,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.485,80.485,100],"t":162,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.597,85.597,100],"t":163,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88.641,88.641,100],"t":164,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.739,90.739,100],"t":165,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.305,92.305,100],"t":166,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[93.53,93.53,100],"t":167,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[94.518,94.518,100],"t":168,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.335,95.335,100],"t":169,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.021,96.021,100],"t":170,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.603,96.603,100],"t":171,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.101,97.101,100],"t":172,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.531,97.531,100],"t":173,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.902,97.902,100],"t":174,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.226,98.226,100],"t":175,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.507,98.507,100],"t":176,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.753,98.753,100],"t":177,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.966,98.966,100],"t":178,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.152,99.152,100],"t":179,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.313,99.313,100],"t":180,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.451,99.451,100],"t":181,"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":182,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.671,99.671,100],"t":183,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.756,99.756,100],"t":184,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.826,99.826,100],"t":185,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.883,99.883,100],"t":186,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.982,99.982,100],"t":189,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[100,100,100],"t":380,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.552,99.552,100],"t":381,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.109,98.109,100],"t":382,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.326,95.326,100],"t":383,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.35,90.35,100],"t":384,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80,80,100],"t":385,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[68.215,68.215,100],"t":386,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[63.027,63.027,100],"t":387,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.02,60.02,100],"t":388,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.977,57.977,100],"t":389,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.47,56.47,100],"t":390,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[55.306,55.306,100],"t":391,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[54.377,54.377,100],"t":392,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.618,53.618,100],"t":393,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.993,52.993,100],"t":394,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.469,52.469,100],"t":395,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.03,52.03,100],"t":396,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.657,51.657,100],"t":397,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.341,51.341,100],"t":398,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.074,51.074,100],"t":399,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.848,50.848,100],"t":400,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.658,50.658,100],"t":401,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.5,50.5,100],"t":402,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.368,50.368,100],"t":403,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.26,50.26,100],"t":404,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.174,50.174,100],"t":405,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.107,50.107,100],"t":406,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.059,50.059,100],"t":407,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.024,50.024,100],"t":408,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse 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":"Ellipse 7511","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[167,15]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,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}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"app - 4","sr":1,"ks":{"o":{"k":[{"s":[0],"t":161,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[54.85],"t":162,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":163,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":384,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":386,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0},"p":{"k":[{"s":[123.341,15,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[123.654,15,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[124.223,15,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[125.146,15,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[126.662,15,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[129.549,15,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[132.838,15,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[134.455,15,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[135.421,15,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[136.083,15,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[136.576,15,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[136.962,15,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[137.272,15,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[137.528,15,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[137.742,15,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[137.924,15,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[138.08,15,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[138.216,15,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[138.334,15,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[138.437,15,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[138.527,15,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[138.606,15,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[138.734,15,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[138.869,15,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[138.864,15,0],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[138.402,15,0],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[137.527,15,0],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[135.96,15,0],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[132.701,15,0],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[129.002,15,0],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[127.358,15,0],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[126.406,15,0],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[125.763,15,0],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[125.288,15,0],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[124.923,15,0],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[124.633,15,0],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[124.396,15,0],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[124.199,15,0],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[124.034,15,0],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[123.895,15,0],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[123.776,15,0],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[123.675,15,0],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[123.589,15,0],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[123.516,15,0],"t":400,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[123.403,15,0],"t":402,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[123.299,15,0],"t":405,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"a":0,"k":[139,15,0]},"s":{"k":[{"s":[50,50,100],"t":155,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.309,50.309,100],"t":156,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.284,51.284,100],"t":157,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.079,53.079,100],"t":158,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.019,56.019,100],"t":159,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.828,60.828,100],"t":160,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[70,70,100],"t":161,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.485,80.485,100],"t":162,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.597,85.597,100],"t":163,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88.641,88.641,100],"t":164,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.739,90.739,100],"t":165,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.305,92.305,100],"t":166,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[93.53,93.53,100],"t":167,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[94.518,94.518,100],"t":168,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.335,95.335,100],"t":169,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.021,96.021,100],"t":170,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.603,96.603,100],"t":171,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.101,97.101,100],"t":172,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.531,97.531,100],"t":173,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.902,97.902,100],"t":174,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.226,98.226,100],"t":175,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.507,98.507,100],"t":176,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.753,98.753,100],"t":177,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.966,98.966,100],"t":178,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.152,99.152,100],"t":179,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.313,99.313,100],"t":180,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.451,99.451,100],"t":181,"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":182,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.671,99.671,100],"t":183,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.756,99.756,100],"t":184,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.826,99.826,100],"t":185,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.883,99.883,100],"t":186,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.982,99.982,100],"t":189,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[100,100,100],"t":380,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.552,99.552,100],"t":381,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.109,98.109,100],"t":382,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.326,95.326,100],"t":383,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.35,90.35,100],"t":384,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80,80,100],"t":385,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[68.215,68.215,100],"t":386,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[63.027,63.027,100],"t":387,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.02,60.02,100],"t":388,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.977,57.977,100],"t":389,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.47,56.47,100],"t":390,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[55.306,55.306,100],"t":391,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[54.377,54.377,100],"t":392,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.618,53.618,100],"t":393,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.993,52.993,100],"t":394,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.469,52.469,100],"t":395,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.03,52.03,100],"t":396,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.657,51.657,100],"t":397,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.341,51.341,100],"t":398,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.074,51.074,100],"t":399,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.848,50.848,100],"t":400,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.658,50.658,100],"t":401,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.5,50.5,100],"t":402,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.368,50.368,100],"t":403,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.26,50.26,100],"t":404,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.174,50.174,100],"t":405,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.107,50.107,100],"t":406,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.059,50.059,100],"t":407,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.024,50.024,100],"t":408,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse 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":"Ellipse 7508","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[139,15]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,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}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"app - 3","sr":1,"ks":{"o":{"k":[{"s":[0],"t":161,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[54.85],"t":162,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":163,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":384,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":386,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0},"p":{"k":[{"s":[104.041,15,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.182,15,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.436,15,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.844,15,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[105.517,15,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[106.808,15,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[108.265,15,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[108.981,15,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[109.409,15,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[109.703,15,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[109.923,15,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.092,15,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.228,15,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.341,15,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.436,15,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.517,15,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.587,15,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.648,15,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.746,15,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.853,15,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.956,15,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.938,15,0],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.73,15,0],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.34,15,0],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[109.649,15,0],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[108.197,15,0],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[106.559,15,0],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[105.828,15,0],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[105.403,15,0],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[105.117,15,0],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.906,15,0],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.745,15,0],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.616,15,0],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.511,15,0],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.424,15,0],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.35,15,0],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.288,15,0],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.19,15,0],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.091,15,0],"t":401,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"a":0,"k":[111,15,0]},"s":{"k":[{"s":[50,50,100],"t":155,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.309,50.309,100],"t":156,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.284,51.284,100],"t":157,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.079,53.079,100],"t":158,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.019,56.019,100],"t":159,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.828,60.828,100],"t":160,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[70,70,100],"t":161,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.485,80.485,100],"t":162,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.597,85.597,100],"t":163,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88.641,88.641,100],"t":164,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.739,90.739,100],"t":165,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.305,92.305,100],"t":166,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[93.53,93.53,100],"t":167,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[94.518,94.518,100],"t":168,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.335,95.335,100],"t":169,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.021,96.021,100],"t":170,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.603,96.603,100],"t":171,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.101,97.101,100],"t":172,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.531,97.531,100],"t":173,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.902,97.902,100],"t":174,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.226,98.226,100],"t":175,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.507,98.507,100],"t":176,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.753,98.753,100],"t":177,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.966,98.966,100],"t":178,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.152,99.152,100],"t":179,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.313,99.313,100],"t":180,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.451,99.451,100],"t":181,"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":182,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.671,99.671,100],"t":183,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.756,99.756,100],"t":184,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.826,99.826,100],"t":185,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.883,99.883,100],"t":186,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.982,99.982,100],"t":189,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[100,100,100],"t":380,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.552,99.552,100],"t":381,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.109,98.109,100],"t":382,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.326,95.326,100],"t":383,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.35,90.35,100],"t":384,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80,80,100],"t":385,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[68.215,68.215,100],"t":386,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[63.027,63.027,100],"t":387,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.02,60.02,100],"t":388,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.977,57.977,100],"t":389,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.47,56.47,100],"t":390,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[55.306,55.306,100],"t":391,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[54.377,54.377,100],"t":392,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.618,53.618,100],"t":393,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.993,52.993,100],"t":394,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.469,52.469,100],"t":395,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.03,52.03,100],"t":396,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.657,51.657,100],"t":397,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.341,51.341,100],"t":398,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.074,51.074,100],"t":399,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.848,50.848,100],"t":400,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.658,50.658,100],"t":401,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.5,50.5,100],"t":402,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.368,50.368,100],"t":403,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.26,50.26,100],"t":404,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.174,50.174,100],"t":405,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.107,50.107,100],"t":406,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.059,50.059,100],"t":407,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.024,50.024,100],"t":408,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse 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":"Ellipse 7507","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[111,15]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,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}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"app - 2","sr":1,"ks":{"o":{"k":[{"s":[0],"t":161,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[54.85],"t":162,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":163,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":384,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":386,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0},"p":{"k":[{"s":[84.704,15,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[84.639,15,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[84.537,15,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[84.371,15,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[84.048,15,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.684,15,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.505,15,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.398,15,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.324,15,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.271,15,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.195,15,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.123,15,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.045,15,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.068,15,0],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.166,15,0],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.338,15,0],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.702,15,0],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[84.112,15,0],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[84.294,15,0],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[84.399,15,0],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[84.47,15,0],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[84.521,15,0],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[84.593,15,0],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[84.676,15,0],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"a":0,"k":[83,15,0]},"s":{"k":[{"s":[50,50,100],"t":155,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.309,50.309,100],"t":156,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.284,51.284,100],"t":157,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.079,53.079,100],"t":158,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.019,56.019,100],"t":159,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.828,60.828,100],"t":160,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[70,70,100],"t":161,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.485,80.485,100],"t":162,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.597,85.597,100],"t":163,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88.641,88.641,100],"t":164,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.739,90.739,100],"t":165,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.305,92.305,100],"t":166,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[93.53,93.53,100],"t":167,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[94.518,94.518,100],"t":168,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.335,95.335,100],"t":169,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.021,96.021,100],"t":170,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.603,96.603,100],"t":171,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.101,97.101,100],"t":172,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.531,97.531,100],"t":173,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.902,97.902,100],"t":174,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.226,98.226,100],"t":175,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.507,98.507,100],"t":176,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.753,98.753,100],"t":177,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.966,98.966,100],"t":178,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.152,99.152,100],"t":179,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.313,99.313,100],"t":180,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.451,99.451,100],"t":181,"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":182,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.671,99.671,100],"t":183,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.756,99.756,100],"t":184,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.826,99.826,100],"t":185,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.883,99.883,100],"t":186,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.982,99.982,100],"t":189,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[100,100,100],"t":380,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.552,99.552,100],"t":381,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.109,98.109,100],"t":382,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.326,95.326,100],"t":383,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.35,90.35,100],"t":384,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80,80,100],"t":385,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[68.215,68.215,100],"t":386,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[63.027,63.027,100],"t":387,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.02,60.02,100],"t":388,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.977,57.977,100],"t":389,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.47,56.47,100],"t":390,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[55.306,55.306,100],"t":391,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[54.377,54.377,100],"t":392,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.618,53.618,100],"t":393,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.993,52.993,100],"t":394,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.469,52.469,100],"t":395,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.03,52.03,100],"t":396,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.657,51.657,100],"t":397,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.341,51.341,100],"t":398,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.074,51.074,100],"t":399,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.848,50.848,100],"t":400,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.658,50.658,100],"t":401,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.5,50.5,100],"t":402,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.368,50.368,100],"t":403,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.26,50.26,100],"t":404,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.174,50.174,100],"t":405,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.107,50.107,100],"t":406,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.059,50.059,100],"t":407,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.024,50.024,100],"t":408,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse 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":"Ellipse 7506","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[83,15]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,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}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"app - 1","sr":1,"ks":{"o":{"k":[{"s":[0],"t":161,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[54.85],"t":162,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":163,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":384,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":386,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0},"p":{"k":[{"s":[65.439,15,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[65.229,15,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[64.849,15,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[64.236,15,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[63.226,15,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[61.296,15,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[59.111,15,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[58.033,15,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[57.388,15,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.945,15,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.616,15,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.359,15,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.154,15,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.984,15,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.842,15,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.72,15,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.616,15,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.525,15,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.447,15,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.378,15,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.317,15,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.265,15,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.219,15,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.178,15,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.143,15,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.113,15,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.066,15,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.012,15,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55,15,0],"t":380,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.092,15,0],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.403,15,0],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.986,15,0],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[57.027,15,0],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[59.212,15,0],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[61.67,15,0],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[62.762,15,0],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[63.396,15,0],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[63.825,15,0],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[64.141,15,0],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[64.385,15,0],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[64.578,15,0],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[64.736,15,0],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[64.867,15,0],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[64.977,15,0],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[65.07,15,0],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[65.149,15,0],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[65.217,15,0],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[65.274,15,0],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[65.323,15,0],"t":400,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[65.364,15,0],"t":401,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[65.426,15,0],"t":403,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[65.491,15,0],"t":407,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"a":0,"k":[55,15,0]},"s":{"k":[{"s":[50,50,100],"t":155,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.309,50.309,100],"t":156,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.284,51.284,100],"t":157,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.079,53.079,100],"t":158,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.019,56.019,100],"t":159,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.828,60.828,100],"t":160,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[70,70,100],"t":161,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.485,80.485,100],"t":162,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.597,85.597,100],"t":163,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88.641,88.641,100],"t":164,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.739,90.739,100],"t":165,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.305,92.305,100],"t":166,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[93.53,93.53,100],"t":167,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[94.518,94.518,100],"t":168,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.335,95.335,100],"t":169,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.021,96.021,100],"t":170,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.603,96.603,100],"t":171,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.101,97.101,100],"t":172,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.531,97.531,100],"t":173,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.902,97.902,100],"t":174,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.226,98.226,100],"t":175,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.507,98.507,100],"t":176,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.753,98.753,100],"t":177,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.966,98.966,100],"t":178,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.152,99.152,100],"t":179,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.313,99.313,100],"t":180,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.451,99.451,100],"t":181,"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":182,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.671,99.671,100],"t":183,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.756,99.756,100],"t":184,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.826,99.826,100],"t":185,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.883,99.883,100],"t":186,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.982,99.982,100],"t":189,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[100,100,100],"t":380,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.552,99.552,100],"t":381,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.109,98.109,100],"t":382,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.326,95.326,100],"t":383,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.35,90.35,100],"t":384,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80,80,100],"t":385,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[68.215,68.215,100],"t":386,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[63.027,63.027,100],"t":387,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.02,60.02,100],"t":388,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.977,57.977,100],"t":389,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.47,56.47,100],"t":390,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[55.306,55.306,100],"t":391,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[54.377,54.377,100],"t":392,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.618,53.618,100],"t":393,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.993,52.993,100],"t":394,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.469,52.469,100],"t":395,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.03,52.03,100],"t":396,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.657,51.657,100],"t":397,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.341,51.341,100],"t":398,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.074,51.074,100],"t":399,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.848,50.848,100],"t":400,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.658,50.658,100],"t":401,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.5,50.5,100],"t":402,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.368,50.368,100],"t":403,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.26,50.26,100],"t":404,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.174,50.174,100],"t":405,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.107,50.107,100],"t":406,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.059,50.059,100],"t":407,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.024,50.024,100],"t":408,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse 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":"Ellipse 7505","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[55,15]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,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":7,"ty":4,"nm":"divider","sr":1,"ks":{"o":{"k":[{"s":[0],"t":161,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[54.85],"t":162,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":163,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":384,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":386,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":90},"p":{"k":[{"s":[51,15,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.913,15,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.615,15,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.073,15,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[49.194,15,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[47.751,15,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[45.001,15,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.869,15,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[40.328,15,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[39.409,15,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[38.778,15,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[38.309,15,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[37.941,15,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[37.645,15,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[37.402,15,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[37.199,15,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[37.025,15,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.876,15,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.747,15,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.635,15,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.536,15,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.451,15,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.376,15,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.31,15,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.253,15,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.203,15,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.161,15,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.124,15,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.093,15,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.068,15,0],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.047,15,0],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.017,15,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36,15,0],"t":380,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.129,15,0],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.569,15,0],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[37.403,15,0],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[38.895,15,0],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.999,15,0],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[45.522,15,0],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[47.088,15,0],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[47.994,15,0],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[48.607,15,0],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[49.059,15,0],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[49.407,15,0],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[49.683,15,0],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[49.909,15,0],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.096,15,0],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.253,15,0],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.386,15,0],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.499,15,0],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.596,15,0],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.677,15,0],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.747,15,0],"t":400,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.806,15,0],"t":401,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.854,15,0],"t":402,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.895,15,0],"t":403,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.927,15,0],"t":404,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.973,15,0],"t":406,"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,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"k":[{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2,-0.5],[2,-0.5]],"c":false}],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.019,-0.5],[2.019,-0.5]],"c":false}],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.077,-0.5],[2.077,-0.5]],"c":false}],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.185,-0.5],[2.185,-0.5]],"c":false}],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.361,-0.5],[2.361,-0.5]],"c":false}],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.65,-0.5],[2.65,-0.5]],"c":false}],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-3.2,-0.5],[3.2,-0.5]],"c":false}],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-3.829,-0.5],[3.829,-0.5]],"c":false}],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.136,-0.5],[4.136,-0.5]],"c":false}],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.318,-0.5],[4.318,-0.5]],"c":false}],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.444,-0.5],[4.444,-0.5]],"c":false}],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.538,-0.5],[4.538,-0.5]],"c":false}],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.612,-0.5],[4.612,-0.5]],"c":false}],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.671,-0.5],[4.671,-0.5]],"c":false}],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.72,-0.5],[4.72,-0.5]],"c":false}],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.761,-0.5],[4.761,-0.5]],"c":false}],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.796,-0.5],[4.796,-0.5]],"c":false}],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.826,-0.5],[4.826,-0.5]],"c":false}],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.852,-0.5],[4.852,-0.5]],"c":false}],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.874,-0.5],[4.874,-0.5]],"c":false}],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.894,-0.5],[4.894,-0.5]],"c":false}],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.91,-0.5],[4.91,-0.5]],"c":false}],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.925,-0.5],[4.925,-0.5]],"c":false}],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.938,-0.5],[4.938,-0.5]],"c":false}],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.949,-0.5],[4.949,-0.5]],"c":false}],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.959,-0.5],[4.959,-0.5]],"c":false}],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.967,-0.5],[4.967,-0.5]],"c":false}],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.974,-0.5],[4.974,-0.5]],"c":false}],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.98,-0.5],[4.98,-0.5]],"c":false}],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.99,-0.5],[4.99,-0.5]],"c":false}],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.996,-0.5],[4.996,-0.5]],"c":false}],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-5,-0.5],[5,-0.5]],"c":false}],"t":380,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.973,-0.5],[4.973,-0.5]],"c":false}],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.887,-0.5],[4.887,-0.5]],"c":false}],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.72,-0.5],[4.72,-0.5]],"c":false}],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.421,-0.5],[4.421,-0.5]],"c":false}],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-3.8,-0.5],[3.8,-0.5]],"c":false}],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-3.093,-0.5],[3.093,-0.5]],"c":false}],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.782,-0.5],[2.782,-0.5]],"c":false}],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.601,-0.5],[2.601,-0.5]],"c":false}],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.479,-0.5],[2.479,-0.5]],"c":false}],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.388,-0.5],[2.388,-0.5]],"c":false}],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.318,-0.5],[2.318,-0.5]],"c":false}],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.263,-0.5],[2.263,-0.5]],"c":false}],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.217,-0.5],[2.217,-0.5]],"c":false}],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.18,-0.5],[2.18,-0.5]],"c":false}],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.148,-0.5],[2.148,-0.5]],"c":false}],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.122,-0.5],[2.122,-0.5]],"c":false}],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.099,-0.5],[2.099,-0.5]],"c":false}],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.081,-0.5],[2.081,-0.5]],"c":false}],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.064,-0.5],[2.064,-0.5]],"c":false}],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.051,-0.5],[2.051,-0.5]],"c":false}],"t":400,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.039,-0.5],[2.039,-0.5]],"c":false}],"t":401,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.03,-0.5],[2.03,-0.5]],"c":false}],"t":402,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.022,-0.5],[2.022,-0.5]],"c":false}],"t":403,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.01,-0.5],[2.01,-0.5]],"c":false}],"t":405,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.006,-0.5],[2.006,-0.5]],"c":false}],"t":406,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.004,-0.5],[2.004,-0.5]],"c":false}],"t":407,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.001,-0.5],[2.001,-0.5]],"c":false}],"t":408,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":1,"ml":4,"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":"divider","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":9,"sr":1,"ks":{"o":{"k":[{"s":[0],"t":161,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[54.85],"t":162,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":163,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":384,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":386,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0},"p":{"k":[{"s":[-52.349,0.652,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.453,0.652,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.813,0.652,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.464,0.652,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-54.515,0.652,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-56.247,0.652,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-59.565,0.652,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-63.323,0.652,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-65.162,0.652,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-66.258,0.652,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-67.015,0.652,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-67.578,0.652,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-68.019,0.652,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-68.375,0.652,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-68.668,0.652,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-68.914,0.652,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-69.122,0.652,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-69.301,0.652,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-69.456,0.652,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-69.59,0.652,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-69.708,0.652,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-69.81,0.652,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-69.9,0.652,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-69.978,0.652,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-70.047,0.652,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-70.106,0.652,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-70.157,0.652,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-70.2,0.652,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-70.268,0.652,0],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-70.328,0.652,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-70.349,0.652,0],"t":380,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-70.193,0.652,0],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-69.662,0.652,0],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-68.662,0.652,0],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-66.874,0.652,0],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-63.132,0.652,0],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-58.906,0.652,0],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-57.04,0.652,0],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-55.956,0.652,0],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-55.22,0.652,0],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-54.678,0.652,0],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-54.259,0.652,0],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.925,0.652,0],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.654,0.652,0],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.43,0.652,0],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.241,0.652,0],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.082,0.652,0],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.947,0.652,0],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.831,0.652,0],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.734,0.652,0],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.65,0.652,0],"t":400,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.58,0.652,0],"t":401,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.522,0.652,0],"t":402,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.474,0.652,0],"t":403,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.435,0.652,0],"t":404,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.404,0.652,0],"t":405,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.354,0.652,0],"t":408,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"a":0,"k":[6.826,6.826,0]},"s":{"k":[{"s":[50,50,100],"t":155,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.309,50.309,100],"t":156,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.284,51.284,100],"t":157,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.079,53.079,100],"t":158,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.019,56.019,100],"t":159,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.828,60.828,100],"t":160,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[70,70,100],"t":161,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.485,80.485,100],"t":162,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.597,85.597,100],"t":163,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88.641,88.641,100],"t":164,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.739,90.739,100],"t":165,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.305,92.305,100],"t":166,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[93.53,93.53,100],"t":167,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[94.518,94.518,100],"t":168,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.335,95.335,100],"t":169,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.021,96.021,100],"t":170,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.603,96.603,100],"t":171,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.101,97.101,100],"t":172,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.531,97.531,100],"t":173,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.902,97.902,100],"t":174,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.226,98.226,100],"t":175,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.507,98.507,100],"t":176,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.753,98.753,100],"t":177,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.966,98.966,100],"t":178,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.152,99.152,100],"t":179,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.313,99.313,100],"t":180,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.451,99.451,100],"t":181,"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":182,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.671,99.671,100],"t":183,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.756,99.756,100],"t":184,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.826,99.826,100],"t":185,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.883,99.883,100],"t":186,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.982,99.982,100],"t":189,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[100,100,100],"t":380,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.552,99.552,100],"t":381,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.109,98.109,100],"t":382,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.326,95.326,100],"t":383,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.35,90.35,100],"t":384,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80,80,100],"t":385,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[68.215,68.215,100],"t":386,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[63.027,63.027,100],"t":387,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.02,60.02,100],"t":388,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.977,57.977,100],"t":389,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.47,56.47,100],"t":390,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[55.306,55.306,100],"t":391,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[54.377,54.377,100],"t":392,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.618,53.618,100],"t":393,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.993,52.993,100],"t":394,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.469,52.469,100],"t":395,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.03,52.03,100],"t":396,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.657,51.657,100],"t":397,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.341,51.341,100],"t":398,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.074,51.074,100],"t":399,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.848,50.848,100],"t":400,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.658,50.658,100],"t":401,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.5,50.5,100],"t":402,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.368,50.368,100],"t":403,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.26,50.26,100],"t":404,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.174,50.174,100],"t":405,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.107,50.107,100],"t":406,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.059,50.059,100],"t":407,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.024,50.024,100],"t":408,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-1.381,0],[0,1.381],[1.381,0],[0,-1.381]],"o":[[1.381,0],[0,-1.381],[-1.381,0],[0,1.381]],"v":[[0,2.5],[2.5,0],[0,-2.5],[-2.5,0]],"c":true}},"nm":"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":"Ellipse 12","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[2.5,9.501]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 12","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-1.381,0],[0,1.381],[1.381,0],[0,-1.381]],"o":[[1.381,0],[0,-1.381],[-1.381,0],[0,1.381]],"v":[[0,2.5],[2.5,0],[0,-2.5],[-2.5,0]],"c":true}},"nm":"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":"Ellipse 11","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[2.5,2.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":"Ellipse 11","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-1.381,0],[0,1.381],[1.381,0],[0,-1.381]],"o":[[1.381,0],[0,-1.381],[-1.381,0],[0,1.381]],"v":[[0,2.5],[2.5,0],[0,-2.5],[-2.5,0]],"c":true}},"nm":"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":"Ellipse 5","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[9.5,2.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":"Ellipse 5","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.185,0.148],[0,0],[0,0],[0,0],[-0.086,0.24],[0,0.271],[0.468,0.462],[0.671,0],[0.468,-0.468],[0,-0.671],[-0.462,-0.468],[-0.671,0],[-0.24,0.086]],"o":[[0,0],[0,0],[0,0],[0.148,-0.185],[0.086,-0.24],[0,-0.671],[-0.462,-0.468],[-0.671,0],[-0.462,0.462],[0,0.671],[0.468,0.462],[0.271,0],[0.24,-0.086]],"v":[[0.48,0.998],[2.809,3.326],[3.326,2.809],[0.998,0.48],[1.349,-0.157],[1.478,-0.924],[0.776,-2.624],[-0.924,-3.326],[-2.633,-2.624],[-3.326,-0.924],[-2.633,0.785],[-0.924,1.478],[-0.157,1.349]],"c":true}},"nm":"Path 1","hd":false},{"ty":"mm","mm":5,"nm":"Merge Paths 1","hd":false},{"ind":2,"ty":"sh","ks":{"a":0,"k":{"i":[[0.326,-0.326],[0.462,0],[0.326,0.32],[0,0.462],[-0.32,0.32],[-0.462,0],[-0.32,-0.326],[0,-0.462]],"o":[[-0.32,0.32],[-0.462,0],[-0.32,-0.326],[0,-0.462],[0.326,-0.326],[0.462,0],[0.326,0.32],[0,0.462]],"v":[[0.249,0.259],[-0.924,0.739],[-2.106,0.259],[-2.587,-0.924],[-2.106,-2.097],[-0.924,-2.587],[0.249,-2.097],[0.739,-0.924]],"c":true}},"nm":"Path 2","hd":false},{"ty":"mm","mm":5,"nm":"Merge Paths 2","hd":false},{"ty":"st","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":0.4},"lc":1,"lj":2,"bm":0,"nm":"Stroke 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":"icon","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[10.326,10.326]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,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":9,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[91,15,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":{"k":[{"s":[120,4],"t":155,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[120.383,4.161],"t":156,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[121.592,4.668],"t":157,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[123.818,5.601],"t":158,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[127.463,7.13],"t":159,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[133.427,9.631],"t":160,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[144.8,14.4],"t":161,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[157.801,19.852],"t":162,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[164.141,22.511],"t":163,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[167.915,24.093],"t":164,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[170.516,25.184],"t":165,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[172.458,25.999],"t":166,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[173.978,26.636],"t":167,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[175.203,27.15],"t":168,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[176.216,27.574],"t":169,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[177.065,27.931],"t":170,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[177.788,28.234],"t":171,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[178.406,28.493],"t":172,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[178.938,28.716],"t":173,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[179.399,28.909],"t":174,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[179.8,29.078],"t":175,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[180.149,29.224],"t":176,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[180.454,29.352],"t":177,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[180.718,29.463],"t":178,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[180.949,29.559],"t":179,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[181.148,29.643],"t":180,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[181.32,29.715],"t":181,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[181.467,29.777],"t":182,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[181.592,29.829],"t":183,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[181.697,29.873],"t":184,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[181.784,29.91],"t":185,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[181.855,29.939],"t":186,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[181.909,29.962],"t":187,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[181.978,29.991],"t":189,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[182,30],"t":380,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[181.445,29.767],"t":381,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[179.655,29.017],"t":382,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[176.204,27.569],"t":383,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[170.034,24.982],"t":384,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[157.2,19.6],"t":385,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[142.586,13.472],"t":386,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[136.154,10.774],"t":387,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[132.424,9.21],"t":388,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[129.891,8.148],"t":389,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[128.022,7.364],"t":390,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[126.579,6.759],"t":391,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[125.427,6.276],"t":392,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[124.487,5.881],"t":393,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[123.712,5.556],"t":394,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[123.062,5.284],"t":395,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[122.517,5.055],"t":396,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[122.055,4.862],"t":397,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[121.663,4.697],"t":398,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[121.332,4.559],"t":399,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[121.051,4.441],"t":400,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[120.815,4.342],"t":401,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[120.62,4.26],"t":402,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[120.456,4.191],"t":403,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[120.323,4.135],"t":404,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[120.216,4.091],"t":405,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[120.133,4.056],"t":406,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[120.073,4.03],"t":407,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[120.03,4.013],"t":408,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[120.008,4.003],"t":409,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}}]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":32.672},"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":"Taskbar Lofi","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]},{"id":"comp_2","nm":"Recents_LofiApp","fr":60,"pfr":1,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","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.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":"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.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":"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.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":"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.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":"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.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":"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.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":"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":".onSecondaryFixed","cl":"onSecondaryFixed","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.145098039216,0.101960784314,0.01568627451,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,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":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","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.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":"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.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":"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.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":"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":".onSecondaryFixed","cl":"onSecondaryFixed","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.145098039216,0.101960784314,0.01568627451,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,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.145098039216,0.101960784314,0.01568627451,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,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.145098039216,0.101960784314,0.01568627451,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,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.145098039216,0.101960784314,0.01568627451,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,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.145098039216,0.101960784314,0.01568627451,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,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.145098039216,0.101960784314,0.01568627451,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,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.145098039216,0.101960784314,0.01568627451,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,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.145098039216,0.101960784314,0.01568627451,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,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":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","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.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":"app only","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","parent":2,"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":250,"s":[100]},{"t":256,"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":248,"s":[28,28]},{"t":258,"s":[36,36]}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse 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":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":248,"s":[33,0],"to":[0,0],"ti":[0,0]},{"t":258,"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":248,"s":[28,28]},{"t":258,"s":[36,36]}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse 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":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":248,"s":[-33,0],"to":[0,0],"ti":[0,0]},{"t":258,"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":248,"s":[28,28]},{"t":258,"s":[36,36]}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse 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":"size","bm":0,"hd":false}],"ip":37,"op":345,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","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.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":"Frame 1321317559","bm":0,"hd":false}],"ip":0,"op":600,"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,"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,1,1,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":0,"nm":"Recents_EDU Loop","parent":4,"tt":1,"tp":4,"refId":"comp_0","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":0,"k":[100,100,100]}},"ao":0,"w":504,"h":315,"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"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":8,"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":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":"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":511,"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":600,"st":0,"ct":1,"bm":0}],"markers":[{"tm":121,"cm":"start","dr":0},{"tm":142,"cm":"gesture","dr":75},{"tm":250,"cm":"release","dr":36},{"tm":356,"cm":"FLIP","dr":0},{"tm":392,"cm":"launch","dr":66}],"props":{}}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/trackpad_recent_apps_success.json b/packages/SystemUI/res/raw/trackpad_recent_apps_success.json
new file mode 100644
index 0000000..bec6f35
--- /dev/null
+++ b/packages/SystemUI/res/raw/trackpad_recent_apps_success.json
@@ -0,0 +1 @@
+{"v":"5.12.1","fr":60,"ip":0,"op":50,"w":554,"h":564,"nm":"Trackpad-JSON_Recents-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":"Recents_LofiApp","fr":60,"pfr":1,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","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.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":"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.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":"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.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":"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.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":"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.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":"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.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":"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":".onSecondaryFixed","cl":"onSecondaryFixed","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.145098039216,0.101960784314,0.01568627451,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,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":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","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.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":"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.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":"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.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":"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":".onSecondaryFixed","cl":"onSecondaryFixed","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.145098039216,0.101960784314,0.01568627451,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,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.145098039216,0.101960784314,0.01568627451,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,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.145098039216,0.101960784314,0.01568627451,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,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.145098039216,0.101960784314,0.01568627451,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,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.145098039216,0.101960784314,0.01568627451,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,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.145098039216,0.101960784314,0.01568627451,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,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.145098039216,0.101960784314,0.01568627451,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,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.145098039216,0.101960784314,0.01568627451,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,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":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","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.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":"app only","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":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","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.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":"Frame 1321317559","bm":0,"hd":false}],"ip":0,"op":600,"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,"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,1,1,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false}],"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":0,"nm":"Recents_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":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"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":7,"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":8,"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/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
index 2a27b47..3efe7a5 100644
--- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
@@ -26,6 +26,10 @@
     <dimen name="keyguard_clock_top_margin">8dp</dimen>
     <dimen name="keyguard_smartspace_top_offset">0dp</dimen>
 
+    <!-- New keyboard shortcut helper -->
+    <dimen name="shortcut_helper_width">864dp</dimen>
+    <dimen name="shortcut_helper_height">728dp</dimen>
+
     <!-- QS-->
     <dimen name="qs_panel_padding_top">16dp</dimen>
     <dimen name="qs_panel_padding">24dp</dimen>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index e1808fa..00846cb 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1005,6 +1005,10 @@
     <dimen name="ksh_app_item_minimum_height">64dp</dimen>
     <dimen name="ksh_category_separator_margin">16dp</dimen>
 
+    <!-- New keyboard shortcut helper -->
+    <dimen name="shortcut_helper_width">412dp</dimen>
+    <dimen name="shortcut_helper_height">728dp</dimen>
+
     <!-- The size of corner radius of the arrow in the onboarding toast. -->
     <dimen name="recents_onboarding_toast_arrow_corner_radius">2dp</dimen>
 
@@ -1976,6 +1980,14 @@
     <dimen name="backlight_indicator_step_small_radius">4dp</dimen>
     <dimen name="backlight_indicator_step_large_radius">28dp</dimen>
 
+    <!-- Touchpad gestures tutorial-->
+    <!-- This value is in unit of dp/ms
+        TriggerSwipeUpTouchTracker (which is base for gesture tutorial implementation) uses value
+        of 0.5dp but from manual testing it's too high and doesn't really feel like it's forcing
+        slowing down. Also for tutorial it should be fine to lean to the side of being more strict
+        rather than not strict enough and not teaching user the proper gesture as a result.-->
+    <dimen name="touchpad_recent_apps_gesture_velocity_threshold">0.05dp</dimen>
+
     <!-- Broadcast dialog -->
     <dimen name="broadcast_dialog_title_img_margin_top">18dp</dimen>
     <dimen name="broadcast_dialog_title_text_size">24sp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 9614067..f9c2aef 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3369,6 +3369,8 @@
     <!-- Toast shown when a notification does not support dragging to split [CHAR LIMIT=NONE] -->
     <string name="drag_split_not_supported">This notification does not support dragging to split screen</string>
 
+    <!-- Content description for the location icon in the dream overlay status bar [CHAR LIMIT=NONE] -->
+    <string name="dream_overlay_location_active">Location active</string>
     <!-- Content description for the Wi-Fi off icon in the dream overlay status bar [CHAR LIMIT=NONE] -->
     <string name="dream_overlay_status_bar_wifi_off">Wi\u2011Fi unavailable</string>
     <!-- Content description for the priority mode icon in the dream overlay status bar [CHAR LIMIT=NONE] -->
@@ -3561,6 +3563,9 @@
     <!-- Content description for Wi-Fi not available icon on dream [CHAR LIMIT=NONE]-->
     <string name="wifi_unavailable_dream_overlay_content_description">Wi-Fi not available</string>
 
+    <!-- Content description for location in use icon on dream [CHAR LIMIT=NONE] -->
+    <string name="location_active_dream_overlay_content_description">Location active</string>
+
     <!-- Content description for camera blocked icon on dream [CHAR LIMIT=NONE] -->
     <string name="camera_blocked_dream_overlay_content_description">Camera blocked</string>
 
@@ -3724,8 +3729,8 @@
     <string name="touchpad_tutorial_back_gesture_button">Back gesture</string>
     <!-- Label for button opening tutorial for back gesture on touchpad [CHAR LIMIT=NONE] -->
     <string name="touchpad_tutorial_home_gesture_button">Home gesture</string>
-    <!-- Label for button opening tutorial on using Action key from keyboard [CHAR LIMIT=NONE] -->
-    <string name="touchpad_tutorial_action_key_button">Action key</string>
+    <!-- Label for button opening tutorial for "view recent apps" gesture on touchpad [CHAR LIMIT=NONE] -->
+    <string name="touchpad_tutorial_recent_apps_gesture_button">View recent apps</string>
     <!-- Label for button finishing touchpad tutorial [CHAR LIMIT=NONE] -->
     <string name="touchpad_tutorial_done_button">Done</string>
     <!-- BACK GESTURE -->
@@ -3747,6 +3752,15 @@
     <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>
+    <!-- RECENT APPS GESTURE -->
+    <!-- Touchpad recent apps gesture action name in tutorial [CHAR LIMIT=NONE] -->
+    <string name="touchpad_recent_apps_gesture_action_title">View recent apps</string>
+    <!-- Touchpad recent apps gesture guidance in gestures tutorial [CHAR LIMIT=NONE] -->
+    <string name="touchpad_recent_apps_gesture_guidance">Swipe up and hold using three fingers on your touchpad.</string>
+    <!-- Screen title after recent apps gesture was done successfully [CHAR LIMIT=NONE] -->
+    <string name="touchpad_recent_apps_gesture_success_title">Great job!</string>
+    <!-- Text shown to the user after they complete recent apps gesture tutorial [CHAR LIMIT=NONE] -->
+    <string name="touchpad_recent_apps_gesture_success_body">You completed the view recent apps gesture.</string>
 
     <!-- KEYBOARD TUTORIAL-->
     <!-- Action key tutorial title [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardConstants.java b/packages/SystemUI/src/com/android/keyguard/KeyguardConstants.java
index a5b62b6..b16c683 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardConstants.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardConstants.java
@@ -28,5 +28,4 @@
      * be used temporarily for debugging.
      */
     public static final boolean DEBUG = Log.isLoggable("Keyguard", Log.DEBUG);
-    public static final boolean DEBUG_SIM_STATES = true;
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
index 1c1acf8..52c93f7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -53,7 +53,6 @@
         extends KeyguardPinBasedInputViewController<KeyguardSimPinView> {
     public static final String TAG = "KeyguardSimPinView";
     private static final String LOG_TAG = "KeyguardSimPinView";
-    private static final boolean DEBUG = KeyguardConstants.DEBUG_SIM_STATES;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final TelephonyManager mTelephonyManager;
 
@@ -71,7 +70,7 @@
     KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() {
         @Override
         public void onSimStateChanged(int subId, int slotId, int simState) {
-            if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")");
+            Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")");
             // If subId has gone to PUK required then we need to go to the PUK screen.
             if (subId == mSubId && simState == TelephonyManager.SIM_STATE_PUK_REQUIRED) {
                 getKeyguardSecurityCallback().showCurrentSecurityScreen();
@@ -129,7 +128,7 @@
     @Override
     void resetState() {
         super.resetState();
-        if (DEBUG) Log.v(TAG, "Resetting state");
+        Log.v(TAG, "Resetting state");
         handleSubInfoChangeIfNeeded();
         mMessageAreaController.setMessage("");
         if (mShowDefaultMessage) {
@@ -216,11 +215,9 @@
                                 mMessageAreaController.setMessage(mView.getResources().getString(
                                         R.string.kg_password_pin_failed));
                             }
-                            if (DEBUG) {
-                                Log.d(LOG_TAG, "verifyPasswordAndUnlock "
-                                        + " CheckSimPin.onSimCheckResponse: " + result
-                                        + " attemptsRemaining=" + result.getAttemptsRemaining());
-                            }
+                            Log.d(LOG_TAG, "verifyPasswordAndUnlock "
+                                    + " CheckSimPin.onSimCheckResponse: " + result
+                                    + " attemptsRemaining=" + result.getAttemptsRemaining());
                         }
                         getKeyguardSecurityCallback().userActivity();
                         mCheckSimPinThread = null;
@@ -280,10 +277,8 @@
             displayMessage = mView.getResources()
                     .getString(R.string.kg_sim_lock_esim_instructions, displayMessage);
         }
-        if (DEBUG) {
-            Log.d(LOG_TAG, "getPinPasswordErrorMessage: attemptsRemaining="
-                    + attemptsRemaining + " displayMessage=" + displayMessage);
-        }
+        Log.d(LOG_TAG, "getPinPasswordErrorMessage: attemptsRemaining="
+                + attemptsRemaining + " displayMessage=" + displayMessage);
         return displayMessage;
     }
 
@@ -323,14 +318,10 @@
 
         @Override
         public void run() {
-            if (DEBUG) {
-                Log.v(TAG, "call supplyIccLockPin(subid=" + mSubId + ")");
-            }
+            Log.v(TAG, "call supplyIccLockPin(subid=" + mSubId + ")");
             TelephonyManager telephonyManager = mTelephonyManager.createForSubscriptionId(mSubId);
             final PinResult result = telephonyManager.supplyIccLockPin(mPin);
-            if (DEBUG) {
-                Log.v(TAG, "supplyIccLockPin returned: " + result.toString());
-            }
+            Log.v(TAG, "supplyIccLockPin returned: " + result.toString());
             mView.post(() -> onSimCheckResponse(result));
         }
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index f731186..22130f8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -43,6 +43,7 @@
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
+import static com.android.systemui.Flags.simPinBouncerReset;
 import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED;
 
 import android.annotation.AnyThread;
@@ -1703,6 +1704,9 @@
                         intent.getStringExtra(Intent.EXTRA_SIM_STATE),
                         args.slotId,
                         args.subId);
+                if (args.slotId == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
+                    return;
+                }
                 mHandler.obtainMessage(MSG_SIM_STATE_CHANGE, args.subId, args.slotId, args.simState)
                         .sendToTarget();
             } else if (TelephonyManager.ACTION_PHONE_STATE_CHANGED.equals(action)) {
@@ -1940,7 +1944,13 @@
             }
             int state = TelephonyManager.SIM_STATE_UNKNOWN;
             String stateExtra = intent.getStringExtra(Intent.EXTRA_SIM_STATE);
-            int slotId = intent.getIntExtra(SubscriptionManager.EXTRA_SLOT_INDEX, 0);
+
+            int defaultSlotId = 0;
+            if (simPinBouncerReset()) {
+                defaultSlotId = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
+            }
+            int slotId = intent.getIntExtra(SubscriptionManager.EXTRA_SLOT_INDEX,
+                    defaultSlotId);
             int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
                     SubscriptionManager.INVALID_SUBSCRIPTION_ID);
             if (Intent.SIM_STATE_ABSENT.equals(stateExtra)) {
@@ -2479,6 +2489,12 @@
                     this::onTransitionStateChanged
             );
         }
+
+        // start() can be invoked in the middle of user switching, so check for this state and issue
+        // the call manually as that important event was missed.
+        if (mUserTracker.isUserSwitching()) {
+            handleUserSwitching(mUserTracker.getUserId(), () -> {});
+        }
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarView.java b/packages/SystemUI/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarView.java
index aa96231..d4e74d3 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarView.java
@@ -54,6 +54,7 @@
             STATUS_ICON_MIC_CAMERA_DISABLED,
             STATUS_ICON_PRIORITY_MODE_ON,
             STATUS_ICON_ASSISTANT_ATTENTION_ACTIVE,
+            STATUS_ICON_LOCATION_ACTIVE,
     })
     public @interface StatusIconType {}
     public static final int STATUS_ICON_NOTIFICATIONS = 0;
@@ -64,6 +65,7 @@
     public static final int STATUS_ICON_MIC_CAMERA_DISABLED = 5;
     public static final int STATUS_ICON_PRIORITY_MODE_ON = 6;
     public static final int STATUS_ICON_ASSISTANT_ATTENTION_ACTIVE = 7;
+    public static final int STATUS_ICON_LOCATION_ACTIVE = 8;
 
     private final Map<Integer, View> mStatusIcons = new HashMap<>();
     private Context mContext;
@@ -136,6 +138,8 @@
                 addDoubleShadow(fetchStatusIconForResId(R.id.dream_overlay_priority_mode)));
         mStatusIcons.put(STATUS_ICON_ASSISTANT_ATTENTION_ACTIVE,
                 fetchStatusIconForResId(R.id.dream_overlay_assistant_attention_indicator));
+        mStatusIcons.put(STATUS_ICON_LOCATION_ACTIVE,
+                fetchStatusIconForResId(R.id.dream_overlay_location_active));
 
         mSystemStatusViewGroup = findViewById(R.id.dream_overlay_system_status);
         mExtraSystemStatusViewGroup = findViewById(R.id.dream_overlay_extra_items);
@@ -151,6 +155,7 @@
             case STATUS_ICON_MIC_CAMERA_DISABLED -> "mic_camera_disabled";
             case STATUS_ICON_PRIORITY_MODE_ON -> "priority_mode_on";
             case STATUS_ICON_ASSISTANT_ATTENTION_ACTIVE -> "assistant_attention_active";
+            case STATUS_ICON_LOCATION_ACTIVE -> "location_active";
             default -> type + "(unknown)";
         };
     }
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewController.java
index 04595a2..75024c6 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewController.java
@@ -27,6 +27,7 @@
 import android.util.PluralsMessageFormatter;
 import android.view.View;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
@@ -39,6 +40,9 @@
 import com.android.systemui.dreams.DreamOverlayStatusBarItemsProvider.StatusBarItem;
 import com.android.systemui.log.LogBuffer;
 import com.android.systemui.log.dagger.DreamLog;
+import com.android.systemui.privacy.PrivacyItem;
+import com.android.systemui.privacy.PrivacyItemController;
+import com.android.systemui.privacy.PrivacyType;
 import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.CrossFadeHelper;
@@ -79,6 +83,7 @@
     private final DreamOverlayStateController mDreamOverlayStateController;
     private final UserTracker mUserTracker;
     private final WifiInteractor mWifiInteractor;
+    private final PrivacyItemController mPrivacyItemController;
     private final StatusBarWindowStateController mStatusBarWindowStateController;
     private final DreamOverlayStatusBarItemsProvider mStatusBarItemsProvider;
     private final Executor mMainExecutor;
@@ -131,6 +136,9 @@
     private final StatusBarWindowStateListener mStatusBarWindowStateListener =
             this::onSystemStatusBarStateChanged;
 
+    private final PrivacyItemController.Callback mPrivacyItemControllerCallback =
+            this::onPrivacyItemsChanged;
+
     @Inject
     public AmbientStatusBarViewController(
             AmbientStatusBarView view,
@@ -147,6 +155,7 @@
             DreamOverlayStateController dreamOverlayStateController,
             UserTracker userTracker,
             WifiInteractor wifiInteractor,
+            PrivacyItemController privacyItemController,
             CommunalSceneInteractor communalSceneInteractor,
             @DreamLog LogBuffer logBuffer) {
         super(view);
@@ -163,6 +172,7 @@
         mDreamOverlayStateController = dreamOverlayStateController;
         mUserTracker = userTracker;
         mWifiInteractor = wifiInteractor;
+        mPrivacyItemController = privacyItemController;
         mCommunalSceneInteractor = communalSceneInteractor;
         mLogger = new DreamLogger(logBuffer, TAG);
     }
@@ -174,10 +184,12 @@
         // Register to receive show/hide updates for the system status bar. Our custom status bar
         // needs to hide when the system status bar is showing to ovoid overlapping status bars.
         mStatusBarWindowStateController.addListener(mStatusBarWindowStateListener);
+        mPrivacyItemController.addCallback(mPrivacyItemControllerCallback);
     }
 
     @Override
     public void destroy() {
+        mPrivacyItemController.removeCallback(mPrivacyItemControllerCallback);
         mStatusBarWindowStateController.removeListener(mStatusBarWindowStateListener);
 
         super.destroy();
@@ -274,6 +286,11 @@
                 R.string.wifi_unavailable_dream_overlay_content_description);
     }
 
+    void updateLocationStatusIcon(boolean enabled) {
+        showIcon(AmbientStatusBarView.STATUS_ICON_LOCATION_ACTIVE, enabled,
+                R.string.location_active_dream_overlay_content_description);
+    }
+
     private void updateAlarmStatusIcon() {
         final AlarmManager.AlarmClockInfo alarm =
                 mAlarmManager.getNextAlarmClock(mUserTracker.getUserId());
@@ -369,6 +386,11 @@
         mMainExecutor.execute(this::updateVisibility);
     }
 
+    private void onPrivacyItemsChanged(@NonNull List<PrivacyItem> privacyItems) {
+        updateLocationStatusIcon(privacyItems.stream()
+                .anyMatch(item -> item.getPrivacyType() == PrivacyType.TYPE_LOCATION));
+    }
+
     private void onStatusBarItemsChanged(List<StatusBarItem> newItems) {
         mMainExecutor.execute(() -> {
             mExtraStatusBarItems.clear();
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
index c28bce2..6d6cd45 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
@@ -294,7 +294,9 @@
 
     /** Tell the bouncer that bouncer is requested when device is already authenticated */
     fun notifyUserRequestedBouncerWhenAlreadyAuthenticated(userId: Int) {
-        applicationScope.launch { repository.setKeyguardAuthenticatedPrimaryAuth(userId) }
+        applicationScope.launch {
+            repository.setUserRequestedBouncerWhenAlreadyAuthenticated(userId)
+        }
     }
 
     /** Tell the bouncer that keyguard is authenticated with biometrics. */
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt
index d7a4863b..7647cf6 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.bouncer.shared.flag
 
 import com.android.systemui.Flags
+import com.android.systemui.flags.RefactorFlagUtils
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 
 object ComposeBouncerFlags {
@@ -34,6 +35,18 @@
     }
 
     /**
+     * Called to ensure code is only run when the flag is enabled. This protects users from the
+     * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+     * build to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    fun isUnexpectedlyInLegacyMode() =
+        RefactorFlagUtils.isUnexpectedlyInLegacyMode(
+            isEnabled,
+            "SceneContainerFlag || ComposeBouncerFlag"
+        )
+
+    /**
      * Returns `true` if only compose bouncer is enabled and scene container framework is not
      * enabled.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerContainerViewModel.kt
index d223657..c60f932 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerContainerViewModel.kt
@@ -17,12 +17,14 @@
 package com.android.systemui.bouncer.ui.viewmodel
 
 import androidx.compose.runtime.getValue
-import com.android.keyguard.ViewMediatorCallback
 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
 import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.lifecycle.Hydrator
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.util.kotlin.Utils.Companion.sample
+import com.android.systemui.util.kotlin.sample
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import kotlinx.coroutines.coroutineScope
@@ -34,7 +36,7 @@
     private val legacyInteractor: PrimaryBouncerInteractor,
     private val authenticationInteractor: AuthenticationInteractor,
     private val selectedUserInteractor: SelectedUserInteractor,
-    private val viewMediatorCallback: ViewMediatorCallback?,
+    private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
 ) : ExclusiveActivatable() {
 
     private val hydrator = Hydrator("BouncerContainerViewModel")
@@ -45,23 +47,23 @@
     override suspend fun onActivated(): Nothing {
         coroutineScope {
             launch {
+                legacyInteractor.isShowing
+                    .sample(deviceUnlockedInteractor.deviceUnlockStatus, ::Pair)
+                    .collect { (isShowing, unlockStatus) ->
+                        if (isShowing && unlockStatus.isUnlocked) {
+                            legacyInteractor.notifyUserRequestedBouncerWhenAlreadyAuthenticated(
+                                selectedUserInteractor.getSelectedUserId()
+                            )
+                        }
+                    }
+            }
+
+            launch {
                 authenticationInteractor.onAuthenticationResult.collect { authenticationSucceeded ->
                     if (authenticationSucceeded) {
-                        // Some dismiss actions require that keyguard be dismissed right away or
-                        // deferred until something else later on dismisses keyguard (eg. end of
-                        // a hide animation).
-                        val deferKeyguardDone =
-                            legacyInteractor.bouncerDismissAction?.onDismissAction?.onDismiss()
-                        legacyInteractor.setDismissAction(null, null)
-
-                        viewMediatorCallback?.let {
-                            val selectedUserId = selectedUserInteractor.getSelectedUserId()
-                            if (deferKeyguardDone == true) {
-                                it.keyguardDonePending(selectedUserId)
-                            } else {
-                                it.keyguardDone(selectedUserId)
-                            }
-                        }
+                        legacyInteractor.notifyKeyguardAuthenticatedPrimaryAuth(
+                            selectedUserInteractor.getSelectedUserId()
+                        )
                     }
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index b570e14..a687734 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -117,7 +117,7 @@
     sceneInteractor: SceneInteractor,
     @CommunalLog logBuffer: LogBuffer,
     @CommunalTableLog tableLogBuffer: TableLogBuffer,
-    private val managedProfileController: ManagedProfileController
+    private val managedProfileController: ManagedProfileController,
 ) {
     private val logger = Logger(logBuffer, "CommunalInteractor")
 
@@ -154,7 +154,7 @@
         allOf(
                 communalSettingsInteractor.isCommunalEnabled,
                 not(keyguardInteractor.isEncryptedOrLockdown),
-                keyguardInteractor.isKeyguardShowing
+                keyguardInteractor.isKeyguardShowing,
             )
             .distinctUntilChanged()
             .onEach { available ->
@@ -342,7 +342,7 @@
     fun changeScene(
         newScene: SceneKey,
         loggingReason: String,
-        transitionKey: TransitionKey? = null
+        transitionKey: TransitionKey? = null,
     ) = communalSceneInteractor.changeScene(newScene, loggingReason, transitionKey)
 
     fun setEditModeOpen(isOpen: Boolean) {
@@ -354,9 +354,7 @@
     }
 
     /** Show the widget editor Activity. */
-    fun showWidgetEditor(
-        shouldOpenWidgetPickerOnStart: Boolean = false,
-    ) {
+    fun showWidgetEditor(shouldOpenWidgetPickerOnStart: Boolean = false) {
         communalSceneInteractor.setEditModeState(EditModeState.STARTING)
         editWidgetsActivityStarter.startActivity(shouldOpenWidgetPickerOnStart)
     }
@@ -419,7 +417,7 @@
                     IntentFilter().apply {
                         addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
                         addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)
-                    },
+                    }
             )
             .emitOnStart()
 
@@ -450,7 +448,7 @@
                             rank = widget.rank,
                             providerInfo = widget.providerInfo,
                             appWidgetHost = appWidgetHost,
-                            inQuietMode = isQuietModeEnabled(widget.providerInfo.profile)
+                            inQuietMode = isQuietModeEnabled(widget.providerInfo.profile),
                         )
                     }
                     is CommunalWidgetContentModel.Pending -> {
@@ -468,7 +466,7 @@
     /** Filter widgets based on whether their associated profile is allowed by device policy. */
     private fun filterWidgetsAllowedByDevicePolicy(
         list: List<CommunalWidgetContentModel>,
-        disallowedByDevicePolicyUser: UserInfo?
+        disallowedByDevicePolicyUser: UserInfo?,
     ): List<CommunalWidgetContentModel> =
         if (disallowedByDevicePolicyUser == null) {
             list
@@ -507,7 +505,7 @@
      * A flow of ongoing content, including smartspace timers and umo, ordered by creation time and
      * sized dynamically.
      */
-    val ongoingContent: Flow<List<CommunalContentModel.Ongoing>> =
+    fun ongoingContent(isMediaHostVisible: Boolean): Flow<List<CommunalContentModel.Ongoing>> =
         combine(smartspaceRepository.timers, mediaRepository.mediaModel) { timers, media ->
                 val ongoingContent = mutableListOf<CommunalContentModel.Ongoing>()
 
@@ -523,22 +521,20 @@
                 )
 
                 // Add UMO
-                if (media.hasAnyMediaOrRecommendation) {
+                if (isMediaHostVisible && media.hasAnyMediaOrRecommendation) {
                     ongoingContent.add(
                         CommunalContentModel.Umo(
-                            createdTimestampMillis = media.createdTimestampMillis,
+                            createdTimestampMillis = media.createdTimestampMillis
                         )
                     )
                 }
 
-                // Order by creation time descending
+                // Order by creation time descending.
                 ongoingContent.sortByDescending { it.createdTimestampMillis }
+                // Resize the items.
+                ongoingContent.resizeItems()
 
-                // Dynamic sizing
-                ongoingContent.forEachIndexed { index, model ->
-                    model.size = dynamicContentSize(ongoingContent.size, index)
-                }
-
+                // Return the sorted and resized items.
                 ongoingContent
             }
             .flowOn(bgDispatcher)
@@ -548,7 +544,7 @@
      * stale data following user deletion.
      */
     private fun filterWidgetsByExistingUsers(
-        list: List<CommunalWidgetContentModel>,
+        list: List<CommunalWidgetContentModel>
     ): List<CommunalWidgetContentModel> {
         val currentUserIds = userTracker.userProfiles.map { it.id }.toSet()
         return list.filter { widget ->
@@ -560,6 +556,40 @@
         }
     }
 
+    // Dynamically resizes the height of items in the list of ongoing items such that they fit in
+    // columns in as compact a space as possible.
+    //
+    // Currently there are three possible sizes. When the total number is 1, size for that  content
+    // is [FULL], when the total number is 2, size for each is [HALF], and 3, size for  each is
+    // [THIRD].
+    //
+    // This algorithm also respects each item's minimum size. All items in a column will have the
+    // same size, and all items in a column will be no smaller than any item's minimum size.
+    private fun List<CommunalContentModel.Ongoing>.resizeItems() {
+        fun resizeColumn(c: List<CommunalContentModel.Ongoing>) {
+            if (c.isEmpty()) return
+            val newSize = CommunalContentSize.toSize(span = FULL.span / c.size)
+            c.forEach { item -> item.size = newSize }
+        }
+
+        val column = mutableListOf<CommunalContentModel.Ongoing>()
+        var available = FULL.span
+
+        forEach { item ->
+            if (available < item.minSize.span) {
+                resizeColumn(column)
+                column.clear()
+                available = FULL.span
+            }
+
+            column.add(item)
+            available -= item.minSize.span
+        }
+
+        // Make sure to resize the final column.
+        resizeColumn(column)
+    }
+
     companion object {
         const val TAG = "CommunalInteractor"
 
@@ -574,31 +604,6 @@
          * of -1 means that the user's chosen screen timeout will be used instead.
          */
         const val AWAKE_INTERVAL_MS = -1
-
-        /**
-         * Calculates the content size dynamically based on the total number of contents of that
-         * type.
-         *
-         * Contents with the same type are expected to fill each column evenly. Currently there are
-         * three possible sizes. When the total number is 1, size for that content is [FULL], when
-         * the total number is 2, size for each is [HALF], and 3, size for each is [THIRD].
-         *
-         * When dynamic contents fill in multiple columns, the first column follows the algorithm
-         * above, and the remaining contents are packed in [THIRD]s. For example, when the total
-         * number if 4, the first one is [FULL], filling the column, and the remaining 3 are
-         * [THIRD].
-         *
-         * @param size The total number of contents of this type.
-         * @param index The index of the current content of this type.
-         */
-        private fun dynamicContentSize(size: Int, index: Int): CommunalContentSize {
-            val remainder = size % CommunalContentSize.entries.size
-            return CommunalContentSize.toSize(
-                span =
-                    FULL.span /
-                        if (index > remainder - 1) CommunalContentSize.entries.size else remainder
-            )
-        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
index 4c821d4..c2f6e85 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
@@ -34,12 +34,18 @@
     /** Size to be rendered in the grid. */
     val size: CommunalContentSize
 
+    /** The minimum size content can be resized to. */
+    val minSize: CommunalContentSize
+        get() = CommunalContentSize.HALF
+
     /**
      * A type of communal content is ongoing / live / ephemeral, and can be sized and ordered
      * dynamically.
      */
     sealed interface Ongoing : CommunalContentModel {
         override var size: CommunalContentSize
+        override val minSize
+            get() = CommunalContentSize.THIRD
 
         /** Timestamp in milliseconds of when the content was created. */
         val createdTimestampMillis: Long
@@ -72,7 +78,7 @@
         data class DisabledWidget(
             override val appWidgetId: Int,
             override val rank: Int,
-            val providerInfo: AppWidgetProviderInfo
+            val providerInfo: AppWidgetProviderInfo,
         ) : WidgetContent {
             override val key = KEY.disabledWidget(appWidgetId)
             override val componentName: ComponentName = providerInfo.provider
@@ -109,10 +115,7 @@
         override val size = CommunalContentSize.HALF
     }
 
-    class Tutorial(
-        id: Int,
-        override var size: CommunalContentSize,
-    ) : CommunalContentModel {
+    class Tutorial(id: Int, override var size: CommunalContentSize) : CommunalContentModel {
         override val key = KEY.tutorial(id)
     }
 
@@ -128,6 +131,7 @@
     class Umo(
         override val createdTimestampMillis: Long,
         override var size: CommunalContentSize = CommunalContentSize.HALF,
+        override var minSize: CommunalContentSize = CommunalContentSize.HALF,
     ) : Ongoing {
         override val key = KEY.umo()
     }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index d69ba1b..53109ac 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -125,15 +125,9 @@
     private var frozenCommunalContent: List<CommunalContentModel>? = null
 
     private val ongoingContent =
-        combine(
-            isMediaHostVisible,
-            communalInteractor.ongoingContent.onEach { mediaHost.updateViewVisibility() }
-        ) { mediaVisible, ongoingContent ->
-            if (mediaVisible) {
-                ongoingContent
-            } else {
-                // Media is not visible, don't show UMO
-                ongoingContent.filterNot { it is CommunalContentModel.Umo }
+        isMediaHostVisible.flatMapLatest { isMediaHostVisible ->
+            communalInteractor.ongoingContent(isMediaHostVisible).onEach {
+                mediaHost.updateViewVisibility()
             }
         }
 
@@ -148,8 +142,7 @@
                     ongoingContent,
                     communalInteractor.widgetContent,
                     communalInteractor.ctaTileContent,
-                ) { ongoing, widgets, ctaTile,
-                    ->
+                ) { ongoing, widgets, ctaTile ->
                     ongoing + widgets + ctaTile
                 }
             }
@@ -172,10 +165,10 @@
         allOf(
                 keyguardTransitionInteractor.isFinishedIn(
                     scene = Scenes.Communal,
-                    stateWithoutSceneContainer = KeyguardState.GLANCEABLE_HUB
+                    stateWithoutSceneContainer = KeyguardState.GLANCEABLE_HUB,
                 ),
                 keyguardInteractor.isKeyguardOccluded,
-                not(keyguardInteractor.isAbleToDream)
+                not(keyguardInteractor.isAbleToDream),
             )
             .distinctUntilChanged()
             .onEach { logger.d("isCommunalContentFlowFrozen: $it") }
@@ -208,7 +201,7 @@
         combine(
                 keyguardTransitionInteractor.isFinishedIn(
                     scene = Scenes.Communal,
-                    stateWithoutSceneContainer = KeyguardState.GLANCEABLE_HUB
+                    stateWithoutSceneContainer = KeyguardState.GLANCEABLE_HUB,
                 ),
                 communalInteractor.isIdleOnCommunal,
                 shadeInteractor.isAnyFullyExpanded,
@@ -221,7 +214,7 @@
         object : View.AccessibilityDelegate() {
             override fun onInitializeAccessibilityNodeInfo(
                 host: View,
-                info: AccessibilityNodeInfo
+                info: AccessibilityNodeInfo,
             ) {
                 super.onInitializeAccessibilityNodeInfo(host, info)
                 // Hint user to long press in order to enter edit mode
@@ -230,7 +223,7 @@
                         AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK.id,
                         resources
                             .getString(R.string.accessibility_action_label_edit_widgets)
-                            .lowercase()
+                            .lowercase(),
                     )
                 )
             }
@@ -238,7 +231,7 @@
             override fun performAccessibilityAction(
                 host: View,
                 action: Int,
-                args: Bundle?
+                args: Bundle?,
             ): Boolean {
                 when (action) {
                     AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK.id -> {
@@ -271,9 +264,7 @@
         }
     }
 
-    override fun onOpenWidgetEditor(
-        shouldOpenWidgetPickerOnStart: Boolean,
-    ) {
+    override fun onOpenWidgetEditor(shouldOpenWidgetPickerOnStart: Boolean) {
         persistScrollPosition()
         communalInteractor.showWidgetEditor(shouldOpenWidgetPickerOnStart)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt b/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt
index 0e39a99..ec03227 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt
@@ -21,8 +21,10 @@
 import android.util.SizeF
 import com.android.app.tracing.coroutines.withContext
 import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.widgets.AppWidgetHostListenerDelegate
 import com.android.systemui.communal.widgets.CommunalAppWidgetHost
 import com.android.systemui.communal.widgets.CommunalAppWidgetHostView
+import com.android.systemui.communal.widgets.WidgetInteractionHandler
 import com.android.systemui.dagger.qualifiers.UiBackground
 import javax.inject.Inject
 import kotlin.coroutines.CoroutineContext
@@ -33,6 +35,8 @@
 constructor(
     @UiBackground private val uiBgContext: CoroutineContext,
     private val appWidgetHost: CommunalAppWidgetHost,
+    private val interactionHandler: WidgetInteractionHandler,
+    private val listenerFactory: AppWidgetHostListenerDelegate.Factory,
 ) {
     suspend fun createWidget(
         context: Context,
@@ -40,18 +44,20 @@
         size: SizeF,
     ): CommunalAppWidgetHostView =
         withContext("$TAG#createWidget", uiBgContext) {
-            appWidgetHost
-                .createViewForCommunal(context, model.appWidgetId, model.providerInfo)
-                .apply {
-                    updateAppWidgetSize(
-                        /* newOptions = */ Bundle(),
-                        /* minWidth = */ size.width.toInt(),
-                        /* minHeight = */ size.height.toInt(),
-                        /* maxWidth = */ size.width.toInt(),
-                        /* maxHeight = */ size.height.toInt(),
-                        /* ignorePadding = */ true,
-                    )
-                }
+            val view = CommunalAppWidgetHostView(context, interactionHandler)
+            view.setAppWidget(model.appWidgetId, model.providerInfo)
+            // Instead of setting the view as the listener directly, we wrap the view in a delegate
+            // which ensures the callbacks always get called on the main thread.
+            appWidgetHost.setListener(model.appWidgetId, listenerFactory.create(view))
+            view.updateAppWidgetSize(
+                /* newOptions = */ Bundle(),
+                /* minWidth = */ size.width.toInt(),
+                /* minHeight = */ size.height.toInt(),
+                /* maxWidth = */ size.width.toInt(),
+                /* maxHeight = */ size.height.toInt(),
+                /* ignorePadding = */ true,
+            )
+            view
         }
 
     private companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/AppWidgetHostListenerDelegate.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/AppWidgetHostListenerDelegate.kt
new file mode 100644
index 0000000..f341621
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/AppWidgetHostListenerDelegate.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.widgets
+
+import android.appwidget.AppWidgetHost.AppWidgetHostListener
+import android.appwidget.AppWidgetProviderInfo
+import android.widget.RemoteViews
+import com.android.systemui.dagger.qualifiers.Main
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.util.concurrent.Executor
+
+/**
+ * Wrapper for an [AppWidgetHostListener] to ensure the callbacks are executed on the main thread.
+ */
+class AppWidgetHostListenerDelegate
+@AssistedInject
+constructor(
+    @Main private val mainExecutor: Executor,
+    @Assisted private val listener: AppWidgetHostListener,
+) : AppWidgetHostListener {
+
+    @AssistedFactory
+    interface Factory {
+        fun create(listener: AppWidgetHostListener): AppWidgetHostListenerDelegate
+    }
+
+    override fun onUpdateProviderInfo(appWidget: AppWidgetProviderInfo?) {
+        mainExecutor.execute { listener.onUpdateProviderInfo(appWidget) }
+    }
+
+    override fun updateAppWidget(views: RemoteViews?) {
+        mainExecutor.execute { listener.updateAppWidget(views) }
+    }
+
+    override fun onViewDataChanged(viewId: Int) {
+        mainExecutor.execute { listener.onViewDataChanged(viewId) }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt
index 10a565f..b46698e 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt
@@ -17,11 +17,7 @@
 package com.android.systemui.communal.widgets
 
 import android.appwidget.AppWidgetHost
-import android.appwidget.AppWidgetHostView
-import android.appwidget.AppWidgetProviderInfo
 import android.content.Context
-import android.os.Looper
-import android.widget.RemoteViews
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.Logger
 import javax.annotation.concurrent.GuardedBy
@@ -36,11 +32,8 @@
     context: Context,
     private val backgroundScope: CoroutineScope,
     hostId: Int,
-    private val interactionHandler: RemoteViews.InteractionHandler,
-    looper: Looper,
     logBuffer: LogBuffer,
-) : AppWidgetHost(context, hostId, interactionHandler, looper) {
-
+) : AppWidgetHost(context, hostId) {
     private val logger = Logger(logBuffer, TAG)
 
     private val _appWidgetIdToRemove = MutableSharedFlow<Int>()
@@ -50,29 +43,6 @@
 
     @GuardedBy("observers") private val observers = mutableSetOf<Observer>()
 
-    override fun onCreateView(
-        context: Context,
-        appWidgetId: Int,
-        appWidget: AppWidgetProviderInfo?
-    ): AppWidgetHostView {
-        return CommunalAppWidgetHostView(context, interactionHandler)
-    }
-
-    /**
-     * Creates and returns a [CommunalAppWidgetHostView]. This method does the same thing as
-     * `createView`. The only difference is that the returned value will be casted to
-     * [CommunalAppWidgetHostView].
-     */
-    fun createViewForCommunal(
-        context: Context?,
-        appWidgetId: Int,
-        appWidget: AppWidgetProviderInfo?
-    ): CommunalAppWidgetHostView {
-        // `createView` internally calls `onCreateView` to create the view. We cannot override
-        // `createView`, but we are sure that the hostView is `CommunalAppWidgetHostView`
-        return createView(context, appWidgetId, appWidget) as CommunalAppWidgetHostView
-    }
-
     override fun onAppWidgetRemoved(appWidgetId: Int) {
         backgroundScope.launch {
             logger.i({ "App widget removed from system: $int1" }) { int1 = appWidgetId }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetModule.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetModule.kt
index 684303ae..f4962085 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetModule.kt
@@ -20,7 +20,6 @@
 import android.appwidget.AppWidgetManager
 import android.content.Context
 import android.content.res.Resources
-import android.os.Looper
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
@@ -46,18 +45,9 @@
         fun provideCommunalAppWidgetHost(
             @Application context: Context,
             @Background backgroundScope: CoroutineScope,
-            interactionHandler: WidgetInteractionHandler,
-            @Main looper: Looper,
             @CommunalLog logBuffer: LogBuffer,
         ): CommunalAppWidgetHost {
-            return CommunalAppWidgetHost(
-                context,
-                backgroundScope,
-                APP_WIDGET_HOST_ID,
-                interactionHandler,
-                looper,
-                logBuffer,
-            )
+            return CommunalAppWidgetHost(context, backgroundScope, APP_WIDGET_HOST_ID, logBuffer)
         }
 
         @SysUISingleton
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 1f5878b..a327e4a 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
@@ -28,7 +28,6 @@
 import android.view.Display
 import com.android.app.tracing.FlowTracing.traceEach
 import com.android.app.tracing.traceSection
-import com.android.systemui.Flags
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
@@ -51,7 +50,6 @@
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.scan
-import kotlinx.coroutines.flow.shareIn
 import kotlinx.coroutines.flow.stateIn
 
 /** Provides a [Flow] of [Display] as returned by [DisplayManager]. */
@@ -102,7 +100,7 @@
     private val displayManager: DisplayManager,
     @Background backgroundHandler: Handler,
     @Background bgApplicationScope: CoroutineScope,
-    @Background backgroundCoroutineDispatcher: CoroutineDispatcher
+    @Background backgroundCoroutineDispatcher: CoroutineDispatcher,
 ) : DisplayRepository {
     private val allDisplayEvents: Flow<DisplayEvent> =
         conflatedCallbackFlow {
@@ -139,70 +137,55 @@
     override val displayAdditionEvent: Flow<Display?> =
         allDisplayEvents.filterIsInstance<DisplayEvent.Added>().map { getDisplay(it.displayId) }
 
-    // TODO: b/345472038 - Delete after the flag is ramped up.
-    private val oldEnabledDisplays: Flow<Set<Display>> =
-        allDisplayEvents
-            .map { getDisplays() }
-            .shareIn(bgApplicationScope, started = SharingStarted.WhileSubscribed(), replay = 1)
+    // 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).
+    private val initialDisplays: Set<Display> =
+        traceSection("$TAG#initialDisplays") { displayManager.displays?.toSet() ?: emptySet() }
+    private val initialDisplayIds = initialDisplays.map { display -> display.displayId }.toSet()
 
     /** Propagate to the listeners only enabled displays */
     private val enabledDisplayIds: Flow<Set<Int>> =
-        if (Flags.enableEfficientDisplayRepository()) {
-                allDisplayEvents
-                    .scan(initial = emptySet()) { previousIds: Set<Int>, event: DisplayEvent ->
-                        val id = event.displayId
-                        when (event) {
-                            is DisplayEvent.Removed -> previousIds - id
-                            is DisplayEvent.Added,
-                            is DisplayEvent.Changed -> previousIds + id
-                        }
-                    }
-                    .distinctUntilChanged()
-                    .stateIn(
-                        bgApplicationScope,
-                        SharingStarted.WhileSubscribed(),
-                        // 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 ->
-                    enabledDisplaysSet.map { it.displayId }.toSet()
+        allDisplayEvents
+            .scan(initial = initialDisplayIds) { previousIds: Set<Int>, event: DisplayEvent ->
+                val id = event.displayId
+                when (event) {
+                    is DisplayEvent.Removed -> previousIds - id
+                    is DisplayEvent.Added,
+                    is DisplayEvent.Changed -> previousIds + id
                 }
             }
+            .distinctUntilChanged()
+            .stateIn(bgApplicationScope, SharingStarted.WhileSubscribed(), initialDisplayIds)
             .debugLog("enabledDisplayIds")
 
     private val defaultDisplay by lazy {
         getDisplay(Display.DEFAULT_DISPLAY) ?: error("Unable to get default display.")
     }
+
     /**
      * Represents displays that went though the [DisplayListener.onDisplayAdded] callback.
      *
      * Those are commonly the ones provided by [DisplayManager.getDisplays] by default.
      */
     private val enabledDisplays: Flow<Set<Display>> =
-        if (Flags.enableEfficientDisplayRepository()) {
-            enabledDisplayIds
-                .mapElementsLazily { displayId -> getDisplay(displayId) }
-                .onEach {
-                    if (it.isEmpty()) Log.wtf(TAG, "No enabled displays. This should never happen.")
-                }
-                .flowOn(backgroundCoroutineDispatcher)
-                .debugLog("enabledDisplays")
-                .stateIn(
-                    bgApplicationScope,
-                    started = SharingStarted.WhileSubscribed(),
-                    // 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
-        }
+        enabledDisplayIds
+            .mapElementsLazily { displayId -> getDisplay(displayId) }
+            .onEach {
+                if (it.isEmpty()) Log.wtf(TAG, "No enabled displays. This should never happen.")
+            }
+            .flowOn(backgroundCoroutineDispatcher)
+            .debugLog("enabledDisplays")
+            .stateIn(
+                bgApplicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                // 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 = initialDisplays,
+            )
 
     /**
      * Represents displays that went though the [DisplayListener.onDisplayAdded] callback.
@@ -211,10 +194,7 @@
      */
     override val displays: Flow<Set<Display>> = enabledDisplays
 
-    private fun getDisplays(): Set<Display> =
-        traceSection("$TAG#getDisplays()") { displayManager.displays?.toSet() ?: emptySet() }
-
-    private val _ignoredDisplayIds = MutableStateFlow<Set<Int>>(emptySet())
+    val _ignoredDisplayIds = MutableStateFlow<Set<Int>>(emptySet())
     private val ignoredDisplayIds: Flow<Set<Int>> = _ignoredDisplayIds.debugLog("ignoredDisplayIds")
 
     private fun getInitialConnectedDisplays(): Set<Int> =
@@ -271,7 +251,7 @@
                 // the flow starts being collected. This is to ensure the call to get displays (an
                 // IPC) happens in the background instead of when this object
                 // is instantiated.
-                initialValue = emptySet()
+                initialValue = emptySet(),
             )
 
     private val connectedExternalDisplayIds: Flow<Set<Int>> =
@@ -308,7 +288,7 @@
                         TAG,
                         "combining enabled=$enabledDisplaysIds, " +
                             "connectedExternalDisplayIds=$connectedExternalDisplayIds, " +
-                            "ignored=$ignoredDisplayIds"
+                            "ignored=$ignoredDisplayIds",
                     )
                 }
                 connectedExternalDisplayIds - enabledDisplaysIds - ignoredDisplayIds
@@ -382,7 +362,7 @@
             val previousSet: Set<T>,
             // Caches T values from the previousSet that were already converted to V
             val valueMap: Map<T, V>,
-            val resultSet: Set<V>
+            val resultSet: Set<V>,
         )
 
         val emptyInitialState = State(emptySet<T>(), emptyMap(), emptySet<V>())
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
index 81ea2e7..62720a5 100644
--- a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
@@ -79,7 +79,7 @@
             .combine(concurrentDisplaysInProgessFlow) { pendingDisplay, concurrentDisplaysInProgress
                 ->
                 if (pendingDisplay == null) {
-                    hideDialog()
+                    dismissDialog()
                 } else {
                     showDialog(pendingDisplay, concurrentDisplaysInProgress)
                 }
@@ -88,17 +88,17 @@
     }
 
     private fun showDialog(pendingDisplay: PendingDisplay, concurrentDisplaysInProgess: Boolean) {
-        hideDialog()
+        dismissDialog()
         dialog =
             bottomSheetFactory
                 .createDialog(
                     onStartMirroringClickListener = {
                         scope.launch(bgDispatcher) { pendingDisplay.enable() }
-                        hideDialog()
+                        dismissDialog()
                     },
                     onCancelMirroring = {
                         scope.launch(bgDispatcher) { pendingDisplay.ignore() }
-                        hideDialog()
+                        dismissDialog()
                     },
                     navbarBottomInsetsProvider = { Utils.getNavbarInsets(context).bottom },
                     showConcurrentDisplayInfo = concurrentDisplaysInProgess
@@ -106,8 +106,8 @@
                 .apply { show() }
     }
 
-    private fun hideDialog() {
-        dialog?.hide()
+    private fun dismissDialog() {
+        dialog?.dismiss()
         dialog = null
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index beec348..11a0543 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
@@ -144,7 +144,7 @@
                 useSinglePane,
                 onSearchQueryChanged,
                 modifier,
-                onKeyboardSettingsClicked
+                onKeyboardSettingsClicked,
             )
         }
         else -> {
@@ -159,7 +159,7 @@
     useSinglePane: @Composable () -> Boolean,
     onSearchQueryChanged: (String) -> Unit,
     modifier: Modifier,
-    onKeyboardSettingsClicked: () -> Unit
+    onKeyboardSettingsClicked: () -> Unit,
 ) {
     var selectedCategoryType by
         remember(shortcutsUiState.defaultSelectedCategory) {
@@ -183,7 +183,7 @@
             shortcutsUiState.shortcutCategories,
             selectedCategoryType,
             onCategorySelected = { selectedCategoryType = it },
-            onKeyboardSettingsClicked
+            onKeyboardSettingsClicked,
         )
     }
 }
@@ -223,14 +223,14 @@
                 searchQuery,
                 categories,
                 selectedCategoryType,
-                onCategorySelected
+                onCategorySelected,
             )
             Spacer(modifier = Modifier.weight(1f))
         }
         KeyboardSettings(
             horizontalPadding = 16.dp,
             verticalPadding = 32.dp,
-            onClick = onKeyboardSettingsClicked
+            onClick = onKeyboardSettingsClicked,
         )
     }
 }
@@ -282,11 +282,7 @@
     onClick: () -> Unit,
     shape: Shape,
 ) {
-    Surface(
-        color = MaterialTheme.colorScheme.surfaceBright,
-        shape = shape,
-        onClick = onClick,
-    ) {
+    Surface(color = MaterialTheme.colorScheme.surfaceBright, shape = shape, onClick = onClick) {
         Column {
             Row(
                 verticalAlignment = Alignment.CenterVertically,
@@ -327,7 +323,7 @@
     source: IconSource,
     modifier: Modifier = Modifier,
     contentDescription: String? = null,
-    tint: Color = LocalContentColor.current
+    tint: Color = LocalContentColor.current,
 ) {
     if (source.imageVector != null) {
         Icon(source.imageVector, contentDescription, modifier, tint)
@@ -350,7 +346,7 @@
 
 private fun getApplicationLabelForCurrentApp(
     type: ShortcutCategoryType.CurrentApp,
-    context: Context
+    context: Context,
 ): String {
     val packageManagerForUser = CentralSurfaces.getPackageManagerForUser(context, context.userId)
     return try {
@@ -358,7 +354,7 @@
             packageManagerForUser.getApplicationInfoAsUser(
                 type.packageName,
                 /* flags = */ 0,
-                context.userId
+                context.userId,
             )
         packageManagerForUser.getApplicationLabel(currentAppInfo).toString()
     } catch (e: NameNotFoundException) {
@@ -377,13 +373,13 @@
                 } else {
                     0f
                 },
-            label = "Expand icon rotation animation"
+            label = "Expand icon rotation animation",
         )
     Icon(
         modifier =
             Modifier.background(
                     color = MaterialTheme.colorScheme.surfaceContainerHigh,
-                    shape = CircleShape
+                    shape = CircleShape,
                 )
                 .graphicsLayer { rotationZ = expandIconRotationDegrees },
         imageVector = Icons.Default.ExpandMore,
@@ -393,7 +389,7 @@
             } else {
                 stringResource(R.string.shortcut_helper_content_description_expand_icon)
             },
-        tint = MaterialTheme.colorScheme.onSurface
+        tint = MaterialTheme.colorScheme.onSurface,
     )
 }
 
@@ -435,11 +431,11 @@
         Row(Modifier.fillMaxWidth()) {
             StartSidePanel(
                 onSearchQueryChanged = onSearchQueryChanged,
-                modifier = Modifier.width(200.dp),
+                modifier = Modifier.width(240.dp),
                 categories = categories,
                 onKeyboardSettingsClicked = onKeyboardSettingsClicked,
                 selectedCategory = selectedCategoryType,
-                onCategoryClicked = { onCategorySelected(it.type) }
+                onCategoryClicked = { onCategorySelected(it.type) },
             )
             Spacer(modifier = Modifier.width(24.dp))
             EndSidePanel(searchQuery, Modifier.fillMaxSize().padding(top = 8.dp), selectedCategory)
@@ -475,7 +471,7 @@
             modifier
                 .padding(vertical = 8.dp)
                 .background(MaterialTheme.colorScheme.surfaceBright, RoundedCornerShape(28.dp))
-                .padding(horizontal = horizontalPadding, vertical = 24.dp)
+                .padding(horizontal = horizontalPadding, vertical = 24.dp),
     )
 }
 
@@ -484,7 +480,7 @@
     Surface(
         modifier = Modifier.fillMaxWidth(),
         shape = RoundedCornerShape(28.dp),
-        color = MaterialTheme.colorScheme.surfaceBright
+        color = MaterialTheme.colorScheme.surfaceBright,
     ) {
         Column(Modifier.padding(24.dp)) {
             SubCategoryTitle(subCategory.label)
@@ -519,7 +515,7 @@
                 isFocused = isFocused,
                 focusColor = MaterialTheme.colorScheme.secondary,
                 padding = 8.dp,
-                cornerRadius = 16.dp
+                cornerRadius = 16.dp,
             )
     ) {
         Row(
@@ -528,21 +524,12 @@
             verticalAlignment = Alignment.CenterVertically,
         ) {
             if (shortcut.icon != null) {
-                ShortcutIcon(
-                    shortcut.icon,
-                    modifier = Modifier.size(24.dp),
-                )
+                ShortcutIcon(shortcut.icon, modifier = Modifier.size(24.dp))
             }
-            ShortcutDescriptionText(
-                searchQuery = searchQuery,
-                shortcut = shortcut,
-            )
+            ShortcutDescriptionText(searchQuery = searchQuery, shortcut = shortcut)
         }
         Spacer(modifier = Modifier.width(16.dp))
-        ShortcutKeyCombinations(
-            modifier = Modifier.weight(1f),
-            shortcut = shortcut,
-        )
+        ShortcutKeyCombinations(modifier = Modifier.weight(1f), shortcut = shortcut)
     }
 }
 
@@ -566,14 +553,11 @@
 
 @OptIn(ExperimentalLayoutApi::class)
 @Composable
-private fun ShortcutKeyCombinations(
-    modifier: Modifier = Modifier,
-    shortcut: Shortcut,
-) {
+private fun ShortcutKeyCombinations(modifier: Modifier = Modifier, shortcut: Shortcut) {
     FlowRow(
         modifier = modifier,
         verticalArrangement = Arrangement.spacedBy(8.dp),
-        horizontalArrangement = Arrangement.End
+        horizontalArrangement = Arrangement.End,
     ) {
         shortcut.commands.forEachIndexed { index, command ->
             if (index > 0) {
@@ -609,8 +593,8 @@
             Modifier.height(36.dp)
                 .background(
                     color = MaterialTheme.colorScheme.surfaceContainer,
-                    shape = RoundedCornerShape(12.dp)
-                ),
+                    shape = RoundedCornerShape(12.dp),
+                )
     ) {
         shortcutKeyContent()
     }
@@ -630,7 +614,7 @@
     Icon(
         painter = painterResource(key.drawableResId),
         contentDescription = null,
-        modifier = Modifier.align(Alignment.Center).padding(6.dp)
+        modifier = Modifier.align(Alignment.Center).padding(6.dp),
     )
 }
 
@@ -701,7 +685,7 @@
         KeyboardSettings(
             horizontalPadding = 24.dp,
             verticalPadding = 24.dp,
-            onKeyboardSettingsClicked
+            onKeyboardSettingsClicked,
         )
     }
 }
@@ -710,7 +694,7 @@
 private fun CategoriesPanelTwoPane(
     categories: List<ShortcutCategory>,
     selectedCategory: ShortcutCategoryType?,
-    onCategoryClicked: (ShortcutCategory) -> Unit
+    onCategoryClicked: (ShortcutCategory) -> Unit,
 ) {
     Column {
         categories.fastForEach {
@@ -718,7 +702,7 @@
                 label = it.label(LocalContext.current),
                 iconSource = it.icon,
                 selected = selectedCategory == it.type,
-                onClick = { onCategoryClicked(it) }
+                onClick = { onCategoryClicked(it) },
             )
         }
     }
@@ -747,7 +731,7 @@
                     isFocused = isFocused,
                     focusColor = MaterialTheme.colorScheme.secondary,
                     padding = 2.dp,
-                    cornerRadius = 33.dp
+                    cornerRadius = 33.dp,
                 ),
         shape = RoundedCornerShape(28.dp),
         color = colors.containerColor(selected).value,
@@ -758,7 +742,7 @@
                 modifier = Modifier.size(24.dp),
                 source = iconSource,
                 contentDescription = null,
-                tint = colors.iconColor(selected).value
+                tint = colors.iconColor(selected).value,
             )
             Spacer(Modifier.width(12.dp))
             Box(Modifier.weight(1f)) {
@@ -766,7 +750,7 @@
                     fontSize = 18.sp,
                     color = colors.textColor(selected).value,
                     style = MaterialTheme.typography.headlineSmall,
-                    text = label
+                    text = label,
                 )
             }
         }
@@ -777,7 +761,7 @@
     isFocused: Boolean,
     focusColor: Color,
     padding: Dp,
-    cornerRadius: Dp
+    cornerRadius: Dp,
 ): Modifier {
     if (isFocused) {
         return this.drawWithContent {
@@ -795,7 +779,7 @@
                     style = Stroke(width = 3.dp.toPx()),
                     topLeft = focusOutline.topLeft,
                     size = focusOutline.size,
-                    cornerRadius = CornerRadius(cornerRadius.toPx())
+                    cornerRadius = CornerRadius(cornerRadius.toPx()),
                 )
             }
             // Increasing Z-Index so focus outline is drawn on top of "selected" category
@@ -815,9 +799,9 @@
             Text(
                 text = stringResource(R.string.shortcut_helper_title),
                 color = MaterialTheme.colorScheme.onSurface,
-                style = MaterialTheme.typography.headlineSmall
+                style = MaterialTheme.typography.headlineSmall,
             )
-        }
+        },
     )
 }
 
@@ -852,7 +836,7 @@
         onSearch = {},
         leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) },
         placeholder = { Text(text = stringResource(R.string.shortcut_helper_search_placeholder)) },
-        content = {}
+        content = {},
     )
 }
 
@@ -874,21 +858,21 @@
                         isFocused = isFocused,
                         focusColor = MaterialTheme.colorScheme.secondary,
                         padding = 8.dp,
-                        cornerRadius = 28.dp
+                        cornerRadius = 28.dp,
                     ),
-            verticalAlignment = Alignment.CenterVertically
+            verticalAlignment = Alignment.CenterVertically,
         ) {
             Text(
                 "Keyboard Settings",
                 color = MaterialTheme.colorScheme.onSurfaceVariant,
-                fontSize = 16.sp
+                fontSize = 16.sp,
             )
             Spacer(modifier = Modifier.weight(1f))
             Icon(
                 imageVector = Icons.AutoMirrored.Default.OpenInNew,
                 contentDescription = null,
                 tint = MaterialTheme.colorScheme.onSurfaceVariant,
-                modifier = Modifier.size(24.dp)
+                modifier = Modifier.size(24.dp),
             )
         }
     }
@@ -900,17 +884,15 @@
         val singlePaneFirstCategory =
             RoundedCornerShape(
                 topStart = Dimensions.SinglePaneCategoryCornerRadius,
-                topEnd = Dimensions.SinglePaneCategoryCornerRadius
+                topEnd = Dimensions.SinglePaneCategoryCornerRadius,
             )
         val singlePaneLastCategory =
             RoundedCornerShape(
                 bottomStart = Dimensions.SinglePaneCategoryCornerRadius,
-                bottomEnd = Dimensions.SinglePaneCategoryCornerRadius
+                bottomEnd = Dimensions.SinglePaneCategoryCornerRadius,
             )
         val singlePaneSingleCategory =
-            RoundedCornerShape(
-                size = Dimensions.SinglePaneCategoryCornerRadius,
-            )
+            RoundedCornerShape(size = Dimensions.SinglePaneCategoryCornerRadius)
         val singlePaneCategory = RectangleShape
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt
index 2039743..799999a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt
@@ -39,6 +39,7 @@
 import com.android.systemui.keyboard.shortcut.ui.viewmodel.ShortcutHelperViewModel
 import com.android.systemui.res.R
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.dpToPx
 import com.google.android.material.bottomsheet.BottomSheetBehavior
 import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback
 import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_HIDDEN
@@ -51,10 +52,8 @@
  */
 class ShortcutHelperActivity
 @Inject
-constructor(
-    private val userTracker: UserTracker,
-    private val viewModel: ShortcutHelperViewModel,
-) : ComponentActivity() {
+constructor(private val userTracker: UserTracker, private val viewModel: ShortcutHelperViewModel) :
+    ComponentActivity() {
 
     private val bottomSheetContainer
         get() = requireViewById<View>(R.id.shortcut_helper_sheet_container)
@@ -69,7 +68,7 @@
         setupEdgeToEdge()
         super.onCreate(savedInstanceState)
         setContentView(R.layout.activity_keyboard_shortcut_helper)
-        setUpBottomSheetWidth()
+        setUpWidth()
         expandBottomSheet()
         setUpInsets()
         setUpPredictiveBack()
@@ -80,6 +79,13 @@
         viewModel.onViewOpened()
     }
 
+    private fun setUpWidth() {
+        // we override this because when maxWidth isn't specified, material imposes a max width
+        // constraint on bottom sheets on larger screens which is smaller than our desired width.
+        bottomSheetBehavior.maxWidth =
+            resources.getDimension(R.dimen.shortcut_helper_width).dpToPx(resources).toInt()
+    }
+
     private fun setUpComposeView() {
         requireViewById<ComposeView>(R.id.shortcut_helper_compose_container).apply {
             setContent {
@@ -102,7 +108,7 @@
         try {
             startActivityAsUser(
                 Intent(Settings.ACTION_HARD_KEYBOARD_SETTINGS),
-                userTracker.userHandle
+                userTracker.userHandle,
             )
         } catch (e: ActivityNotFoundException) {
             // From the Settings docs: In some cases, a matching Activity may not exist, so ensure
@@ -133,15 +139,6 @@
         window.setDecorFitsSystemWindows(false)
     }
 
-    private fun setUpBottomSheetWidth() {
-        val sheetScreenWidthFraction =
-            resources.getFloat(R.dimen.shortcut_helper_screen_width_fraction)
-        // maxWidth needs to be set before the sheet is drawn, otherwise the call will have no
-        // effect.
-        val screenWidth = windowManager.maximumWindowMetrics.bounds.width()
-        bottomSheetBehavior.maxWidth = (sheetScreenWidthFraction * screenWidth).toInt()
-    }
-
     private fun setUpInsets() {
         bottomSheetContainer.setOnApplyWindowInsetsListener { _, insets ->
             val safeDrawingInsets = insets.safeDrawing
@@ -153,7 +150,7 @@
             bottomSheet.updatePadding(
                 left = safeDrawingInsets.left,
                 right = safeDrawingInsets.right,
-                bottom = safeDrawingInsets.bottom
+                bottom = safeDrawingInsets.bottom,
             )
             // The bottom sheet has to be expanded only after setting up insets, otherwise there is
             // a bug and it will not use full height.
@@ -191,7 +188,7 @@
             }
         onBackPressedDispatcher.addCallback(
             owner = this,
-            onBackPressedCallback = onBackPressedCallback
+            onBackPressedCallback = onBackPressedCallback,
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
index 342325f..6df8355 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
@@ -54,29 +54,25 @@
             addURI(
                 Contract.AUTHORITY,
                 Contract.LockScreenQuickAffordances.qualifiedTablePath(
-                    Contract.LockScreenQuickAffordances.SlotTable.TABLE_NAME,
+                    Contract.LockScreenQuickAffordances.SlotTable.TABLE_NAME
                 ),
                 MATCH_CODE_ALL_SLOTS,
             )
             addURI(
                 Contract.AUTHORITY,
                 Contract.LockScreenQuickAffordances.qualifiedTablePath(
-                    Contract.LockScreenQuickAffordances.AffordanceTable.TABLE_NAME,
+                    Contract.LockScreenQuickAffordances.AffordanceTable.TABLE_NAME
                 ),
                 MATCH_CODE_ALL_AFFORDANCES,
             )
             addURI(
                 Contract.AUTHORITY,
                 Contract.LockScreenQuickAffordances.qualifiedTablePath(
-                    Contract.LockScreenQuickAffordances.SelectionTable.TABLE_NAME,
+                    Contract.LockScreenQuickAffordances.SelectionTable.TABLE_NAME
                 ),
                 MATCH_CODE_ALL_SELECTIONS,
             )
-            addURI(
-                Contract.AUTHORITY,
-                Contract.FlagsTable.TABLE_NAME,
-                MATCH_CODE_ALL_FLAGS,
-            )
+            addURI(Contract.AUTHORITY, Contract.FlagsTable.TABLE_NAME, MATCH_CODE_ALL_FLAGS)
         }
 
     override fun onCreate(): Boolean {
@@ -106,15 +102,15 @@
             when (uriMatcher.match(uri)) {
                 MATCH_CODE_ALL_SLOTS ->
                     Contract.LockScreenQuickAffordances.qualifiedTablePath(
-                        Contract.LockScreenQuickAffordances.SlotTable.TABLE_NAME,
+                        Contract.LockScreenQuickAffordances.SlotTable.TABLE_NAME
                     )
                 MATCH_CODE_ALL_AFFORDANCES ->
                     Contract.LockScreenQuickAffordances.qualifiedTablePath(
-                        Contract.LockScreenQuickAffordances.AffordanceTable.TABLE_NAME,
+                        Contract.LockScreenQuickAffordances.AffordanceTable.TABLE_NAME
                     )
                 MATCH_CODE_ALL_SELECTIONS ->
                     Contract.LockScreenQuickAffordances.qualifiedTablePath(
-                        Contract.LockScreenQuickAffordances.SelectionTable.TABLE_NAME,
+                        Contract.LockScreenQuickAffordances.SelectionTable.TABLE_NAME
                     )
                 MATCH_CODE_ALL_FLAGS -> Contract.FlagsTable.TABLE_NAME
                 else -> null
@@ -128,6 +124,7 @@
     }
 
     override fun insert(uri: Uri, values: ContentValues?): Uri? {
+        if (!::mainDispatcher.isInitialized) return null
         if (uriMatcher.match(uri) != MATCH_CODE_ALL_SELECTIONS) {
             throw UnsupportedOperationException()
         }
@@ -142,6 +139,7 @@
         selectionArgs: Array<out String>?,
         sortOrder: String?,
     ): Cursor? {
+        if (!::mainDispatcher.isInitialized) return null
         return runBlocking("$TAG#query", mainDispatcher) {
             when (uriMatcher.match(uri)) {
                 MATCH_CODE_ALL_AFFORDANCES -> queryAffordances()
@@ -163,11 +161,8 @@
         return 0
     }
 
-    override fun delete(
-        uri: Uri,
-        selection: String?,
-        selectionArgs: Array<out String>?,
-    ): Int {
+    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
+        if (!::mainDispatcher.isInitialized) return 0
         if (uriMatcher.match(uri) != MATCH_CODE_ALL_SELECTIONS) {
             throw UnsupportedOperationException()
         }
@@ -232,11 +227,7 @@
             throw IllegalArgumentException("Cannot insert selection, affordance ID was empty!")
         }
 
-        val success =
-            interactor.select(
-                slotId = slotId,
-                affordanceId = affordanceId,
-            )
+        val success = interactor.select(slotId = slotId, affordanceId = affordanceId)
 
         return if (success) {
             Log.d(TAG, "Successfully selected $affordanceId for slot $slotId")
@@ -318,22 +309,14 @@
             )
             .apply {
                 interactor.getSlotPickerRepresentations().forEach { representation ->
-                    addRow(
-                        arrayOf(
-                            representation.id,
-                            representation.maxSelectedAffordances,
-                        )
-                    )
+                    addRow(arrayOf(representation.id, representation.maxSelectedAffordances))
                 }
             }
     }
 
     private suspend fun queryFlags(): Cursor {
         return MatrixCursor(
-                arrayOf(
-                    Contract.FlagsTable.Columns.NAME,
-                    Contract.FlagsTable.Columns.VALUE,
-                )
+                arrayOf(Contract.FlagsTable.Columns.NAME, Contract.FlagsTable.Columns.VALUE)
             )
             .apply {
                 interactor.getPickerFlags().forEach { flag ->
@@ -351,10 +334,7 @@
             }
     }
 
-    private suspend fun deleteSelection(
-        uri: Uri,
-        selectionArgs: Array<out String>?,
-    ): Int {
+    private suspend fun deleteSelection(uri: Uri, selectionArgs: Array<out String>?): Int {
         if (selectionArgs == null) {
             throw IllegalArgumentException(
                 "Cannot delete selection, selection arguments not included!"
@@ -372,11 +352,7 @@
                     )
             }
 
-        val deleted =
-            interactor.unselect(
-                slotId = slotId,
-                affordanceId = affordanceId,
-            )
+        val deleted = interactor.unselect(slotId = slotId, affordanceId = affordanceId)
 
         return if (deleted) {
             Log.d(TAG, "Successfully unselected $affordanceId for slot $slotId")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 3b1569d..1a0525d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -42,6 +42,7 @@
 import static com.android.systemui.DejankUtils.whitelistIpcs;
 import static com.android.systemui.Flags.notifyPowerManagerUserActivityBackground;
 import static com.android.systemui.Flags.relockWithPowerButtonImmediately;
+import static com.android.systemui.Flags.simPinBouncerReset;
 import static com.android.systemui.Flags.translucentOccludingActivityFix;
 import static com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel.DREAMING_ANIMATION_DURATION_MS;
 
@@ -238,7 +239,6 @@
     private static final long KEYGUARD_DONE_PENDING_TIMEOUT_MS = 3000;
 
     private static final boolean DEBUG = KeyguardConstants.DEBUG;
-    private static final boolean DEBUG_SIM_STATES = KeyguardConstants.DEBUG_SIM_STATES;
 
     private final static String TAG = "KeyguardViewMediator";
 
@@ -649,11 +649,8 @@
 
         @Override
         public void onSimStateChanged(int subId, int slotId, int simState) {
-
-            if (DEBUG_SIM_STATES) {
-                Log.d(TAG, "onSimStateChanged(subId=" + subId + ", slotId=" + slotId
-                        + ",state=" + simState + ")");
-            }
+            Log.d(TAG, "onSimStateChanged(subId=" + subId + ", slotId=" + slotId
+                    + ",state=" +  TelephonyManager.simStateToString(simState) + ")");
 
             int size = mKeyguardStateCallbacks.size();
             boolean simPinSecure = mUpdateMonitor.isSimPinSecure();
@@ -686,7 +683,7 @@
                     synchronized (KeyguardViewMediator.this) {
                         if (shouldWaitForProvisioning()) {
                             if (!mShowing) {
-                                if (DEBUG_SIM_STATES) Log.d(TAG, "ICC_ABSENT isn't showing,"
+                                Log.d(TAG, "ICC_ABSENT isn't showing,"
                                         + " we need to show the keyguard since the "
                                         + "device isn't provisioned yet.");
                                 doKeyguardLocked(null);
@@ -698,11 +695,21 @@
                             // MVNO SIMs can become transiently NOT_READY when switching networks,
                             // so we should only lock when they are ABSENT.
                             if (lastSimStateWasLocked) {
-                                if (DEBUG_SIM_STATES) Log.d(TAG, "SIM moved to ABSENT when the "
+                                Log.d(TAG, "SIM moved to ABSENT when the "
                                         + "previous state was locked. Reset the state.");
                                 resetStateLocked();
                             }
                             mSimWasLocked.append(slotId, false);
+                        } else if (simState == TelephonyManager.SIM_STATE_NOT_READY) {
+                            if (simPinBouncerReset()) {
+                                // Support eSIM disablement, and do not clear `mSimWasLocked`.
+                                // NOT_READY could just be a temporary state
+                                if (lastSimStateWasLocked) {
+                                    Log.d(TAG, "SIM moved to NOT_READY when the "
+                                            + "previous state was locked. Reset the state.");
+                                    resetStateLocked();
+                                }
+                            }
                         }
                     }
                     break;
@@ -712,7 +719,7 @@
                         mSimWasLocked.append(slotId, true);
                         mPendingPinLock = true;
                         if (!mShowing) {
-                            if (DEBUG_SIM_STATES) Log.d(TAG,
+                            Log.d(TAG,
                                     "INTENT_VALUE_ICC_LOCKED and keygaurd isn't "
                                     + "showing; need to show keyguard so user can enter sim pin");
                             doKeyguardLocked(null);
@@ -724,11 +731,11 @@
                 case TelephonyManager.SIM_STATE_PERM_DISABLED:
                     synchronized (KeyguardViewMediator.this) {
                         if (!mShowing) {
-                            if (DEBUG_SIM_STATES) Log.d(TAG, "PERM_DISABLED and "
+                            Log.d(TAG, "PERM_DISABLED and "
                                   + "keygaurd isn't showing.");
                             doKeyguardLocked(null);
                         } else {
-                            if (DEBUG_SIM_STATES) Log.d(TAG, "PERM_DISABLED, resetStateLocked to"
+                            Log.d(TAG, "PERM_DISABLED, resetStateLocked to"
                                   + "show permanently disabled message in lockscreen.");
                             resetStateLocked();
                         }
@@ -736,9 +743,9 @@
                     break;
                 case TelephonyManager.SIM_STATE_READY:
                     synchronized (KeyguardViewMediator.this) {
-                        if (DEBUG_SIM_STATES) Log.d(TAG, "READY, reset state? " + mShowing);
+                        Log.d(TAG, "READY, reset state? " + mShowing);
                         if (mShowing && mSimWasLocked.get(slotId, false)) {
-                            if (DEBUG_SIM_STATES) Log.d(TAG, "SIM moved to READY when the "
+                            Log.d(TAG, "SIM moved to READY when the "
                                     + "previously was locked. Reset the state.");
                             mSimWasLocked.append(slotId, false);
                             resetStateLocked();
@@ -746,7 +753,7 @@
                     }
                     break;
                 default:
-                    if (DEBUG_SIM_STATES) Log.v(TAG, "Unspecific state: " + simState);
+                    Log.v(TAG, "Unspecific state: " + simState);
                     break;
             }
         }
@@ -1682,6 +1689,12 @@
                     });
             mJavaAdapter.alwaysCollectFlow(communalViewModel.getTransitionFromOccludedEnded(),
                     getFinishedCallbackConsumer());
+
+            // System ready can be invoked in the middle of user switching, so check for this state
+            // and issue the call manually as that important event was missed.
+            if (mUserTracker.isUserSwitching()) {
+                mUpdateCallback.onUserSwitching(mUserTracker.getUserId());
+            }
         }
         // Most services aren't available until the system reaches the ready state, so we
         // send it here when the device first boots.
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 228e01e..cd5daf9 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
@@ -108,7 +108,7 @@
             .transition(
                 edge = Edge.create(from = KeyguardState.LOCKSCREEN, to = Scenes.Gone),
                 edgeWithoutSceneContainer =
-                    Edge.create(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+                    Edge.create(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE),
             )
             .map<TransitionStep, Boolean?> {
                 true // Make the surface visible during LS -> GONE transitions.
@@ -162,7 +162,7 @@
                 .collect {
                     startTransitionTo(
                         KeyguardState.PRIMARY_BOUNCER,
-                        ownerReason = "#listenForLockscreenToPrimaryBouncer"
+                        ownerReason = "#listenForLockscreenToPrimaryBouncer",
                     )
                 }
         }
@@ -238,7 +238,7 @@
                                             getDefaultAnimatorForTransitionsToState(
                                                     KeyguardState.LOCKSCREEN
                                                 )
-                                                .apply { duration = 0 }
+                                                .apply { duration = 0 },
                                     )
                                 )
                             }
@@ -249,6 +249,8 @@
                         if (
                             // Use currentTransitionInfo to decide whether to start the transition.
                             currentTransitionInfo.to == KeyguardState.LOCKSCREEN &&
+                                shadeExpansion > 0f &&
+                                shadeExpansion < 1f &&
                                 shadeRepository.legacyShadeTracking.value &&
                                 !isKeyguardUnlocked &&
                                 statusBarState == KEYGUARD
@@ -257,7 +259,7 @@
                                 startTransitionTo(
                                     toState = KeyguardState.PRIMARY_BOUNCER,
                                     animator = null, // transition will be manually controlled,
-                                    ownerReason = "#listenForLockscreenToPrimaryBouncerDragging"
+                                    ownerReason = "#listenForLockscreenToPrimaryBouncerDragging",
                                 )
                         }
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
index 60c5386..eb9b07a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
@@ -21,7 +21,7 @@
 import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.keyguard.shared.model.DismissAction
 import com.android.systemui.keyguard.shared.model.KeyguardDone
@@ -29,13 +29,12 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.domain.resolver.NotifShadeSceneFamilyResolver
-import com.android.systemui.scene.domain.resolver.QuickSettingsSceneFamilyResolver
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.util.kotlin.Utils.Companion.sampleFilter
 import com.android.systemui.util.kotlin.sample
+import dagger.Lazy
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -61,14 +60,11 @@
     transitionInteractor: KeyguardTransitionInteractor,
     val dismissInteractor: KeyguardDismissInteractor,
     @Application private val applicationScope: CoroutineScope,
-    sceneInteractor: dagger.Lazy<SceneInteractor>,
-    deviceEntryInteractor: dagger.Lazy<DeviceEntryInteractor>,
-    quickSettingsSceneFamilyResolver: dagger.Lazy<QuickSettingsSceneFamilyResolver>,
-    notifShadeSceneFamilyResolver: dagger.Lazy<NotifShadeSceneFamilyResolver>,
+    sceneInteractor: Lazy<SceneInteractor>,
+    deviceUnlockedInteractor: Lazy<DeviceUnlockedInteractor>,
     powerInteractor: PowerInteractor,
     alternateBouncerInteractor: AlternateBouncerInteractor,
-    keyguardInteractor: dagger.Lazy<KeyguardInteractor>,
-    shadeInteractor: dagger.Lazy<ShadeInteractor>,
+    shadeInteractor: Lazy<ShadeInteractor>,
 ) {
     val dismissAction: Flow<DismissAction> = repository.dismissAction
 
@@ -106,18 +102,18 @@
         if (SceneContainerFlag.isEnabled) {
             combine(
                     sceneInteractor.get().currentScene,
-                    deviceEntryInteractor.get().isUnlocked,
-                ) { scene, isUnlocked ->
-                    isUnlocked &&
-                        (quickSettingsSceneFamilyResolver.get().includesScene(scene) ||
-                            notifShadeSceneFamilyResolver.get().includesScene(scene))
+                    deviceUnlockedInteractor.get().deviceUnlockStatus,
+                ) { scene, unlockStatus ->
+                    unlockStatus.isUnlocked &&
+                        (scene == Scenes.QuickSettings || scene == Scenes.Shade)
                 }
                 .distinctUntilChanged()
         } else if (ComposeBouncerFlags.isOnlyComposeBouncerEnabled()) {
-            shadeInteractor.get().isAnyExpanded.sample(
-                keyguardInteractor.get().isKeyguardDismissible
-            ) { isAnyExpanded, isKeyguardDismissible ->
-                isAnyExpanded && isKeyguardDismissible
+            combine(
+                shadeInteractor.get().isAnyExpanded,
+                deviceUnlockedInteractor.get().deviceUnlockStatus,
+            ) { isAnyExpanded, deviceUnlockStatus ->
+                isAnyExpanded && deviceUnlockStatus.isUnlocked
             }
         } else {
             flow {
@@ -132,7 +128,7 @@
         merge(
                 finishedTransitionToGone,
                 isOnShadeWhileUnlocked.filter { it }.map {},
-                dismissInteractor.dismissKeyguardRequestWithImmediateDismissAction
+                dismissInteractor.dismissKeyguardRequestWithImmediateDismissAction,
             )
             .sample(dismissAction)
             .filterNot { it is DismissAction.None }
@@ -142,11 +138,11 @@
         combine(
                 transitionInteractor.isFinishedIn(
                     scene = Scenes.Gone,
-                    stateWithoutSceneContainer = GONE
+                    stateWithoutSceneContainer = GONE,
                 ),
                 transitionInteractor.isFinishedIn(
                     scene = Scenes.Bouncer,
-                    stateWithoutSceneContainer = PRIMARY_BOUNCER
+                    stateWithoutSceneContainer = PRIMARY_BOUNCER,
                 ),
                 alternateBouncerInteractor.isVisible,
                 isOnShadeWhileUnlocked,
@@ -164,7 +160,7 @@
     }
 
     fun runAfterKeyguardGone(runnable: Runnable) {
-        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return
+        if (ComposeBouncerFlags.isUnexpectedlyInLegacyMode()) return
         setDismissAction(
             DismissAction.RunAfterKeyguardGone(
                 dismissAction = { runnable.run() },
@@ -176,18 +172,18 @@
     }
 
     fun setDismissAction(dismissAction: DismissAction) {
-        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return
+        if (ComposeBouncerFlags.isUnexpectedlyInLegacyMode()) return
         repository.dismissAction.value.onCancelAction.run()
         repository.setDismissAction(dismissAction)
     }
 
     fun handleDismissAction() {
-        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return
+        if (ComposeBouncerFlags.isUnexpectedlyInLegacyMode()) return
         repository.setDismissAction(DismissAction.None)
     }
 
     suspend fun setKeyguardDone(keyguardDoneTiming: KeyguardDone) {
-        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return
+        if (ComposeBouncerFlags.isUnexpectedlyInLegacyMode()) return
         dismissInteractor.setKeyguardDone(keyguardDoneTiming)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
index 44aafab..ad1a32e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
@@ -76,7 +76,7 @@
             .filter { enabled -> !enabled }
             .sampleCombine(
                 internalTransitionInteractor.currentTransitionInfoInternal,
-                biometricSettingsRepository.isCurrentUserInLockdown
+                biometricSettingsRepository.isCurrentUserInLockdown,
             )
             .map { (_, transitionInfo, inLockdown) ->
                 // ...we hide the keyguard, if it's showing and we're not in lockdown. In that case,
@@ -91,12 +91,18 @@
          */
         scope.launch {
             if (!SceneContainerFlag.isEnabled) {
-                showKeyguardWhenReenabled
-                    .filter { shouldDismiss -> shouldDismiss }
-                    .collect {
-                        keyguardDismissTransitionInteractor.startDismissKeyguardTransition(
-                            "keyguard disabled"
-                        )
+                repository.isKeyguardEnabled
+                    .filter { enabled -> !enabled }
+                    .sampleCombine(
+                        biometricSettingsRepository.isCurrentUserInLockdown,
+                        internalTransitionInteractor.currentTransitionInfoInternal,
+                    )
+                    .collect { (_, inLockdown, currentTransitionInfo) ->
+                        if (currentTransitionInfo.to != KeyguardState.GONE && !inLockdown) {
+                            keyguardDismissTransitionInteractor.startDismissKeyguardTransition(
+                                "keyguard disabled"
+                            )
+                        }
                     }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardStateCallbackInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardStateCallbackInteractor.kt
index 420fbd4..7fd348b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardStateCallbackInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardStateCallbackInteractor.kt
@@ -20,6 +20,7 @@
 import android.os.RemoteException
 import com.android.internal.policy.IKeyguardStateCallback
 import com.android.systemui.CoreStartable
+import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
@@ -27,13 +28,13 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
-import javax.inject.Inject
 
 /**
  * Updates KeyguardStateCallbacks provided to KeyguardService with KeyguardTransitionInteractor
@@ -50,6 +51,8 @@
     @Background private val backgroundDispatcher: CoroutineDispatcher,
     private val selectedUserInteractor: SelectedUserInteractor,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    private val trustInteractor: TrustInteractor,
+    private val simBouncerInteractor: SimBouncerInteractor,
 ) : CoreStartable {
     private val callbacks = mutableListOf<IKeyguardStateCallback>()
 
@@ -60,21 +63,19 @@
 
         applicationScope.launch {
             combine(
-                selectedUserInteractor.selectedUser,
-                keyguardTransitionInteractor.currentKeyguardState,
-                ::Pair
-            ).collectLatest { (selectedUser, currentState) ->
-                val iterator = callbacks.iterator()
+                    selectedUserInteractor.selectedUser,
+                    keyguardTransitionInteractor.currentKeyguardState,
+                    keyguardTransitionInteractor.startedKeyguardTransitionStep,
+                    ::Triple,
+                )
+                .collectLatest { (selectedUser, _, _) ->
+                    val iterator = callbacks.iterator()
                     withContext(backgroundDispatcher) {
                         while (iterator.hasNext()) {
                             val callback = iterator.next()
                             try {
-                                 callback.onShowingStateChanged(
-                                    currentState != KeyguardState.GONE,
-                                    selectedUser
-                                )
-                                callback.onInputRestrictedStateChanged(
-                                    currentState != KeyguardState.GONE)
+                                callback.onShowingStateChanged(!isIdleInGone(), selectedUser)
+                                callback.onInputRestrictedStateChanged(!isIdleInGone())
                             } catch (e: RemoteException) {
                                 if (e is DeadObjectException) {
                                     iterator.remove()
@@ -84,10 +85,58 @@
                     }
                 }
         }
+
+        applicationScope.launch {
+            trustInteractor.isTrusted.collectLatest { isTrusted ->
+                val iterator = callbacks.iterator()
+                withContext(backgroundDispatcher) {
+                    while (iterator.hasNext()) {
+                        val callback = iterator.next()
+                        try {
+                            callback.onTrustedChanged(isTrusted)
+                        } catch (e: RemoteException) {
+                            if (e is DeadObjectException) {
+                                iterator.remove()
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        applicationScope.launch {
+            simBouncerInteractor.isAnySimSecure.collectLatest { isSimSecured ->
+                val iterator = callbacks.iterator()
+                withContext(backgroundDispatcher) {
+                    while (iterator.hasNext()) {
+                        val callback = iterator.next()
+                        try {
+                            callback.onSimSecureStateChanged(isSimSecured)
+                        } catch (e: RemoteException) {
+                            if (e is DeadObjectException) {
+                                iterator.remove()
+                            }
+                        }
+                    }
+                }
+            }
+        }
     }
 
     fun addCallback(callback: IKeyguardStateCallback) {
         KeyguardWmStateRefactor.isUnexpectedlyInLegacyMode()
         callbacks.add(callback)
+
+        // Send initial values to new callbacks.
+        callback.onShowingStateChanged(!isIdleInGone(), selectedUserInteractor.getSelectedUserId())
+        callback.onInputRestrictedStateChanged(!isIdleInGone())
+        callback.onTrustedChanged(trustInteractor.isTrusted.value)
+        callback.onSimSecureStateChanged(simBouncerInteractor.isAnySimSecure.value)
     }
-}
\ No newline at end of file
+
+    /** Whether we're in KeyguardState.GONE and haven't started a transition to another state. */
+    private fun isIdleInGone(): Boolean {
+        return keyguardTransitionInteractor.getCurrentState() == KeyguardState.GONE &&
+            keyguardTransitionInteractor.getStartedState() == KeyguardState.GONE
+    }
+}
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 e19b72e..c4f231d 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
@@ -77,7 +77,7 @@
             MutableSharedFlow<Float>(
                     replay = 1,
                     extraBufferCapacity = 2,
-                    onBufferOverflow = BufferOverflow.DROP_OLDEST
+                    onBufferOverflow = BufferOverflow.DROP_OLDEST,
                 )
                 .also { it.tryEmit(0f) }
         }
@@ -97,8 +97,8 @@
                 SharingStarted.Eagerly,
                 WithPrev(
                     sceneInteractor.transitionState.value,
-                    sceneInteractor.transitionState.value
-                )
+                    sceneInteractor.transitionState.value,
+                ),
             )
 
     /**
@@ -156,7 +156,7 @@
                     Log.e(
                         TAG,
                         "STARTED step ($startedStep) was preceded by a RUNNING step " +
-                            "($prevStep), which should never happen. Things could go badly here."
+                            "($prevStep), which should never happen. Things could go badly here.",
                     )
                 }
             }
@@ -202,7 +202,7 @@
             transitionMap.getOrPut(mappedEdge) {
                 MutableSharedFlow(
                     extraBufferCapacity = 10,
-                    onBufferOverflow = BufferOverflow.DROP_OLDEST
+                    onBufferOverflow = BufferOverflow.DROP_OLDEST,
                 )
             }
 
@@ -262,7 +262,7 @@
             is Edge.StateToState ->
                 Edge.create(
                     from = edge.from?.mapToSceneContainerState(),
-                    to = edge.to?.mapToSceneContainerState()
+                    to = edge.to?.mapToSceneContainerState(),
                 )
             is Edge.SceneToState -> Edge.create(UNDEFINED, edge.to)
             is Edge.StateToScene -> Edge.create(edge.from, UNDEFINED)
@@ -286,9 +286,7 @@
      * The value will be `0` (or close to `0`, due to float point arithmetic) if not in this step or
      * `1` when fully in the given state.
      */
-    fun transitionValue(
-        state: KeyguardState,
-    ): Flow<Float> {
+    fun transitionValue(state: KeyguardState): Flow<Float> {
         if (SceneContainerFlag.isEnabled && state != state.mapToSceneContainerState()) {
             Log.e(TAG, "SceneContainer is enabled but a deprecated state $state is used.")
             return transitionValue(state.mapToSceneContainerScene()!!, state)
@@ -369,10 +367,9 @@
             .stateIn(scope, SharingStarted.Eagerly, OFF)
 
     val isInTransition =
-        combine(
-            isInTransitionWhere({ true }, { true }),
-            sceneInteractor.transitionState,
-        ) { isKeyguardTransitioning, sceneTransitionState ->
+        combine(isInTransitionWhere({ true }, { true }), sceneInteractor.transitionState) {
+            isKeyguardTransitioning,
+            sceneTransitionState ->
             isKeyguardTransitioning ||
                 (SceneContainerFlag.isEnabled && sceneTransitionState.isTransitioning())
         }
@@ -465,6 +462,10 @@
         return currentKeyguardState.replayCache.last()
     }
 
+    fun getStartedState(): KeyguardState {
+        return startedKeyguardTransitionStep.value.to
+    }
+
     private val finishedKeyguardState: StateFlow<KeyguardState> =
         repository.transitions
             .filter { it.transitionState == TransitionState.FINISHED }
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 7e13d22..4b62eab 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
@@ -181,7 +181,7 @@
         }
         .stateIn(
             scope = applicationScope,
-            started = SharingStarted.WhileSubscribed(),
+            started = SharingStarted.Eagerly,
             initialValue =
                 KeyguardQuickAffordanceViewModel(
                     slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId()
@@ -202,7 +202,7 @@
         }
         .stateIn(
             scope = applicationScope,
-            started = SharingStarted.WhileSubscribed(),
+            started = SharingStarted.Eagerly,
             initialValue =
                 KeyguardQuickAffordanceViewModel(
                     slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId()
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 eaa61a1..38ca888 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
@@ -36,6 +36,7 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
 import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
 import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
 import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
@@ -174,6 +175,9 @@
                     keyguardTransitionInteractor.isInTransition(
                         Edge.create(from = LOCKSCREEN, to = DREAMING)
                     ),
+                    keyguardTransitionInteractor.isInTransition(
+                        Edge.create(from = LOCKSCREEN, to = OCCLUDED)
+                    ),
                 ),
                 isOnLockscreen,
                 shadeInteractor.qsExpansion,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModel.kt
index ecae079..3b266f9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModel.kt
@@ -21,13 +21,11 @@
 import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.Swipe
 import com.android.compose.animation.scene.SwipeDirection
-import com.android.compose.animation.scene.TransitionKey
 import com.android.compose.animation.scene.UserAction
 import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 import com.android.systemui.scene.shared.model.Overlays
-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.SceneContainerEdge
@@ -71,9 +69,8 @@
 
                             addAll(
                                 when (shadeMode) {
-                                    ShadeMode.Single -> fullscreenShadeActions()
-                                    ShadeMode.Split ->
-                                        fullscreenShadeActions(transitionKey = ToSplitShade)
+                                    ShadeMode.Single -> singleShadeActions()
+                                    ShadeMode.Split -> splitShadeActions()
                                     ShadeMode.Dual -> dualShadeActions()
                                 }
                             )
@@ -84,18 +81,26 @@
             .collect { setActions(it) }
     }
 
-    private fun fullscreenShadeActions(
-        transitionKey: TransitionKey? = null
-    ): Array<Pair<UserAction, UserActionResult>> {
-        val notifShadeSceneKey = UserActionResult(SceneFamilies.NotifShade, transitionKey)
-        val qsShadeSceneKey = UserActionResult(SceneFamilies.QuickSettings, transitionKey)
+    private fun singleShadeActions(): Array<Pair<UserAction, UserActionResult>> {
         return arrayOf(
             // Swiping down, not from the edge, always goes to shade.
-            Swipe.Down to notifShadeSceneKey,
-            swipeDown(pointerCount = 2) to notifShadeSceneKey,
+            Swipe.Down to Scenes.Shade,
+            swipeDown(pointerCount = 2) to Scenes.Shade,
             // Swiping down from the top edge goes to QS.
-            swipeDownFromTop(pointerCount = 1) to qsShadeSceneKey,
-            swipeDownFromTop(pointerCount = 2) to qsShadeSceneKey,
+            swipeDownFromTop(pointerCount = 1) to Scenes.QuickSettings,
+            swipeDownFromTop(pointerCount = 2) to Scenes.QuickSettings,
+        )
+    }
+
+    private fun splitShadeActions(): Array<Pair<UserAction, UserActionResult>> {
+        val splitShadeSceneKey = UserActionResult(Scenes.Shade, ToSplitShade)
+        return arrayOf(
+            // Swiping down, not from the edge, always goes to shade.
+            Swipe.Down to splitShadeSceneKey,
+            swipeDown(pointerCount = 2) to splitShadeSceneKey,
+            // Swiping down from the top edge goes to QS.
+            swipeDownFromTop(pointerCount = 1) to splitShadeSceneKey,
+            swipeDownFromTop(pointerCount = 2) to splitShadeSceneKey,
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/MediaResumeListener.kt
index 9ee59d1..ad84a5e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/MediaResumeListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/MediaResumeListener.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.media.controls.domain.resume
 
+import android.annotation.WorkerThread
 import android.content.BroadcastReceiver
 import android.content.ComponentName
 import android.content.Context
@@ -41,6 +42,7 @@
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.tuner.TunerService
 import com.android.systemui.util.Utils
+import com.android.systemui.util.kotlin.logD
 import com.android.systemui.util.time.SystemClock
 import java.io.PrintWriter
 import java.util.concurrent.ConcurrentLinkedQueue
@@ -86,11 +88,12 @@
     @VisibleForTesting
     val userUnlockReceiver =
         object : BroadcastReceiver() {
+            @WorkerThread
             override fun onReceive(context: Context, intent: Intent) {
                 if (Intent.ACTION_USER_UNLOCKED == intent.action) {
                     val userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1)
                     if (userId == currentUserId) {
-                        backgroundExecutor.execute { loadMediaResumptionControls() }
+                        loadMediaResumptionControls()
                     }
                 }
             }
@@ -109,7 +112,7 @@
             override fun addTrack(
                 desc: MediaDescription,
                 component: ComponentName,
-                browser: ResumeMediaBrowser
+                browser: ResumeMediaBrowser,
             ) {
                 val token = browser.token
                 val appIntent = browser.appIntent
@@ -123,7 +126,7 @@
                     Log.e(TAG, "Error getting package information", e)
                 }
 
-                Log.d(TAG, "Adding resume controls for ${browser.userId}: $desc")
+                logD(TAG) { "Adding resume controls for ${browser.userId}: $desc" }
                 mediaDataManager.addResumptionControls(
                     browser.userId,
                     desc,
@@ -131,7 +134,7 @@
                     token,
                     appName.toString(),
                     appIntent,
-                    component.packageName
+                    component.packageName,
                 )
             }
         }
@@ -144,8 +147,8 @@
             broadcastDispatcher.registerReceiver(
                 userUnlockReceiver,
                 unlockFilter,
-                null,
-                UserHandle.ALL
+                backgroundExecutor,
+                UserHandle.ALL,
             )
             userTracker.addCallback(userTrackerCallback, mainExecutor)
             loadSavedComponents()
@@ -163,7 +166,7 @@
                     mediaDataManager.setMediaResumptionEnabled(useMediaResumption)
                 }
             },
-            Settings.Secure.MEDIA_CONTROLS_RESUME
+            Settings.Secure.MEDIA_CONTROLS_RESUME,
         )
     }
 
@@ -197,11 +200,11 @@
                 }
             resumeComponents.add(component to lastPlayed)
         }
-        Log.d(
-            TAG,
+
+        logD(TAG) {
             "loaded resume components for $currentUserId: " +
-                "${resumeComponents.toArray().contentToString()}"
-        )
+                resumeComponents.toArray().contentToString()
+        }
 
         if (needsUpdate) {
             // Save any missing times that we had to fill in
@@ -228,7 +231,7 @@
                         mediaBrowserFactory.create(mediaBrowserCallback, it.first, currentUserId)
                     browser.findRecentMedia()
                 } else {
-                    Log.d(TAG, "User $currentUserId does not have component ${it.first}")
+                    logD(TAG) { "User $currentUserId does not have component ${it.first}" }
                 }
             }
         }
@@ -240,7 +243,7 @@
         data: MediaData,
         immediately: Boolean,
         receivedSmartspaceCardLatency: Int,
-        isSsReactivated: Boolean
+        isSsReactivated: Boolean,
     ) {
         if (useMediaResumption) {
             // If this had been started from a resume state, disconnect now that it's live
@@ -281,7 +284,7 @@
             mediaBrowserFactory.create(
                 object : ResumeMediaBrowser.Callback() {
                     override fun onConnected() {
-                        Log.d(TAG, "Connected to $componentName")
+                        logD(TAG) { "Connected to $componentName" }
                     }
 
                     override fun onError() {
@@ -292,20 +295,20 @@
                     override fun addTrack(
                         desc: MediaDescription,
                         component: ComponentName,
-                        browser: ResumeMediaBrowser
+                        browser: ResumeMediaBrowser,
                     ) {
                         // Since this is a test, just save the component for later
-                        Log.d(
-                            TAG,
+                        logD(TAG) {
                             "Can get resumable media for ${browser.userId} from $componentName"
-                        )
+                        }
+
                         mediaDataManager.setResumeAction(key, getResumeAction(componentName))
                         updateResumptionList(componentName)
                         mediaBrowser = null
                     }
                 },
                 componentName,
-                currentUserId
+                currentUserId,
             )
         mediaBrowser?.testConnection()
     }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
index 4251b81..d596589 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -53,6 +53,7 @@
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.Window;
+import android.view.WindowManager;
 
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
@@ -308,6 +309,9 @@
     private void setUpDialog(AlertDialog dialog) {
         SystemUIDialog.registerDismissListener(dialog);
         SystemUIDialog.applyFlags(dialog, /* showWhenLocked= */ false);
+
+        final Window w = dialog.getWindow();
+        w.setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
         SystemUIDialog.setDialogSize(dialog);
 
         dialog.setOnCancelListener(this::onDialogDismissedOrCancelled);
@@ -315,7 +319,6 @@
         dialog.create();
         dialog.getButton(DialogInterface.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true);
 
-        final Window w = dialog.getWindow();
         w.addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt b/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
index db5a545..0d748a1 100644
--- a/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
+++ b/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
@@ -17,11 +17,13 @@
 package com.android.systemui.model
 
 import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.OverlayKey
 import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Overlays
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING
 import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_COMMUNAL_HUB_SHOWING
@@ -57,12 +59,12 @@
 
         val transitionState = sceneInteractor.get().transitionState.value
         val idleTransitionStateOrNull = transitionState as? ObservableTransitionState.Idle
-        val currentSceneOrNull = idleTransitionStateOrNull?.currentScene
         val invisibleDueToOcclusion = occlusionInteractor.get().invisibleDueToOcclusion.value
-        return currentSceneOrNull?.let { sceneKey ->
+        return idleTransitionStateOrNull?.let { idleState ->
             EvaluatorByFlag[flag]?.invoke(
                 SceneContainerPluginState(
-                    scene = sceneKey,
+                    scene = idleState.currentScene,
+                    overlays = idleState.currentOverlays,
                     invisibleDueToOcclusion = invisibleDueToOcclusion,
                 )
             )
@@ -89,10 +91,15 @@
                             it.invisibleDueToOcclusion -> false
                             it.scene == Scenes.Lockscreen -> true
                             it.scene == Scenes.Shade -> true
+                            Overlays.NotificationsShade in it.overlays -> true
                             else -> false
                         }
                     },
-                SYSUI_STATE_QUICK_SETTINGS_EXPANDED to { it.scene == Scenes.QuickSettings },
+                SYSUI_STATE_QUICK_SETTINGS_EXPANDED to
+                    {
+                        it.scene == Scenes.QuickSettings ||
+                            Overlays.QuickSettingsShade in it.overlays
+                    },
                 SYSUI_STATE_BOUNCER_SHOWING to { it.scene == Scenes.Bouncer },
                 SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING to
                     {
@@ -106,5 +113,9 @@
             )
     }
 
-    data class SceneContainerPluginState(val scene: SceneKey, val invisibleDueToOcclusion: Boolean)
+    data class SceneContainerPluginState(
+        val scene: SceneKey,
+        val overlays: Set<OverlayKey>,
+        val invisibleDueToOcclusion: Boolean,
+    )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModel.kt
index b6868c1..63bfbd1 100644
--- a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModel.kt
@@ -18,9 +18,13 @@
 
 import com.android.compose.animation.scene.Back
 import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
 import com.android.compose.animation.scene.UserAction
 import com.android.compose.animation.scene.UserActionResult
+import com.android.compose.animation.scene.UserActionResult.HideOverlay
+import com.android.compose.animation.scene.UserActionResult.ReplaceByOverlay
 import com.android.systemui.scene.shared.model.Overlays
+import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge
 import com.android.systemui.scene.ui.viewmodel.UserActionsViewModel
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
@@ -32,8 +36,10 @@
     override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
         setActions(
             mapOf(
-                Swipe.Up to UserActionResult.HideOverlay(Overlays.NotificationsShade),
-                Back to UserActionResult.HideOverlay(Overlays.NotificationsShade),
+                Swipe.Up to HideOverlay(Overlays.NotificationsShade),
+                Back to HideOverlay(Overlays.NotificationsShade),
+                Swipe(direction = SwipeDirection.Down, fromSource = SceneContainerEdge.TopRight) to
+                    ReplaceByOverlay(Overlays.QuickSettingsShade),
             )
         )
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TEST_MAPPING b/packages/SystemUI/src/com/android/systemui/qs/TEST_MAPPING
index 66f020f..75140be 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TEST_MAPPING
+++ b/packages/SystemUI/src/com/android/systemui/qs/TEST_MAPPING
@@ -1,28 +1,12 @@
 {
   "presubmit": [
     {
-      "name": "CtsTileServiceTestCases",
-      "options": [
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsTileServiceTestCases"
     }
   ],
  "postsubmit": [
     {
-      "name": "QuickSettingsDeviceResetTests",
-      "options": [
-          {
-            "exclude-annotation": "org.junit.Ignore"
-          },
-          {
-            "exclude-annotation": "androidx.test.filters.FlakyTest"
-          }
-      ]
+      "name": "QuickSettingsDeviceResetTests"
     }
   ]
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
index c2f1c3d..af167d4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -158,7 +158,7 @@
     override fun onCreateView(
         inflater: LayoutInflater,
         container: ViewGroup?,
-        savedInstanceState: Bundle?
+        savedInstanceState: Bundle?,
     ): View {
         val context = inflater.context
         return ComposeView(context).apply {
@@ -181,7 +181,7 @@
                                     notificationScrimClippingParams.bottom,
                                     notificationScrimClippingParams.radius,
                                 )
-                            }
+                            },
                     ) {
                         AnimatedContent(targetState = qsState) {
                             when (it) {
@@ -272,7 +272,7 @@
         qsExpansionFraction: Float,
         panelExpansionFraction: Float,
         headerTranslation: Float,
-        squishinessFraction: Float
+        squishinessFraction: Float,
     ) {
         viewModel.qsExpansionValue = qsExpansionFraction
         viewModel.panelExpansionFractionValue = panelExpansionFraction
@@ -318,12 +318,12 @@
     override fun setTransitionToFullShadeProgress(
         isTransitioningToFullShade: Boolean,
         qsTransitionFraction: Float,
-        qsSquishinessFraction: Float
+        qsSquishinessFraction: Float,
     ) {
         super.setTransitionToFullShadeProgress(
             isTransitioningToFullShade,
             qsTransitionFraction,
-            qsSquishinessFraction
+            qsSquishinessFraction,
         )
     }
 
@@ -334,7 +334,7 @@
         bottom: Int,
         cornerRadius: Int,
         visible: Boolean,
-        fullWidth: Boolean
+        fullWidth: Boolean,
     ) {
         notificationScrimClippingParams.isEnabled = visible
         notificationScrimClippingParams.top = top
@@ -402,7 +402,7 @@
                 launch {
                     setListenerJob(
                         heightListener,
-                        viewModel.containerViewModel.editModeViewModel.isEditing
+                        viewModel.containerViewModel.editModeViewModel.isEditing,
                     ) {
                         onQsHeightChanged()
                     }
@@ -410,7 +410,7 @@
                 launch {
                     setListenerJob(
                         qsContainerController,
-                        viewModel.containerViewModel.editModeViewModel.isEditing
+                        viewModel.containerViewModel.editModeViewModel.isEditing,
                     ) {
                         setCustomizerShowing(it)
                     }
@@ -422,6 +422,7 @@
     @Composable
     private fun QuickQuickSettingsElement() {
         val qqsPadding by viewModel.qqsHeaderHeight.collectAsStateWithLifecycle()
+        val bottomPadding = dimensionResource(id = R.dimen.qqs_layout_padding_bottom)
         DisposableEffect(Unit) {
             qqsVisible.value = true
 
@@ -441,7 +442,7 @@
                             )
                         }
                         .onSizeChanged { size -> qqsHeight.value = size.height }
-                        .padding(top = { qqsPadding })
+                        .padding(top = { qqsPadding }, bottom = { bottomPadding.roundToPx() })
             ) {
                 val qsEnabled by viewModel.qsEnabled.collectAsStateWithLifecycle()
                 if (qsEnabled) {
@@ -450,7 +451,7 @@
                         modifier =
                             Modifier.collapseExpandSemanticAction(
                                 stringResource(id = R.string.accessibility_quick_settings_expand)
-                            )
+                            ),
                     )
                 }
             }
@@ -482,7 +483,7 @@
                     FooterActions(
                         viewModel = viewModel.footerActionsViewModel,
                         qsVisibilityLifecycleOwner = this@QSFragmentCompose,
-                        modifier = Modifier.sysuiResTag("qs_footer_actions")
+                        modifier = Modifier.sysuiResTag("qs_footer_actions"),
                     )
                 }
             }
@@ -562,7 +563,7 @@
 private suspend inline fun <Listener : Any, Data> setListenerJob(
     listenerFlow: MutableStateFlow<Listener?>,
     dataFlow: Flow<Data>,
-    crossinline onCollect: suspend Listener.(Data) -> Unit
+    crossinline onCollect: suspend Listener.(Data) -> Unit,
 ) {
     coroutineScope {
         try {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
index eeb55ca..fde40da 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
@@ -22,18 +22,16 @@
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.util.fastMap
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.qs.panels.ui.viewmodel.QuickQuickSettingsViewModel
 
 @Composable
-fun QuickQuickSettings(
-    viewModel: QuickQuickSettingsViewModel,
-    modifier: Modifier = Modifier,
-) {
+fun QuickQuickSettings(viewModel: QuickQuickSettingsViewModel, modifier: Modifier = Modifier) {
     val sizedTiles by
         viewModel.tileViewModels.collectAsStateWithLifecycle(initialValue = emptyList())
-    val tiles = sizedTiles.map { it.tile }
+    val tiles = sizedTiles.fastMap { it.tile }
 
     DisposableEffect(tiles) {
         val token = Any()
@@ -44,14 +42,18 @@
 
     TileLazyGrid(
         modifier = modifier.sysuiResTag("qqs_tile_layout"),
-        columns = GridCells.Fixed(columns)
+        columns = GridCells.Fixed(columns),
     ) {
         items(
-            tiles.size,
+            sizedTiles.size,
             key = { index -> sizedTiles[index].tile.spec.spec },
-            span = { index -> GridItemSpan(sizedTiles[index].width) }
+            span = { index -> GridItemSpan(sizedTiles[index].width) },
         ) { index ->
-            Tile(tile = tiles[index], iconOnly = sizedTiles[index].isIcon, modifier = Modifier)
+            Tile(
+                tile = sizedTiles[index].tile,
+                iconOnly = sizedTiles[index].isIcon,
+                modifier = Modifier,
+            )
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
index 93037d1..afd47a7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
@@ -18,6 +18,7 @@
 
 package com.android.systemui.qs.panels.ui.compose
 
+import android.content.res.Resources
 import android.graphics.drawable.Animatable
 import android.service.quicksettings.Tile.STATE_ACTIVE
 import android.service.quicksettings.Tile.STATE_INACTIVE
@@ -71,6 +72,7 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.ReadOnlyComposable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
@@ -85,13 +87,19 @@
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.layout.onGloballyPositioned
 import androidx.compose.ui.layout.positionInRoot
+import androidx.compose.ui.platform.LocalConfiguration
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.clearAndSetSemantics
+import androidx.compose.ui.semantics.contentDescription
 import androidx.compose.ui.semantics.onClick
+import androidx.compose.ui.semantics.role
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.semantics.stateDescription
+import androidx.compose.ui.semantics.toggleableState
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.Dp
@@ -106,12 +114,15 @@
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.ui.compose.Icon
 import com.android.systemui.common.ui.compose.load
+import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.qs.panels.shared.model.SizedTile
 import com.android.systemui.qs.panels.shared.model.SizedTileImpl
+import com.android.systemui.qs.panels.ui.compose.TileDefaults.longPressLabel
 import com.android.systemui.qs.panels.ui.model.GridCell
 import com.android.systemui.qs.panels.ui.model.SpacerGridCell
 import com.android.systemui.qs.panels.ui.model.TileGridCell
+import com.android.systemui.qs.panels.ui.viewmodel.AccessibilityUiState
 import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.TileUiState
 import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
@@ -126,15 +137,15 @@
 
 object TileType
 
+private const val TEST_TAG_SMALL = "qs_tile_small"
+private const val TEST_TAG_LARGE = "qs_tile_large"
+private const val TEST_TAG_TOGGLE = "qs_tile_toggle_target"
+
 @Composable
-fun Tile(
-    tile: TileViewModel,
-    iconOnly: Boolean,
-    showLabels: Boolean = false,
-    modifier: Modifier,
-) {
+fun Tile(tile: TileViewModel, iconOnly: Boolean, showLabels: Boolean = false, modifier: Modifier) {
     val state by tile.state.collectAsStateWithLifecycle(tile.currentState)
-    val uiState = remember(state) { state.toUiState() }
+    val resources = resources()
+    val uiState = remember(state, resources) { state.toUiState(resources) }
     val colors = TileDefaults.getColorForState(uiState)
 
     // TODO(b/361789146): Draw the shapes instead of clipping
@@ -150,6 +161,7 @@
         onClick = tile::onClick,
         onLongClick = tile::onLongClick,
         modifier = modifier.height(tileHeight()),
+        uiState = uiState,
     ) {
         val icon = getTileIcon(icon = uiState.icon)
         if (iconOnly) {
@@ -169,6 +181,7 @@
                     }
                 },
                 onLongClick = { tile.onLongClick(it) },
+                accessibilityUiState = uiState.accessibilityUiState,
             )
         }
     }
@@ -185,6 +198,7 @@
     onClick: (Expandable) -> Unit = {},
     onLongClick: (Expandable) -> Unit = {},
     modifier: Modifier = Modifier,
+    uiState: TileUiState? = null,
     content: @Composable BoxScope.(Expandable) -> Unit,
 ) {
     Column(
@@ -194,7 +208,7 @@
         modifier = modifier,
     ) {
         val backgroundColor =
-            if (iconOnly) {
+            if (iconOnly || uiState?.handlesSecondaryClick != true) {
                 colors.iconBackground
             } else {
                 colors.background
@@ -202,18 +216,43 @@
         Expandable(
             color = backgroundColor,
             shape = shape,
-            modifier = Modifier.height(tileHeight()).clip(shape)
+            modifier = Modifier.height(tileHeight()).clip(shape),
         ) {
+            val longPressLabel = longPressLabel()
             Box(
                 modifier =
                     Modifier.fillMaxSize()
                         .thenIf(clickEnabled) {
                             Modifier.combinedClickable(
                                 onClick = { onClick(it) },
-                                onLongClick = { onLongClick(it) }
+                                onLongClick = { onLongClick(it) },
+                                onClickLabel = uiState?.accessibilityUiState?.clickLabel,
+                                onLongClickLabel = longPressLabel,
                             )
                         }
-                        .tilePadding(),
+                        .thenIf(uiState != null) {
+                            uiState as TileUiState
+                            Modifier.semantics {
+                                    role = uiState.accessibilityUiState.accessibilityRole
+                                    if (
+                                        uiState.accessibilityUiState.accessibilityRole ==
+                                            Role.Switch
+                                    ) {
+                                        uiState.accessibilityUiState.toggleableState?.let {
+                                            toggleableState = it
+                                        }
+                                    }
+                                    stateDescription = uiState.accessibilityUiState.stateDescription
+                                }
+                                .sysuiResTag(if (iconOnly) TEST_TAG_SMALL else TEST_TAG_LARGE)
+                                .thenIf(iconOnly) {
+                                    Modifier.semantics {
+                                        contentDescription =
+                                            uiState.accessibilityUiState.contentDescription
+                                    }
+                                }
+                        }
+                        .tilePadding()
             ) {
                 content(it)
             }
@@ -238,21 +277,39 @@
     icon: Icon,
     colors: TileColors,
     iconShape: Shape,
+    accessibilityUiState: AccessibilityUiState? = null,
     toggleClickSupported: Boolean = false,
     onClick: () -> Unit = {},
     onLongClick: () -> Unit = {},
 ) {
     Row(
         verticalAlignment = Alignment.CenterVertically,
-        horizontalArrangement = tileHorizontalArrangement()
+        horizontalArrangement = tileHorizontalArrangement(),
     ) {
         // Icon
+        val longPressLabel = longPressLabel()
         Box(
             modifier =
                 Modifier.size(TileDefaults.ToggleTargetSize).thenIf(toggleClickSupported) {
                     Modifier.clip(iconShape)
                         .background(colors.iconBackground, { 1f })
-                        .combinedClickable(onClick = onClick, onLongClick = onLongClick)
+                        .combinedClickable(
+                            onClick = onClick,
+                            onLongClick = onLongClick,
+                            onLongClickLabel = longPressLabel,
+                        )
+                        .thenIf(accessibilityUiState != null) {
+                            accessibilityUiState as AccessibilityUiState
+                            Modifier.semantics {
+                                    contentDescription = accessibilityUiState.contentDescription
+                                    stateDescription = accessibilityUiState.stateDescription
+                                    accessibilityUiState.toggleableState?.let {
+                                        toggleableState = it
+                                    }
+                                    role = Role.Switch
+                                }
+                                .sysuiResTag(TEST_TAG_TOGGLE)
+                        }
                 }
         ) {
             TileIcon(icon = icon, color = colors.icon, modifier = Modifier.align(Alignment.Center))
@@ -260,16 +317,19 @@
 
         // Labels
         Column(verticalArrangement = Arrangement.Center, modifier = Modifier.fillMaxHeight()) {
-            Text(
-                label,
-                color = colors.label,
-                modifier = Modifier.tileMarquee(),
-            )
+            Text(label, color = colors.label, modifier = Modifier.tileMarquee())
             if (!TextUtils.isEmpty(secondaryLabel)) {
                 Text(
                     secondaryLabel ?: "",
                     color = colors.secondaryLabel,
-                    modifier = Modifier.tileMarquee(),
+                    modifier =
+                        Modifier.tileMarquee().thenIf(
+                            accessibilityUiState
+                                ?.stateDescription
+                                ?.contains(secondaryLabel ?: "") == true
+                        ) {
+                            Modifier.clearAndSetSemantics {}
+                        },
                 )
             }
         }
@@ -277,10 +337,7 @@
 }
 
 private fun Modifier.tileMarquee(): Modifier {
-    return basicMarquee(
-        iterations = 1,
-        initialDelayMillis = 200,
-    )
+    return basicMarquee(iterations = 1, initialDelayMillis = 200)
 }
 
 @Composable
@@ -320,11 +377,11 @@
         Column(
             verticalArrangement =
                 spacedBy(dimensionResource(id = R.dimen.qs_label_container_margin)),
-            modifier = modifier.fillMaxSize().verticalScroll(rememberScrollState())
+            modifier = modifier.fillMaxSize().verticalScroll(rememberScrollState()),
         ) {
             AnimatedContent(
                 targetState = currentListState.dragInProgress,
-                modifier = Modifier.wrapContentSize()
+                modifier = Modifier.wrapContentSize(),
             ) { dragIsInProgress ->
                 EditGridHeader(Modifier.dragAndDropRemoveZone(currentListState, onRemoveTile)) {
                     if (dragIsInProgress) {
@@ -348,12 +405,12 @@
             AnimatedVisibility(
                 visible = !currentListState.dragInProgress,
                 enter = fadeIn(),
-                exit = fadeOut()
+                exit = fadeOut(),
             ) {
                 Column(
                     verticalArrangement =
                         spacedBy(dimensionResource(id = R.dimen.qs_label_container_margin)),
-                    modifier = modifier.fillMaxSize()
+                    modifier = modifier.fillMaxSize(),
                 ) {
                     EditGridHeader { Text(text = "Hold and drag to add tiles.") }
 
@@ -381,14 +438,14 @@
 @Composable
 private fun EditGridHeader(
     modifier: Modifier = Modifier,
-    content: @Composable BoxScope.() -> Unit
+    content: @Composable BoxScope.() -> Unit,
 ) {
     CompositionLocalProvider(
         LocalContentColor provides MaterialTheme.colorScheme.onBackground.copy(alpha = .5f)
     ) {
         Box(
             contentAlignment = Alignment.Center,
-            modifier = modifier.fillMaxWidth().height(EditModeTileDefaults.EditGridHeaderHeight)
+            modifier = modifier.fillMaxWidth().height(EditModeTileDefaults.EditGridHeaderHeight),
         ) {
             content()
         }
@@ -403,7 +460,7 @@
         modifier =
             Modifier.fillMaxHeight()
                 .border(1.dp, LocalContentColor.current, shape = CircleShape)
-                .padding(10.dp)
+                .padding(10.dp),
     ) {
         Icon(imageVector = Icons.Default.Clear, contentDescription = null)
         Text(text = "Remove")
@@ -454,7 +511,7 @@
                         gridContentOffset = coordinates.positionInRoot()
                     }
                     .testTag(CURRENT_TILES_GRID_TEST_TAG),
-            columns = GridCells.Fixed(columns)
+            columns = GridCells.Fixed(columns),
         ) {
             editTiles(
                 listState.tiles,
@@ -488,7 +545,7 @@
     // Available tiles
     TileLazyGrid(
         modifier = Modifier.height(availableGridHeight).testTag(AVAILABLE_TILES_GRID_TEST_TAG),
-        columns = GridCells.Fixed(columns)
+        columns = GridCells.Fixed(columns),
     ) {
         groupedTiles.forEach { category, tiles ->
             stickyHeader {
@@ -498,7 +555,7 @@
                     color = labelColors.label,
                     modifier =
                         Modifier.background(Color.Black)
-                            .padding(start = 16.dp, bottom = 8.dp, top = 8.dp)
+                            .padding(start = 16.dp, bottom = 8.dp, top = 8.dp),
                 )
             }
             editTiles(
@@ -542,7 +599,7 @@
         count = cells.size,
         key = { cells[it].key(it, dragAndDropState) },
         span = { cells[it].span },
-        contentType = { TileType }
+        contentType = { TileType },
     ) { index ->
         when (val cell = cells[index]) {
             is TileGridCell ->
@@ -552,7 +609,7 @@
                         Modifier.background(
                                 color = MaterialTheme.colorScheme.secondary,
                                 alpha = { EditModeTileDefaults.PLACEHOLDER_ALPHA },
-                                shape = RoundedCornerShape(TileDefaults.InactiveCornerRadius)
+                                shape = RoundedCornerShape(TileDefaults.InactiveCornerRadius),
                             )
                             .animateItem()
                     )
@@ -565,7 +622,7 @@
                         onClick = onClick,
                         onResize = onResize,
                         showLabels = showLabels,
-                        indicatePosition = indicatePosition
+                        indicatePosition = indicatePosition,
                     )
                 }
             is SpacerGridCell -> SpacerGridCell()
@@ -604,7 +661,7 @@
         modifier =
             Modifier.height(tileHeight)
                 .animateItem()
-                .semantics {
+                .semantics(mergeDescendants = true) {
                     onClick(onClickActionName) { false }
                     this.stateDescription = stateDescription
                 }
@@ -613,7 +670,7 @@
                     onClick,
                     onResize,
                     dragAndDropState,
-                )
+                ),
     )
 }
 
@@ -645,7 +702,7 @@
             TileIcon(
                 icon = tileViewModel.icon,
                 color = colors.icon,
-                modifier = Modifier.align(Alignment.Center)
+                modifier = Modifier.align(Alignment.Center),
             )
         } else {
             LargeTileContent(
@@ -694,11 +751,7 @@
             }
         }
     if (loadedDrawable !is Animatable) {
-        Icon(
-            icon = icon,
-            tint = color,
-            modifier = iconModifier,
-        )
+        Icon(icon = icon, tint = color, modifier = iconModifier)
     } else if (icon is Icon.Resource) {
         val image = AnimatedImageVector.animatedVectorResource(id = icon.res)
         val painter =
@@ -716,7 +769,7 @@
             painter = painter,
             contentDescription = icon.contentDescription?.load(),
             colorFilter = ColorFilter.tint(color = color),
-            modifier = iconModifier
+            modifier = iconModifier,
         )
     }
 }
@@ -765,6 +818,8 @@
     val TileHeight = 72.dp
     val IconTileWithLabelHeight = 140.dp
 
+    @Composable fun longPressLabel() = stringResource(id = R.string.accessibility_long_click_tile)
+
     /** An active tile without dual target uses the active color as background */
     @Composable
     fun activeTileColors(): TileColors =
@@ -850,7 +905,7 @@
                     } else {
                         InactiveCornerRadius
                     },
-                label = label
+                label = label,
             )
         return RoundedCornerShape(animatedCornerRadius)
     }
@@ -858,3 +913,14 @@
 
 private const val CURRENT_TILES_GRID_TEST_TAG = "CurrentTilesGrid"
 private const val AVAILABLE_TILES_GRID_TEST_TAG = "AvailableTilesGrid"
+
+/**
+ * A composable function that returns the [Resources]. It will be recomposed when [Configuration]
+ * gets updated.
+ */
+@Composable
+@ReadOnlyComposable
+private fun resources(): Resources {
+    LocalConfiguration.current
+    return LocalContext.current.resources
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt
index 45051fe..aa42080 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt
@@ -16,8 +16,16 @@
 
 package com.android.systemui.qs.panels.ui.viewmodel
 
+import android.content.res.Resources
+import android.service.quicksettings.Tile
+import android.text.TextUtils
+import android.widget.Switch
 import androidx.compose.runtime.Immutable
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.state.ToggleableState
 import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.tileimpl.SubtitleArrayMapping
+import com.android.systemui.res.R
 import java.util.function.Supplier
 
 @Immutable
@@ -27,14 +35,78 @@
     val state: Int,
     val handlesSecondaryClick: Boolean,
     val icon: Supplier<QSTile.Icon?>,
+    val accessibilityUiState: AccessibilityUiState,
 )
 
-fun QSTile.State.toUiState(): TileUiState {
+data class AccessibilityUiState(
+    val contentDescription: String,
+    val stateDescription: String,
+    val accessibilityRole: Role,
+    val toggleableState: ToggleableState? = null,
+    val clickLabel: String? = null,
+)
+
+fun QSTile.State.toUiState(resources: Resources): TileUiState {
+    val accessibilityRole =
+        if (expandedAccessibilityClassName == Switch::class.java.name && !handlesSecondaryClick) {
+            Role.Switch
+        } else {
+            Role.Button
+        }
+    // State handling and description
+    val stateDescription = StringBuilder()
+    val stateText =
+        if (accessibilityRole == Role.Switch || state == Tile.STATE_UNAVAILABLE) {
+            getStateText(resources)
+        } else {
+            ""
+        }
+    val secondaryLabel = getSecondaryLabel(stateText)
+    if (!TextUtils.isEmpty(stateText)) {
+        stateDescription.append(stateText)
+    }
+    if (disabledByPolicy && state != Tile.STATE_UNAVAILABLE) {
+        stateDescription.append(", ")
+        stateDescription.append(getUnavailableText(spec, resources))
+    }
+    if (
+        !TextUtils.isEmpty(this.stateDescription) &&
+            !stateDescription.contains(this.stateDescription!!)
+    ) {
+        stateDescription.append(", ")
+        stateDescription.append(this.stateDescription)
+    }
+    val toggleableState =
+        if (accessibilityRole == Role.Switch || handlesSecondaryClick) {
+            ToggleableState(state == Tile.STATE_ACTIVE)
+        } else {
+            null
+        }
     return TileUiState(
-        label?.toString() ?: "",
-        secondaryLabel?.toString() ?: "",
-        state,
-        handlesSecondaryClick,
-        icon?.let { Supplier { icon } } ?: iconSupplier ?: Supplier { null },
+        label = label?.toString() ?: "",
+        secondaryLabel = secondaryLabel?.toString() ?: "",
+        state = if (disabledByPolicy) Tile.STATE_UNAVAILABLE else state,
+        handlesSecondaryClick = handlesSecondaryClick,
+        icon = icon?.let { Supplier { icon } } ?: iconSupplier ?: Supplier { null },
+        AccessibilityUiState(
+            contentDescription?.toString() ?: "",
+            stateDescription.toString(),
+            accessibilityRole,
+            toggleableState,
+            resources
+                .getString(R.string.accessibility_tile_disabled_by_policy_action_description)
+                .takeIf { disabledByPolicy },
+        ),
     )
 }
+
+private fun QSTile.State.getStateText(resources: Resources): CharSequence {
+    val arrayResId = SubtitleArrayMapping.getSubtitleId(spec)
+    val array = resources.getStringArray(arrayResId)
+    return array[state]
+}
+
+private fun getUnavailableText(spec: String?, resources: Resources): String {
+    val arrayResId = SubtitleArrayMapping.getSubtitleId(spec)
+    return resources.getStringArray(arrayResId)[Tile.STATE_UNAVAILABLE]
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 7ceb786..7bff827 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -32,7 +32,7 @@
 import android.service.quicksettings.Tile;
 import android.text.TextUtils;
 import android.util.Log;
-import android.widget.Switch;
+import android.widget.Button;
 
 import androidx.annotation.VisibleForTesting;
 
@@ -59,13 +59,13 @@
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.policy.BluetoothController;
 
-import kotlinx.coroutines.Job;
-
 import java.util.List;
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 
+import kotlinx.coroutines.Job;
+
 /** Quick settings tile: Bluetooth **/
 public class BluetoothTile extends QSTileImpl<BooleanState> {
 
@@ -147,6 +147,8 @@
         }
     }
 
+
+
     @Override
     public Intent getLongClickIntent() {
         return new Intent(Settings.ACTION_BLUETOOTH_SETTINGS);
@@ -221,7 +223,7 @@
             state.state = Tile.STATE_INACTIVE;
         }
 
-        state.expandedAccessibilityClassName = Switch.class.getName();
+        state.expandedAccessibilityClassName = Button.class.getName();
         state.forceExpandIcon = mFeatureFlags.isEnabled(Flags.BLUETOOTH_QS_TILE_DIALOG);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
index 078698c..7606293 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
@@ -55,7 +55,7 @@
     qsLogger: QSLogger,
     private val keyguardStateController: KeyguardStateController,
     private val dialogTransitionAnimator: DialogTransitionAnimator,
-    private val fontScalingDialogDelegateProvider: Provider<FontScalingDialogDelegate>
+    private val fontScalingDialogDelegateProvider: Provider<FontScalingDialogDelegate>,
 ) :
     QSTileImpl<QSTile.State?>(
         host,
@@ -66,7 +66,7 @@
         metricsLogger,
         statusBarStateController,
         activityStarter,
-        qsLogger
+        qsLogger,
     ) {
     private val icon = ResourceIcon.get(R.drawable.ic_qs_font_scaling)
 
@@ -86,7 +86,7 @@
                     expandable?.dialogTransitionController(
                         DialogCuj(
                             InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
-                            INTERACTION_JANK_TAG
+                            INTERACTION_JANK_TAG,
                         )
                     )
                 controller?.let { dialogTransitionAnimator.show(dialog, controller) }
@@ -102,7 +102,7 @@
                 /* cancelAction= */ null,
                 /* dismissShade= */ true,
                 /* afterKeyguardGone= */ true,
-                /* deferred= */ false
+                /* deferred= */ false,
             )
         }
     }
@@ -110,6 +110,7 @@
     override fun handleUpdateState(state: QSTile.State?, arg: Any?) {
         state?.label = mContext.getString(R.string.quick_settings_font_scaling_label)
         state?.icon = icon
+        state?.contentDescription = state?.label
     }
 
     override fun getLongClickIntent(): Intent? {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt
index 61c4c8c..31519a9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt
@@ -18,24 +18,41 @@
 
 import com.android.compose.animation.scene.Back
 import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
 import com.android.compose.animation.scene.UserAction
 import com.android.compose.animation.scene.UserActionResult
+import com.android.compose.animation.scene.UserActionResult.HideOverlay
+import com.android.compose.animation.scene.UserActionResult.ReplaceByOverlay
 import com.android.systemui.scene.shared.model.Overlays
+import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge
 import com.android.systemui.scene.ui.viewmodel.UserActionsViewModel
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
+import kotlinx.coroutines.flow.map
 
 /** Models the UI state for the user actions for navigating to other scenes or overlays. */
-class QuickSettingsShadeOverlayActionsViewModel @AssistedInject constructor() :
+class QuickSettingsShadeOverlayActionsViewModel
+@AssistedInject
+constructor(private val containerViewModel: QuickSettingsContainerViewModel) :
     UserActionsViewModel() {
 
     override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
-        setActions(
-            buildMap {
-                put(Swipe.Up, UserActionResult.HideOverlay(Overlays.QuickSettingsShade))
-                put(Back, UserActionResult.HideOverlay(Overlays.QuickSettingsShade))
+        containerViewModel.editModeViewModel.isEditing
+            .map { isEditing ->
+                buildMap {
+                    put(Swipe.Up, HideOverlay(Overlays.QuickSettingsShade))
+                    // When editing, back should go back to QS from edit mode (i.e. remain in the
+                    // same overlay).
+                    if (!isEditing) {
+                        put(Back, HideOverlay(Overlays.QuickSettingsShade))
+                    }
+                    put(
+                        Swipe(SwipeDirection.Down, fromSource = SceneContainerEdge.TopLeft),
+                        ReplaceByOverlay(Overlays.NotificationsShade),
+                    )
+                }
             }
-        )
+            .collect { actions -> setActions(actions) }
     }
 
     @AssistedFactory
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index a402a9d..ac49e91 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -101,10 +101,10 @@
 import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
 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.settings.DisplayTracker;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 import com.android.systemui.shared.system.QuickStepContract;
@@ -158,6 +158,7 @@
     private final ScreenPinningRequest mScreenPinningRequest;
     private final NotificationShadeWindowController mStatusBarWinController;
     private final Provider<SceneInteractor> mSceneInteractor;
+    private final Provider<ShadeInteractor> mShadeInteractor;
 
     private final KeyboardTouchpadEduStatsInteractor mKeyboardTouchpadEduStatsInteractor;
 
@@ -246,11 +247,8 @@
                             }
                         } else if (action == ACTION_UP) {
                             // Gesture was too short to be picked up by scene container touch
-                            // handling; programmatically start the transition to shade scene.
-                            mSceneInteractor.get().changeScene(
-                                    SceneFamilies.NotifShade,
-                                    "short launcher swipe"
-                            );
+                            // handling; programmatically start the transition to the shade.
+                            mShadeInteractor.get().expandNotificationShade("short launcher swipe");
                         }
                     }
                     event.recycle();
@@ -267,10 +265,7 @@
                         mSceneInteractor.get().onRemoteUserInputStarted(
                                 "trackpad swipe");
                     } else if (action == ACTION_UP) {
-                        mSceneInteractor.get().changeScene(
-                                SceneFamilies.NotifShade,
-                                "short trackpad swipe"
-                        );
+                        mShadeInteractor.get().expandNotificationShade("short trackpad swipe");
                     }
                     mStatusBarWinController.getWindowRootView().dispatchTouchEvent(event);
                 } else {
@@ -652,6 +647,7 @@
             NotificationShadeWindowController statusBarWinController,
             SysUiState sysUiState,
             Provider<SceneInteractor> sceneInteractor,
+            Provider<ShadeInteractor> shadeInteractor,
             UserTracker userTracker,
             UserManager userManager,
             WakefulnessLifecycle wakefulnessLifecycle,
@@ -688,6 +684,7 @@
         mScreenPinningRequest = screenPinningRequest;
         mStatusBarWinController = statusBarWinController;
         mSceneInteractor = sceneInteractor;
+        mShadeInteractor = shadeInteractor;
         mUserTracker = userTracker;
         mConnectionBackoffAttempts = 0;
         mRecentsComponentName = ComponentName.unflattenFromString(context.getString(
diff --git a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
index 1aa982f..e441a23 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
@@ -22,8 +22,6 @@
 import com.android.systemui.scene.domain.SceneDomainModule
 import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
 import com.android.systemui.scene.domain.resolver.HomeSceneFamilyResolverModule
-import com.android.systemui.scene.domain.resolver.NotifShadeSceneFamilyResolverModule
-import com.android.systemui.scene.domain.resolver.QuickSettingsSceneFamilyResolverModule
 import com.android.systemui.scene.domain.startable.KeyguardStateCallbackStartable
 import com.android.systemui.scene.domain.startable.SceneContainerStartable
 import com.android.systemui.scene.domain.startable.ScrimStartable
@@ -47,7 +45,6 @@
             EmptySceneModule::class,
             GoneSceneModule::class,
             NotificationsShadeOverlayModule::class,
-            NotificationsShadeSceneModule::class,
             NotificationsShadeSessionModule::class,
             QuickSettingsShadeOverlayModule::class,
             QuickSettingsSceneModule::class,
@@ -56,8 +53,6 @@
 
             // List SceneResolver modules for supported SceneFamilies
             HomeSceneFamilyResolverModule::class,
-            NotifShadeSceneFamilyResolverModule::class,
-            QuickSettingsSceneFamilyResolverModule::class,
         ]
 )
 interface KeyguardlessSceneContainerFrameworkModule {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
index 7f0cf86..a89f752 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
@@ -22,8 +22,6 @@
 import com.android.systemui.scene.domain.SceneDomainModule
 import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
 import com.android.systemui.scene.domain.resolver.HomeSceneFamilyResolverModule
-import com.android.systemui.scene.domain.resolver.NotifShadeSceneFamilyResolverModule
-import com.android.systemui.scene.domain.resolver.QuickSettingsSceneFamilyResolverModule
 import com.android.systemui.scene.domain.startable.KeyguardStateCallbackStartable
 import com.android.systemui.scene.domain.startable.SceneContainerStartable
 import com.android.systemui.scene.domain.startable.ScrimStartable
@@ -52,16 +50,12 @@
             QuickSettingsSceneModule::class,
             ShadeSceneModule::class,
             QuickSettingsShadeOverlayModule::class,
-            QuickSettingsShadeSceneModule::class,
             NotificationsShadeOverlayModule::class,
-            NotificationsShadeSceneModule::class,
             NotificationsShadeSessionModule::class,
             SceneDomainModule::class,
 
             // List SceneResolver modules for supported SceneFamilies
             HomeSceneFamilyResolverModule::class,
-            NotifShadeSceneFamilyResolverModule::class,
-            QuickSettingsSceneFamilyResolverModule::class,
         ]
 )
 interface SceneContainerFrameworkModule {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt
index 429b47b..667827a 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt
@@ -16,13 +16,14 @@
 
 package com.android.systemui.scene.domain.interactor
 
+import com.android.compose.animation.scene.ContentKey
 import com.android.compose.animation.scene.ObservableTransitionState
-import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.domain.interactor.KeyguardOcclusionInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.scene.shared.model.Overlays
 import com.android.systemui.scene.shared.model.Scenes
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -117,28 +118,30 @@
     private val ObservableTransitionState.canBeOccluded: Boolean
         get() =
             when (this) {
-                is ObservableTransitionState.Idle -> currentScene.canBeOccluded
-                is ObservableTransitionState.Transition.ChangeScene ->
-                    fromScene.canBeOccluded && toScene.canBeOccluded
-                is ObservableTransitionState.Transition.ReplaceOverlay,
-                is ObservableTransitionState.Transition.ShowOrHideOverlay ->
-                    TODO("b/359173565: Handle overlay transitions")
+                is ObservableTransitionState.Idle ->
+                    currentOverlays.all { it.canBeOccluded } && currentScene.canBeOccluded
+                is ObservableTransitionState.Transition ->
+                    // TODO(b/356596436): Should also verify currentOverlays.isEmpty(), but
+                    //  currentOverlays is a Flow and we need a state.
+                    fromContent.canBeOccluded && toContent.canBeOccluded
             }
 
     /**
-     * Whether the scene can be occluded by a "show when locked" activity. Some scenes should, on
+     * Whether the content can be occluded by a "show when locked" activity. Some content should, on
      * principle not be occlude-able because they render as if they are expanding on top of the
      * occluding activity.
      */
-    private val SceneKey.canBeOccluded: Boolean
+    private val ContentKey.canBeOccluded: Boolean
         get() =
             when (this) {
+                Overlays.NotificationsShade -> false
+                Overlays.QuickSettingsShade -> false
                 Scenes.Bouncer -> false
                 Scenes.Communal -> true
                 Scenes.Gone -> true
                 Scenes.Lockscreen -> true
                 Scenes.QuickSettings -> false
                 Scenes.Shade -> false
-                else -> error("SceneKey \"$this\" doesn't have a mapping for canBeOccluded!")
+                else -> error("ContentKey \"$this\" doesn't have a mapping for canBeOccluded!")
             }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index 0d24adc..f20e5a5 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -120,21 +120,18 @@
             )
 
     /**
-     * The key of the scene that the UI is currently transitioning to or `null` if there is no
+     * The key of the content that the UI is currently transitioning to or `null` if there is no
      * active transition at the moment.
      *
      * This is a convenience wrapper around [transitionState], meant for flow-challenged consumers
      * like Java code.
      */
-    val transitioningTo: StateFlow<SceneKey?> =
+    val transitioningTo: StateFlow<ContentKey?> =
         transitionState
             .map { state ->
                 when (state) {
                     is ObservableTransitionState.Idle -> null
-                    is ObservableTransitionState.Transition.ChangeScene -> state.toScene
-                    is ObservableTransitionState.Transition.ShowOrHideOverlay,
-                    is ObservableTransitionState.Transition.ReplaceOverlay ->
-                        TODO("b/359173565: Handle overlay transitions")
+                    is ObservableTransitionState.Transition -> state.toContent
                 }
             }
             .stateIn(
@@ -160,15 +157,14 @@
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.WhileSubscribed(),
-                initialValue = false
+                initialValue = false,
             )
 
     /** Whether the scene container is visible. */
     val isVisible: StateFlow<Boolean> =
-        combine(
-                repository.isVisible,
-                repository.isRemoteUserInputOngoing,
-            ) { isVisible, isRemoteUserInteractionOngoing ->
+        combine(repository.isVisible, repository.isRemoteUserInputOngoing) {
+                isVisible,
+                isRemoteUserInteractionOngoing ->
                 isVisibleInternal(
                     raw = isVisible,
                     isRemoteUserInputOngoing = isRemoteUserInteractionOngoing,
@@ -177,7 +173,7 @@
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.WhileSubscribed(),
-                initialValue = isVisibleInternal()
+                initialValue = isVisibleInternal(),
             )
 
     /** Whether there's an ongoing remotely-initiated user interaction. */
@@ -259,10 +255,7 @@
      * The change is instantaneous and not animated; it will be observable in the next frame and
      * there will be no transition animation.
      */
-    fun snapToScene(
-        toScene: SceneKey,
-        loggingReason: String,
-    ) {
+    fun snapToScene(toScene: SceneKey, loggingReason: String) {
         val currentSceneKey = currentScene.value
         val resolvedScene =
             sceneFamilyResolvers.get()[toScene]?.let { familyResolver ->
@@ -313,15 +306,9 @@
             return
         }
 
-        logger.logOverlayChangeRequested(
-            to = overlay,
-            reason = loggingReason,
-        )
+        logger.logOverlayChangeRequested(to = overlay, reason = loggingReason)
 
-        repository.showOverlay(
-            overlay = overlay,
-            transitionKey = transitionKey,
-        )
+        repository.showOverlay(overlay = overlay, transitionKey = transitionKey)
     }
 
     /**
@@ -345,15 +332,9 @@
             return
         }
 
-        logger.logOverlayChangeRequested(
-            from = overlay,
-            reason = loggingReason,
-        )
+        logger.logOverlayChangeRequested(from = overlay, reason = loggingReason)
 
-        repository.hideOverlay(
-            overlay = overlay,
-            transitionKey = transitionKey,
-        )
+        repository.hideOverlay(overlay = overlay, transitionKey = transitionKey)
     }
 
     /**
@@ -378,17 +359,9 @@
             return
         }
 
-        logger.logOverlayChangeRequested(
-            from = from,
-            to = to,
-            reason = loggingReason,
-        )
+        logger.logOverlayChangeRequested(from = from, to = to, reason = loggingReason)
 
-        repository.replaceOverlay(
-            from = from,
-            to = to,
-            transitionKey = transitionKey,
-        )
+        repository.replaceOverlay(from = from, to = to, transitionKey = transitionKey)
     }
 
     /**
@@ -405,11 +378,7 @@
             return
         }
 
-        logger.logVisibilityChange(
-            from = wasVisible,
-            to = isVisible,
-            reason = loggingReason,
-        )
+        logger.logVisibilityChange(from = wasVisible, to = isVisible, reason = loggingReason)
         return repository.setVisible(isVisible)
     }
 
@@ -491,11 +460,7 @@
      * @param loggingReason The reason why the transition is requested, for logging purposes
      * @return `true` if the scene change is valid; `false` if it shouldn't happen
      */
-    private fun validateSceneChange(
-        from: SceneKey,
-        to: SceneKey,
-        loggingReason: String,
-    ): Boolean {
+    private fun validateSceneChange(from: SceneKey, to: SceneKey, loggingReason: String): Boolean {
         if (to !in repository.allContentKeys) {
             return false
         }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/NotifShadeSceneFamilyResolver.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/NotifShadeSceneFamilyResolver.kt
deleted file mode 100644
index a313273..0000000
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/NotifShadeSceneFamilyResolver.kt
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.scene.domain.resolver
-
-import com.android.compose.animation.scene.SceneKey
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.scene.shared.model.SceneFamilies
-import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
-import com.android.systemui.shade.shared.model.ShadeMode
-import dagger.Binds
-import dagger.Module
-import dagger.multibindings.IntoSet
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
-
-@SysUISingleton
-class NotifShadeSceneFamilyResolver
-@Inject
-constructor(
-    @Application applicationScope: CoroutineScope,
-    shadeModeInteractor: ShadeModeInteractor,
-) : SceneResolver {
-    override val targetFamily: SceneKey = SceneFamilies.NotifShade
-
-    override val resolvedScene: StateFlow<SceneKey> =
-        shadeModeInteractor.shadeMode
-            .map(::notifShadeScene)
-            .stateIn(
-                applicationScope,
-                started = SharingStarted.Eagerly,
-                initialValue = notifShadeScene(shadeModeInteractor.shadeMode.value),
-            )
-
-    override fun includesScene(scene: SceneKey): Boolean = scene in notifShadeScenes
-
-    private fun notifShadeScene(shadeMode: ShadeMode) =
-        when (shadeMode) {
-            is ShadeMode.Single -> Scenes.Shade
-            is ShadeMode.Dual -> Scenes.NotificationsShade
-            is ShadeMode.Split -> Scenes.Shade
-        }
-
-    companion object {
-        val notifShadeScenes = setOf(Scenes.NotificationsShade, Scenes.Shade)
-    }
-}
-
-@Module
-interface NotifShadeSceneFamilyResolverModule {
-    @Binds @IntoSet fun bindResolver(interactor: NotifShadeSceneFamilyResolver): SceneResolver
-}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/QuickSettingsSceneFamilyResolver.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/QuickSettingsSceneFamilyResolver.kt
deleted file mode 100644
index 923e712a..0000000
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/QuickSettingsSceneFamilyResolver.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.scene.domain.resolver
-
-import com.android.compose.animation.scene.SceneKey
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.scene.shared.model.SceneFamilies
-import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
-import com.android.systemui.shade.shared.model.ShadeMode
-import dagger.Binds
-import dagger.Module
-import dagger.multibindings.IntoSet
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
-
-@SysUISingleton
-class QuickSettingsSceneFamilyResolver
-@Inject
-constructor(
-    @Application applicationScope: CoroutineScope,
-    shadeModeInteractor: ShadeModeInteractor,
-) : SceneResolver {
-    override val targetFamily: SceneKey = SceneFamilies.QuickSettings
-
-    override val resolvedScene: StateFlow<SceneKey> =
-        shadeModeInteractor.shadeMode
-            .map(::quickSettingsScene)
-            .stateIn(
-                applicationScope,
-                started = SharingStarted.Eagerly,
-                initialValue = quickSettingsScene(shadeModeInteractor.shadeMode.value),
-            )
-
-    override fun includesScene(scene: SceneKey): Boolean = scene in quickSettingsScenes
-
-    private fun quickSettingsScene(shadeMode: ShadeMode) =
-        when (shadeMode) {
-            is ShadeMode.Single -> Scenes.QuickSettings
-            is ShadeMode.Dual -> Scenes.QuickSettingsShade
-            is ShadeMode.Split -> Scenes.Shade
-        }
-
-    companion object {
-        val quickSettingsScenes =
-            setOf(Scenes.QuickSettings, Scenes.QuickSettingsShade, Scenes.Shade)
-    }
-}
-
-@Module
-interface QuickSettingsSceneFamilyResolverModule {
-    @Binds @IntoSet fun bindResolver(interactor: QuickSettingsSceneFamilyResolver): SceneResolver
-}
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 b11d8ed..e11ffcc 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
@@ -107,6 +107,7 @@
  * Hooks up business logic that manipulates the state of the [SceneInteractor] for the system UI
  * scene container based on state from other systems.
  */
+@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class SceneContainerStartable
 @Inject
@@ -280,6 +281,8 @@
         applicationScope.launch {
             sceneInteractor.currentScene.collectLatest { currentScene ->
                 if (currentScene == Scenes.Lockscreen) {
+                    // Wait for the screen to be on
+                    powerInteractor.isAwake.first { it }
                     // Wait for surface to become visible
                     windowMgrLockscreenVisInteractor.surfaceBehindVisibility.first { it }
                     // Make sure the device is actually unlocked before force-changing the scene
@@ -406,8 +409,7 @@
                         }
                         isOnPrimaryBouncer -> {
                             // When the device becomes unlocked in primary Bouncer,
-                            // notify dismiss succeeded and
-                            // go to previous scene or Gone.
+                            // notify dismiss succeeded and go to previous scene or Gone.
                             dismissCallbackRegistry.notifyDismissSucceeded()
                             if (
                                 previousScene.value == Scenes.Lockscreen ||
@@ -594,12 +596,12 @@
             combine(
                     sceneInteractor.transitionState
                         .mapNotNull { it as? ObservableTransitionState.Idle }
-                        .map { it.currentScene }
                         .distinctUntilChanged(),
                     occlusionInteractor.invisibleDueToOcclusion,
-                ) { sceneKey, invisibleDueToOcclusion ->
+                ) { idleState, invisibleDueToOcclusion ->
                     SceneContainerPlugin.SceneContainerPluginState(
-                        scene = sceneKey,
+                        scene = idleState.currentScene,
+                        overlays = idleState.currentOverlays,
                         invisibleDueToOcclusion = invisibleDueToOcclusion,
                     )
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scenes.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scenes.kt
index 115d664..82b4b1c 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scenes.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scenes.kt
@@ -43,22 +43,6 @@
     @JvmField val Lockscreen = SceneKey("lockscreen")
 
     /**
-     * The notifications shade scene primarily shows a scrollable list of notifications as an
-     * overlay UI.
-     *
-     * It's used only in the dual shade configuration, where there are two separate shades: one for
-     * notifications (this scene) and another for [QuickSettingsShade].
-     *
-     * It's not used in the single/accordion configuration (swipe down once to reveal the shade,
-     * swipe down again the to expand quick settings) or in the "split" shade configuration (on
-     * large screens or unfolded foldables, where notifications and quick settings are shown
-     * side-by-side in their own columns).
-     */
-    @Deprecated("The notifications shade scene has been replaced by an overlay")
-    @JvmField
-    val NotificationsShade = SceneKey("notifications_shade")
-
-    /**
      * The quick settings scene shows the quick setting tiles.
      *
      * This scene is used for single/accordion configuration (swipe down once to reveal the shade,
@@ -69,27 +53,12 @@
      * scene is used.
      *
      * For the dual shade configuration, where there are two separate shades: one for notifications
-     * and one for quick settings, [NotificationsShade] and [QuickSettingsShade] scenes are used
-     * respectively.
+     * and one for quick settings, the overlays `NotificationsShade` and `QuickSettingsShade` are
+     * used respectively.
      */
     @JvmField val QuickSettings = SceneKey("quick_settings")
 
     /**
-     * The quick settings shade scene shows the quick setting tiles as an overlay UI.
-     *
-     * It's used only in the dual shade configuration, where there are two separate shades: one for
-     * quick settings (this scene) and another for [NotificationsShade].
-     *
-     * It's not used in the single/accordion configuration (swipe down once to reveal the shade,
-     * swipe down again the to expand quick settings) or in the "split" shade configuration (on
-     * large screens or unfolded foldables, where notifications and quick settings are shown
-     * side-by-side in their own columns).
-     */
-    @Deprecated("The quick settings shade scene has been replaced by an overlay")
-    @JvmField
-    val QuickSettingsShade = SceneKey("quick_settings_shade")
-
-    /**
      * The shade is the scene that shows a scrollable list of notifications and the minimized
      * version of quick settings (AKA "quick quick settings" or "QQS").
      *
@@ -97,7 +66,8 @@
      * swipe down again the to expand quick settings) and for the "split" shade configuration (on
      * large screens or unfolded foldables, where notifications and quick settings are shown
      * side-by-side in their own columns). For the dual shade configuration, where there are two
-     * separate shades: one for notifications and one for quick settings, other scenes are used.
+     * separate shades: one for notifications and one for quick settings, the overlays
+     * `NotificationsShade` and `QuickSettingsShade` are used respectively.
      */
     @JvmField val Shade = SceneKey("shade")
 }
@@ -114,16 +84,4 @@
      * depending on whether the device is unlocked and has been entered.
      */
     @JvmField val Home = SceneKey(debugName = "scene_family_home")
-
-    /**
-     * The scene that contains the full, interactive notification shade. The specific scene it
-     * resolves to can depend on dual / split / single shade settings.
-     */
-    @JvmField val NotifShade = SceneKey(debugName = "scene_family_notif_shade")
-
-    /**
-     * The scene that contains the full QuickSettings (not to be confused with Quick-QuickSettings).
-     * The specific scene it resolves to can depend on dual / split / single shade settings.
-     */
-    @JvmField val QuickSettings = SceneKey(debugName = "scene_family_quick_settings")
 }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModel.kt
index 7bf2b63..5ff507a 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModel.kt
@@ -19,7 +19,6 @@
 import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.Swipe
 import com.android.compose.animation.scene.SwipeDirection
-import com.android.compose.animation.scene.TransitionKey
 import com.android.compose.animation.scene.UserAction
 import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.scene.shared.model.Overlays
@@ -38,21 +37,25 @@
         shadeInteractor.shadeMode.collect { shadeMode ->
             setActions(
                 when (shadeMode) {
-                    ShadeMode.Single -> fullscreenShadeActions()
-                    ShadeMode.Split -> fullscreenShadeActions(transitionKey = ToSplitShade)
+                    ShadeMode.Single -> singleShadeActions()
+                    ShadeMode.Split -> splitShadeActions()
                     ShadeMode.Dual -> dualShadeActions()
                 }
             )
         }
     }
 
-    private fun fullscreenShadeActions(
-        transitionKey: TransitionKey? = null
-    ): Map<UserAction, UserActionResult> {
+    private fun singleShadeActions(): Map<UserAction, UserActionResult> {
         return mapOf(
-            Swipe.Down to UserActionResult(Scenes.Shade, transitionKey),
-            Swipe(direction = SwipeDirection.Down, pointerCount = 2, fromSource = Edge.Top) to
-                UserActionResult(Scenes.Shade, transitionKey),
+            Swipe.Down to Scenes.Shade,
+            swipeDownFromTopWithTwoFingers() to Scenes.QuickSettings,
+        )
+    }
+
+    private fun splitShadeActions(): Map<UserAction, UserActionResult> {
+        return mapOf(
+            Swipe.Down to UserActionResult(Scenes.Shade, ToSplitShade),
+            swipeDownFromTopWithTwoFingers() to UserActionResult(Scenes.Shade, ToSplitShade),
         )
     }
 
@@ -64,6 +67,10 @@
         )
     }
 
+    private fun swipeDownFromTopWithTwoFingers(): UserAction {
+        return Swipe(direction = SwipeDirection.Down, pointerCount = 2, fromSource = Edge.Top)
+    }
+
     @AssistedFactory
     interface Factory {
         fun create(): GoneUserActionsViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
index c451704..af1f5a7 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
@@ -52,7 +52,7 @@
     private val sceneInteractor: SceneInteractor,
     private val falsingInteractor: FalsingInteractor,
     private val powerInteractor: PowerInteractor,
-    private val shadeInteractor: ShadeInteractor,
+    shadeInteractor: ShadeInteractor,
     private val splitEdgeDetector: SplitEdgeDetector,
     private val logger: SceneLogger,
     @Assisted private val motionEventHandlerReceiver: (MotionEventHandler?) -> Unit,
@@ -212,9 +212,10 @@
                         )
                     }
                 }
+                // Overlay transitions don't use scene families, nothing to resolve.
                 is UserActionResult.ShowOverlay,
                 is UserActionResult.HideOverlay,
-                is UserActionResult.ReplaceByOverlay -> TODO("b/353679003: Support overlays")
+                is UserActionResult.ReplaceByOverlay -> null
             } ?: actionResult
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
index d4e711e..663ba20 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
@@ -41,7 +41,6 @@
 import androidx.exifinterface.media.ExifInterface;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.flags.FeatureFlags;
 
 import com.google.common.util.concurrent.ListenableFuture;
 
@@ -94,12 +93,10 @@
     private final ContentResolver mResolver;
     private CompressFormat mCompressFormat = CompressFormat.PNG;
     private int mQuality = 100;
-    private final FeatureFlags mFlags;
 
     @Inject
-    public ImageExporter(ContentResolver resolver, FeatureFlags flags) {
+    public ImageExporter(ContentResolver resolver) {
         mResolver = resolver;
-        mFlags = flags;
     }
 
     /**
@@ -161,8 +158,7 @@
         ZonedDateTime captureTime = ZonedDateTime.now(ZoneId.systemDefault());
         return export(executor,
                 new Task(mResolver, requestId, bitmap, captureTime, mCompressFormat,
-                        mQuality, /* publish */ true, owner, mFlags,
-                        createFilename(captureTime, mCompressFormat, displayId)));
+                        mQuality, owner, createFilename(captureTime, mCompressFormat, displayId)));
     }
 
     /**
@@ -184,7 +180,8 @@
                         bitmap,
                         ZonedDateTime.now(ZoneId.systemDefault()),
                         format,
-                        mQuality, /* publish */ true, owner, mFlags,
+                        mQuality,
+                        owner,
                         createSystemFileDisplayName(fileName, format),
                         true /* allowOverwrite */));
     }
@@ -199,8 +196,7 @@
     public ListenableFuture<Result> export(Executor executor, UUID requestId, Bitmap bitmap,
             ZonedDateTime captureTime, UserHandle owner, int displayId) {
         return export(executor, new Task(mResolver, requestId, bitmap, captureTime, mCompressFormat,
-                mQuality, /* publish */ true, owner, mFlags,
-                createFilename(captureTime, mCompressFormat, displayId)));
+                mQuality, owner, createFilename(captureTime, mCompressFormat, displayId)));
     }
 
     /**
@@ -213,8 +209,7 @@
     ListenableFuture<Result> export(Executor executor, UUID requestId, Bitmap bitmap,
             ZonedDateTime captureTime, UserHandle owner, String fileName) {
         return export(executor, new Task(mResolver, requestId, bitmap, captureTime, mCompressFormat,
-                mQuality, /* publish */ true, owner, mFlags,
-                createSystemFileDisplayName(fileName, mCompressFormat)));
+                mQuality, owner, createSystemFileDisplayName(fileName, mCompressFormat)));
     }
 
     /**
@@ -249,7 +244,6 @@
         public String fileName;
         public long timestamp;
         public CompressFormat format;
-        public boolean published;
 
         @Override
         public String toString() {
@@ -259,7 +253,6 @@
             sb.append(", fileName='").append(fileName).append('\'');
             sb.append(", timestamp=").append(timestamp);
             sb.append(", format=").append(format);
-            sb.append(", published=").append(published);
             sb.append('}');
             return sb.toString();
         }
@@ -274,8 +267,6 @@
         private final int mQuality;
         private final UserHandle mOwner;
         private final String mFileName;
-        private final boolean mPublish;
-        private final FeatureFlags mFlags;
 
         /**
          * This variable specifies the behavior when a file to be exported has a same name and
@@ -287,15 +278,14 @@
         private final boolean mAllowOverwrite;
 
         Task(ContentResolver resolver, UUID requestId, Bitmap bitmap, ZonedDateTime captureTime,
-                CompressFormat format, int quality, boolean publish, UserHandle owner,
-                FeatureFlags flags, String fileName) {
-            this(resolver, requestId, bitmap, captureTime, format, quality, publish, owner, flags,
-                    fileName, false /* allowOverwrite */);
+                CompressFormat format, int quality, UserHandle owner, String fileName) {
+            this(resolver, requestId, bitmap, captureTime, format, quality, owner, fileName,
+                    false /* allowOverwrite */);
         }
 
         Task(ContentResolver resolver, UUID requestId, Bitmap bitmap, ZonedDateTime captureTime,
-                CompressFormat format, int quality, boolean publish, UserHandle owner,
-                FeatureFlags flags, String fileName, boolean allowOverwrite) {
+                CompressFormat format, int quality, UserHandle owner,
+                String fileName, boolean allowOverwrite) {
             mResolver = resolver;
             mRequestId = requestId;
             mBitmap = bitmap;
@@ -304,8 +294,6 @@
             mQuality = quality;
             mOwner = owner;
             mFileName = fileName;
-            mPublish = publish;
-            mFlags = flags;
             mAllowOverwrite = allowOverwrite;
         }
 
@@ -320,7 +308,7 @@
                     start = Instant.now();
                 }
 
-                uri = createEntry(mResolver, mFormat, mCaptureTime, mFileName, mOwner, mFlags,
+                uri = createEntry(mResolver, mFormat, mCaptureTime, mFileName, mOwner,
                         mAllowOverwrite);
                 throwIfInterrupted();
 
@@ -332,10 +320,7 @@
                 writeExif(mResolver, uri, mRequestId, width, height, mCaptureTime);
                 throwIfInterrupted();
 
-                if (mPublish) {
-                    publishEntry(mResolver, uri);
-                    result.published = true;
-                }
+                publishEntry(mResolver, uri);
 
                 result.timestamp = mCaptureTime.toInstant().toEpochMilli();
                 result.requestId = mRequestId;
@@ -365,7 +350,7 @@
     }
 
     private static Uri createEntry(ContentResolver resolver, CompressFormat format,
-            ZonedDateTime time, String fileName, UserHandle owner, FeatureFlags flags,
+            ZonedDateTime time, String fileName, UserHandle owner,
             boolean allowOverwrite) throws ImageExportException {
         Trace.beginSection("ImageExporter_createEntry");
         try {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/MultiUserUtilsModule.java b/packages/SystemUI/src/com/android/systemui/settings/MultiUserUtilsModule.java
index b9f9b92..05f19ef 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/MultiUserUtilsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/MultiUserUtilsModule.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.settings;
 
+import android.app.ActivityManager;
 import android.app.IActivityManager;
 import android.content.Context;
 import android.hardware.display.DisplayManager;
@@ -66,7 +67,7 @@
             @Background CoroutineDispatcher backgroundDispatcher,
             @Background Handler handler
     ) {
-        int startingUser = userManager.getBootUser().getIdentifier();
+        int startingUser = ActivityManager.getCurrentUser();
         UserTrackerImpl tracker = new UserTrackerImpl(context, featureFlagsProvider, userManager,
                 iActivityManager, dumpManager, appScope, backgroundDispatcher, handler);
         tracker.initialize(startingUser);
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
index 1a997a7..e1631cc 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
@@ -16,11 +16,10 @@
 
 package com.android.systemui.settings
 
-import com.android.systemui.util.annotations.WeaklyReferencedCallback
-
 import android.content.Context
 import android.content.pm.UserInfo
 import android.os.UserHandle
+import com.android.systemui.util.annotations.WeaklyReferencedCallback
 import java.util.concurrent.Executor
 
 /**
@@ -31,19 +30,13 @@
  */
 interface UserTracker : UserContentResolverProvider, UserContextProvider {
 
-    /**
-     * Current user's id.
-     */
+    /** Current user's id. */
     val userId: Int
 
-    /**
-     * [UserHandle] for current user
-     */
+    /** [UserHandle] for current user */
     val userHandle: UserHandle
 
-    /**
-     * [UserInfo] for current user
-     */
+    /** [UserInfo] for current user */
     val userInfo: UserInfo
 
     /**
@@ -56,39 +49,33 @@
      */
     val userProfiles: List<UserInfo>
 
-    /**
-     * Add a [Callback] to be notified of chances, on a particular [Executor]
-     */
+    /** Is the system in the process of switching users? */
+    val isUserSwitching: Boolean
+
+    /** Add a [Callback] to be notified of chances, on a particular [Executor] */
     fun addCallback(callback: Callback, executor: Executor)
 
-    /**
-     * Remove a [Callback] previously added.
-     */
+    /** Remove a [Callback] previously added. */
     fun removeCallback(callback: Callback)
 
-    /**
-     * Callback for notifying of changes.
-     */
+    /** Callback for notifying of changes. */
     @WeaklyReferencedCallback
     interface Callback {
-        /**
-         * Notifies that the current user will be changed.
-         */
+        /** Notifies that the current user will be changed. */
         fun onBeforeUserSwitching(newUser: Int) {}
 
         /**
-         * Same as {@link onUserChanging(Int, Context, Runnable)} but the callback will be
-         * called automatically after the completion of this method.
+         * Same as {@link onUserChanging(Int, Context, Runnable)} but the callback will be called
+         * automatically after the completion of this method.
          */
         fun onUserChanging(newUser: Int, userContext: Context) {}
 
         /**
-         * Notifies that the current user is being changed.
-         * Override this method to run things while the screen is frozen for the user switch.
-         * Please use {@link #onUserChanged} if the task doesn't need to push the unfreezing of the
-         * screen further. Please be aware that code executed in this callback will lengthen the
-         * user switch duration. When overriding this method, resultCallback#run() MUST be called
-         * once the  execution is complete.
+         * Notifies that the current user is being changed. Override this method to run things while
+         * the screen is frozen for the user switch. Please use {@link #onUserChanged} if the task
+         * doesn't need to push the unfreezing of the screen further. Please be aware that code
+         * executed in this callback will lengthen the user switch duration. When overriding this
+         * method, resultCallback#run() MUST be called once the execution is complete.
          */
         fun onUserChanging(newUser: Int, userContext: Context, resultCallback: Runnable) {
             onUserChanging(newUser, userContext)
@@ -96,15 +83,13 @@
         }
 
         /**
-         * Notifies that the current user has changed.
-         * Override this method to run things after the screen is unfrozen for the user switch.
-         * Please see {@link #onUserChanging} if you need to hide jank.
+         * Notifies that the current user has changed. Override this method to run things after the
+         * screen is unfrozen for the user switch. Please see {@link #onUserChanging} if you need to
+         * hide jank.
          */
         fun onUserChanged(newUser: Int, userContext: Context) {}
 
-        /**
-         * Notifies that the current user's profiles have changed.
-         */
+        /** Notifies that the current user's profiles have changed. */
         fun onProfilesChanged(profiles: List<@JvmSuppressWildcards UserInfo>) {}
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
index 553d1f5..1863e12 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
@@ -106,6 +106,9 @@
     override var userInfo: UserInfo by SynchronizedDelegate(UserInfo(context.userId, "", 0))
         protected set
 
+    override var isUserSwitching = false
+        protected set
+
     /**
      * Returns a [List<UserInfo>] of all profiles associated with the current user.
      *
@@ -197,6 +200,7 @@
                 }
 
                 override fun onUserSwitching(newUserId: Int, reply: IRemoteCallback?) {
+                    isUserSwitching = true
                     if (isBackgroundUserSwitchEnabled) {
                         userSwitchingJob?.cancel()
                         userSwitchingJob =
@@ -210,6 +214,7 @@
                 }
 
                 override fun onUserSwitchComplete(newUserId: Int) {
+                    isUserSwitching = false
                     if (isBackgroundUserSwitchEnabled) {
                         afterUserSwitchingJob?.cancel()
                         afterUserSwitchingJob =
@@ -221,7 +226,7 @@
                     }
                 }
             },
-            TAG
+            TAG,
         )
     }
 
@@ -349,7 +354,7 @@
 
 private data class DataItem(
     val callback: WeakReference<UserTracker.Callback>,
-    val executor: Executor
+    val executor: Executor,
 ) {
     fun sameOrEmpty(other: UserTracker.Callback): Boolean {
         return callback.get()?.equals(other) ?: true
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 31813b2..42499f0 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -207,7 +207,7 @@
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
-import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
+import com.android.systemui.statusbar.notification.HeadsUpTouchHelper;
 import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
 import com.android.systemui.statusbar.phone.KeyguardBottomAreaViewController;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
index 813df11..859926c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
@@ -245,10 +245,6 @@
     /** */
     default void setNotificationPresenter(NotificationPresenter presenter) {}
 
-    /** */
-    default void setNotificationShadeWindowViewController(
-            NotificationShadeWindowViewController notificationShadeWindowViewController) {}
-
     /** Listens for shade visibility changes. */
     interface ShadeVisibilityListener {
         /** Called when shade expanded and visible state changed. */
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index 07836e4..b7a95e9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -65,6 +65,7 @@
     private final StatusBarWindowController mStatusBarWindowController;
     private final DeviceProvisionedController mDeviceProvisionedController;
 
+    private final Lazy<NotificationShadeWindowViewController> mNotifShadeWindowViewController;
     private final Lazy<NotificationPanelViewController> mNpvc;
     private final Lazy<AssistManager> mAssistManagerLazy;
     private final Lazy<NotificationGutsManager> mGutsManager;
@@ -72,7 +73,6 @@
     private boolean mExpandedVisible;
     private boolean mLockscreenOrShadeVisible;
 
-    private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
     private ShadeVisibilityListener mShadeVisibilityListener;
 
     @Inject
@@ -87,6 +87,7 @@
             DeviceProvisionedController deviceProvisionedController,
             NotificationShadeWindowController notificationShadeWindowController,
             @DisplayId int displayId,
+            Lazy<NotificationShadeWindowViewController> notificationShadeWindowViewController,
             Lazy<NotificationPanelViewController> shadeViewControllerLazy,
             Lazy<AssistManager> assistManagerLazy,
             Lazy<NotificationGutsManager> gutsManager
@@ -105,6 +106,7 @@
         mDeviceProvisionedController = deviceProvisionedController;
         mGutsManager = gutsManager;
         mNotificationShadeWindowController = notificationShadeWindowController;
+        mNotifShadeWindowViewController = notificationShadeWindowViewController;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
         mDisplayId = displayId;
         mKeyguardStateController = keyguardStateController;
@@ -139,7 +141,7 @@
             // release focus immediately to kick off focus change transition
             mNotificationShadeWindowController.setNotificationShadeFocusable(false);
 
-            mNotificationShadeWindowViewController.cancelExpandHelper();
+            mNotifShadeWindowViewController.get().cancelExpandHelper();
             getNpvc().collapse(true, delayed, speedUpFactor);
         }
     }
@@ -242,7 +244,7 @@
     @Override
     public void cancelExpansionAndCollapseShade() {
         if (getNpvc().isTracking()) {
-            mNotificationShadeWindowViewController.cancelCurrentTouch();
+            mNotifShadeWindowViewController.get().cancelCurrentTouch();
         }
         if (getNpvc().isPanelExpanded()
                 && mStatusBarStateController.getState() == StatusBarState.SHADE) {
@@ -367,14 +369,8 @@
         mShadeVisibilityListener.expandedVisibleChanged(expandedVisible);
     }
 
-    @Override
-    public void setNotificationShadeWindowViewController(
-            NotificationShadeWindowViewController controller) {
-        mNotificationShadeWindowViewController = controller;
-    }
-
     private NotificationShadeWindowView getNotificationShadeWindowView() {
-        return mNotificationShadeWindowViewController.getView();
+        return mNotifShadeWindowViewController.get().getView();
     }
 
     private NotificationPanelViewController getNpvc() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
index 5d03a28..361226a4 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
@@ -91,17 +91,14 @@
     }
 
     override fun instantCollapseShade() {
-        sceneInteractor.snapToScene(
-            SceneFamilies.Home,
-            "hide shade",
-        )
+        sceneInteractor.snapToScene(SceneFamilies.Home, "hide shade")
     }
 
     override fun animateCollapseShade(
         flags: Int,
         force: Boolean,
         delayed: Boolean,
-        speedUpFactor: Float
+        speedUpFactor: Float,
     ) {
         if (!force && !shadeInteractor.isAnyExpanded.value) {
             runPostCollapseActions()
@@ -147,7 +144,7 @@
         if (shadeInteractor.isAnyExpanded.value) {
             commandQueue.animateCollapsePanels(
                 CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
-                true /* force */
+                true, /* force */
             )
             assistManagerLazy.get().hideAssist()
         }
@@ -172,17 +169,11 @@
     }
 
     override fun expandToNotifications() {
-        sceneInteractor.changeScene(
-            SceneFamilies.NotifShade,
-            "ShadeController.animateExpandShade",
-        )
+        shadeInteractor.expandNotificationShade("ShadeController.animateExpandShade")
     }
 
     override fun expandToQs() {
-        sceneInteractor.changeScene(
-            SceneFamilies.QuickSettings,
-            "ShadeController.animateExpandQs",
-        )
+        shadeInteractor.expandQuickSettingsShade("ShadeController.animateExpandQs")
     }
 
     override fun setVisibilityListener(listener: ShadeVisibilityListener) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
index c49cfbd..cb589aa 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
@@ -194,6 +194,7 @@
             if (qsVisible && field != value) {
                 header.alpha = ShadeInterpolation.getContentAlpha(value)
                 field = value
+                updateIgnoredSlots()
             }
         }
 
@@ -538,7 +539,7 @@
 
     private fun updateIgnoredSlots() {
         // switching from QQS to QS state halfway through the transition
-        if (singleCarrier || qsExpandedFraction < 0.5) {
+        if (singleCarrier || (!largeScreenActive && qsExpandedFraction < 0.5)) {
             iconContainer.removeIgnoredSlots(carrierIconSlots)
         } else {
             iconContainer.addIgnoredSlots(carrierIconSlots)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt
index e276f88..cea521f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt
@@ -18,10 +18,11 @@
 
 package com.android.systemui.shade.domain.interactor
 
+import com.android.compose.animation.scene.ContentKey
 import com.android.compose.animation.scene.ObservableTransitionState
-import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.Overlays
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.statusbar.SysuiStatusBarStateController
 import javax.inject.Inject
@@ -55,7 +56,9 @@
             when (state) {
                 is ObservableTransitionState.Idle ->
                     flowOf(
-                        if (state.currentScene != Scenes.Gone) {
+                        if (
+                            state.currentScene != Scenes.Gone || state.currentOverlays.isNotEmpty()
+                        ) {
                             // When resting on a non-Gone scene, the panel is fully expanded.
                             1f
                         } else {
@@ -64,10 +67,10 @@
                             0f
                         }
                     )
-                is ObservableTransitionState.Transition.ChangeScene ->
+                is ObservableTransitionState.Transition ->
                     when {
-                        state.fromScene == Scenes.Gone ->
-                            if (state.toScene.isExpandable()) {
+                        state.fromContent == Scenes.Gone ->
+                            if (state.toContent.isExpandable()) {
                                 // Moving from Gone to a scene that can animate-expand has a
                                 // panel expansion that tracks with the transition.
                                 state.progress
@@ -76,8 +79,8 @@
                                 // immediately makes the panel fully expanded.
                                 flowOf(1f)
                             }
-                        state.toScene == Scenes.Gone ->
-                            if (state.fromScene.isExpandable()) {
+                        state.toContent == Scenes.Gone ->
+                            if (state.fromContent.isExpandable()) {
                                 // Moving to Gone from a scene that can animate-expand has a
                                 // panel expansion that tracks with the transition.
                                 state.progress.map { 1 - it }
@@ -88,9 +91,6 @@
                             }
                         else -> flowOf(1f)
                     }
-                is ObservableTransitionState.Transition.ShowOrHideOverlay,
-                is ObservableTransitionState.Transition.ReplaceOverlay ->
-                    TODO("b/359173565: Handle overlay transitions")
             }
         }
 
@@ -132,7 +132,13 @@
         return sceneInteractor.currentScene.value == Scenes.Lockscreen
     }
 
-    private fun SceneKey.isExpandable(): Boolean {
-        return this == Scenes.Shade || this == Scenes.QuickSettings
+    private fun ContentKey.isExpandable(): Boolean {
+        return when (this) {
+            Scenes.Shade,
+            Scenes.QuickSettings,
+            Overlays.NotificationsShade,
+            Overlays.QuickSettingsShade -> true
+            else -> false
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index 6fb96da..b046c50 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -140,6 +140,18 @@
      * animating.
      */
     val isUserInteractingWithQs: Flow<Boolean>
+
+    /**
+     * Triggers the expansion (opening) of the notification shade. If the notification shade is
+     * already open, this has no effect.
+     */
+    fun expandNotificationShade(loggingReason: String)
+
+    /**
+     * Triggers the expansion (opening) of the quick settings shade. If the quick settings shade is
+     * already open, this has no effect.
+     */
+    fun expandQuickSettingsShade(loggingReason: String)
 }
 
 fun createAnyExpansionFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
index 6c0b55a..fb14828 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
@@ -49,4 +49,8 @@
     override val isShadeLayoutWide: StateFlow<Boolean> = inactiveFlowBoolean
 
     override fun getTopEdgeSplitFraction(): Float = 0.5f
+
+    override fun expandNotificationShade(loggingReason: String) {}
+
+    override fun expandQuickSettingsShade(loggingReason: String) {}
 }
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 f48e31e..df09486 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
@@ -61,7 +61,7 @@
                 keyguardRepository.statusBarState,
                 repository.legacyShadeExpansion,
                 repository.qsExpansion,
-                sharedNotificationContainerInteractor.isSplitShadeEnabled
+                sharedNotificationContainerInteractor.isSplitShadeEnabled,
             ) {
                 lockscreenShadeExpansion,
                 statusBarState,
@@ -97,13 +97,13 @@
         repository.legacyExpandedOrAwaitingInputTransfer.stateIn(
             scope,
             SharingStarted.Eagerly,
-            false
+            false,
         )
 
     override val isUserInteractingWithShade: Flow<Boolean> =
         combine(
             userInteractingFlow(repository.legacyShadeTracking, repository.legacyShadeExpansion),
-            repository.legacyLockscreenShadeTracking
+            repository.legacyLockscreenShadeTracking,
         ) { legacyShadeTracking, legacyLockscreenShadeTracking ->
             legacyShadeTracking || legacyLockscreenShadeTracking
         }
@@ -111,6 +111,18 @@
     override val isUserInteractingWithQs: Flow<Boolean> =
         userInteractingFlow(repository.legacyQsTracking, repository.qsExpansion)
 
+    override fun expandNotificationShade(loggingReason: String) {
+        throw UnsupportedOperationException(
+            "expandNotificationShade() is not supported in legacy shade"
+        )
+    }
+
+    override fun expandQuickSettingsShade(loggingReason: String) {
+        throw UnsupportedOperationException(
+            "expandQuickSettingsShade() is not supported in legacy shade"
+        )
+    }
+
     /**
      * Return a flow for whether a user is interacting with an expandable shade component using
      * tracking and expansion flows. NOTE: expansion must be a `StateFlow` to guarantee that
@@ -118,7 +130,7 @@
      */
     private fun userInteractingFlow(
         tracking: Flow<Boolean>,
-        expansion: StateFlow<Float>
+        expansion: StateFlow<Float>,
     ): Flow<Boolean> {
         return flow {
             // initial value is false
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 e84cfa5..81bf712 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
@@ -17,81 +17,70 @@
 package com.android.systemui.shade.domain.interactor
 
 import com.android.app.tracing.FlowTracing.traceAsCounter
+import com.android.compose.animation.scene.ContentKey
 import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.OverlayKey
 import com.android.compose.animation.scene.SceneKey
 import com.android.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.shade.data.repository.ShadeRepository
+import com.android.systemui.scene.shared.model.Overlays
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 
 /** ShadeInteractor implementation for Scene Container. */
+@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class ShadeInteractorSceneContainerImpl
 @Inject
 constructor(
     @Application scope: CoroutineScope,
-    sceneInteractor: SceneInteractor,
-    shadeRepository: ShadeRepository,
+    private val sceneInteractor: SceneInteractor,
+    private val shadeModeInteractor: ShadeModeInteractor,
 ) : BaseShadeInteractor {
     init {
         SceneContainerFlag.assertInNewMode()
     }
 
     override val shadeExpansion: StateFlow<Float> =
-        sceneBasedExpansion(sceneInteractor, SceneFamilies.NotifShade)
+        shadeModeInteractor.shadeMode
+            .flatMapLatest { shadeMode ->
+                transitionProgressExpansion(shadeMode.notificationsContentKey)
+            }
             .traceAsCounter("panel_expansion") { (it * 100f).toInt() }
             .stateIn(scope, SharingStarted.Eagerly, 0f)
 
-    private val sceneBasedQsExpansion =
-        sceneBasedExpansion(sceneInteractor, SceneFamilies.QuickSettings)
-
     override val qsExpansion: StateFlow<Float> =
-        combine(
-                shadeRepository.isShadeLayoutWide,
-                shadeExpansion,
-                sceneBasedQsExpansion,
-            ) { isSplitShadeEnabled, shadeExpansion, qsExpansion ->
-                if (isSplitShadeEnabled) {
-                    shadeExpansion
-                } else {
-                    qsExpansion
-                }
-            }
+        shadeModeInteractor.shadeMode
+            .flatMapLatest { shadeMode -> transitionProgressExpansion(shadeMode.qsContentKey) }
             .stateIn(scope, SharingStarted.Eagerly, 0f)
 
     override val isQsExpanded: StateFlow<Boolean> =
-        qsExpansion
-            .map { it > 0 }
-            .distinctUntilChanged()
-            .stateIn(scope, SharingStarted.Eagerly, false)
+        qsExpansion.map { it > 0 }.stateIn(scope, SharingStarted.Eagerly, false)
 
     override val isQsBypassingShade: Flow<Boolean> =
-        combine(
-                sceneInteractor.resolveSceneFamily(SceneFamilies.QuickSettings),
-                sceneInteractor.resolveSceneFamily(SceneFamilies.NotifShade),
-                ::Pair
-            )
-            .flatMapLatestConflated { (quickSettingsScene, notificationsScene) ->
+        shadeModeInteractor.shadeMode
+            .flatMapLatestConflated { shadeMode ->
                 sceneInteractor.transitionState
                     .map { state ->
                         when (state) {
                             is ObservableTransitionState.Idle -> false
                             is ObservableTransitionState.Transition ->
-                                state.toContent == quickSettingsScene &&
-                                    state.fromContent != notificationsScene
+                                state.toContent == shadeMode.qsContentKey &&
+                                    state.fromContent != shadeMode.notificationsContentKey
                         }
                     }
                     .distinctUntilChanged()
@@ -99,21 +88,22 @@
             .distinctUntilChanged()
 
     override val isQsFullscreen: Flow<Boolean> =
-        combine(
-                shadeRepository.isShadeLayoutWide,
-                sceneInteractor.resolveSceneFamily(SceneFamilies.QuickSettings),
-                ::Pair
-            )
-            .flatMapLatestConflated { (isShadeLayoutWide, quickSettingsScene) ->
-                sceneInteractor.transitionState
-                    .map { state ->
-                        when (state) {
-                            is ObservableTransitionState.Idle ->
-                                !isShadeLayoutWide && state.currentScene == quickSettingsScene
-                            is ObservableTransitionState.Transition -> false
-                        }
-                    }
-                    .distinctUntilChanged()
+        shadeModeInteractor.shadeMode
+            .flatMapLatest { shadeMode ->
+                when (shadeMode) {
+                    ShadeMode.Single ->
+                        sceneInteractor.transitionState
+                            .map { state ->
+                                when (state) {
+                                    is ObservableTransitionState.Idle ->
+                                        state.currentScene == Scenes.QuickSettings
+                                    is ObservableTransitionState.Transition -> false
+                                }
+                            }
+                            .distinctUntilChanged()
+                    ShadeMode.Split,
+                    ShadeMode.Dual -> flowOf(false)
+                }
             }
             .distinctUntilChanged()
 
@@ -121,16 +111,79 @@
         createAnyExpansionFlow(scope, shadeExpansion, qsExpansion)
 
     override val isAnyExpanded =
-        anyExpansion
-            .map { it > 0f }
-            .distinctUntilChanged()
-            .stateIn(scope, SharingStarted.Eagerly, false)
+        anyExpansion.map { it > 0f }.stateIn(scope, SharingStarted.Eagerly, false)
 
     override val isUserInteractingWithShade: Flow<Boolean> =
-        sceneBasedInteracting(sceneInteractor, SceneFamilies.NotifShade)
+        shadeModeInteractor.shadeMode.flatMapLatest { shadeMode ->
+            when (shadeMode) {
+                ShadeMode.Single,
+                ShadeMode.Split -> sceneBasedInteracting(sceneInteractor, Scenes.Shade)
+                ShadeMode.Dual ->
+                    overlayBasedInteracting(sceneInteractor, Overlays.NotificationsShade)
+            }
+        }
 
     override val isUserInteractingWithQs: Flow<Boolean> =
-        sceneBasedInteracting(sceneInteractor, SceneFamilies.QuickSettings)
+        shadeModeInteractor.shadeMode.flatMapLatest { shadeMode ->
+            when (shadeMode) {
+                ShadeMode.Single -> sceneBasedInteracting(sceneInteractor, Scenes.QuickSettings)
+                ShadeMode.Split -> sceneBasedInteracting(sceneInteractor, Scenes.Shade)
+                ShadeMode.Dual ->
+                    overlayBasedInteracting(sceneInteractor, Overlays.QuickSettingsShade)
+            }
+        }
+
+    override fun expandNotificationShade(loggingReason: String) {
+        if (shadeModeInteractor.isDualShade) {
+            if (Overlays.QuickSettingsShade in sceneInteractor.currentOverlays.value) {
+                sceneInteractor.replaceOverlay(
+                    from = Overlays.QuickSettingsShade,
+                    to = Overlays.NotificationsShade,
+                    loggingReason = loggingReason,
+                )
+            } else {
+                sceneInteractor.showOverlay(
+                    overlay = Overlays.NotificationsShade,
+                    loggingReason = loggingReason,
+                )
+            }
+        } else {
+            sceneInteractor.changeScene(toScene = Scenes.Shade, loggingReason = loggingReason)
+        }
+    }
+
+    override fun expandQuickSettingsShade(loggingReason: String) {
+        if (shadeModeInteractor.isDualShade) {
+            if (Overlays.NotificationsShade in sceneInteractor.currentOverlays.value) {
+                sceneInteractor.replaceOverlay(
+                    from = Overlays.NotificationsShade,
+                    to = Overlays.QuickSettingsShade,
+                    loggingReason = loggingReason,
+                )
+            } else {
+                sceneInteractor.showOverlay(
+                    overlay = Overlays.QuickSettingsShade,
+                    loggingReason = loggingReason,
+                )
+            }
+        } else {
+            sceneInteractor.changeScene(
+                toScene = Scenes.QuickSettings,
+                loggingReason = loggingReason,
+            )
+        }
+    }
+
+    /**
+     * Returns a flow that uses scene transition progress to and from a content to a 0-1 expansion
+     * amount float.
+     */
+    private fun transitionProgressExpansion(contentKey: ContentKey): Flow<Float> {
+        return when (contentKey) {
+            is SceneKey -> sceneBasedExpansion(sceneInteractor, contentKey)
+            is OverlayKey -> overlayBasedExpansion(sceneInteractor, contentKey)
+        }
+    }
 
     /**
      * Returns a flow that uses scene transition progress to and from a scene that is pulled down
@@ -181,4 +234,60 @@
                 }
             }
             .distinctUntilChanged()
+
+    /**
+     * Returns a flow that uses scene transition progress to and from [overlay] to a 0-1 expansion
+     * amount float.
+     */
+    private fun overlayBasedExpansion(sceneInteractor: SceneInteractor, overlay: OverlayKey) =
+        sceneInteractor.transitionState
+            .flatMapLatestConflated { state ->
+                when (state) {
+                    is ObservableTransitionState.Idle ->
+                        flowOf(if (overlay in state.currentOverlays) 1f else 0f)
+                    is ObservableTransitionState.Transition ->
+                        if (state.toContent == overlay) {
+                            state.progress
+                        } else if (state.fromContent == overlay) {
+                            state.progress.map { progress -> 1 - progress }
+                        } else {
+                            flowOf(0f)
+                        }
+                }
+            }
+            .distinctUntilChanged()
+
+    /**
+     * Returns a flow that uses scene transition data to determine whether the user is interacting
+     * with [overlay].
+     */
+    private fun overlayBasedInteracting(sceneInteractor: SceneInteractor, overlay: OverlayKey) =
+        sceneInteractor.transitionState
+            .map { state ->
+                when (state) {
+                    is ObservableTransitionState.Idle -> false
+                    is ObservableTransitionState.Transition ->
+                        state.isInitiatedByUserInput &&
+                            (state.toContent == overlay || state.fromContent == overlay)
+                }
+            }
+            .distinctUntilChanged()
+
+    private val ShadeMode.notificationsContentKey: ContentKey
+        get() {
+            return when (this) {
+                ShadeMode.Single,
+                ShadeMode.Split -> Scenes.Shade
+                ShadeMode.Dual -> Overlays.NotificationsShade
+            }
+        }
+
+    private val ShadeMode.qsContentKey: ContentKey
+        get() {
+            return when (this) {
+                ShadeMode.Single -> Scenes.QuickSettings
+                ShadeMode.Split -> Scenes.Shade
+                ShadeMode.Dual -> Overlays.QuickSettingsShade
+            }
+        }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
index e525b86..0fb3790 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
@@ -21,9 +21,10 @@
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.model.SceneFamilies
+import com.android.systemui.scene.shared.model.Overlays
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.shade.shared.model.ShadeMode
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
@@ -45,10 +46,14 @@
     override val udfpsTransitionToFullShadeProgress =
         shadeRepository.udfpsTransitionToFullShadeProgress
 
+    @Deprecated("Use ShadeInteractor instead")
     override fun expandToNotifications() {
-        changeToShadeScene()
+        shadeInteractor.expandNotificationShade(
+            loggingReason = "ShadeLockscreenInteractorImpl.expandToNotifications"
+        )
     }
 
+    @Deprecated("Use ShadeInteractor instead")
     override val isExpanded
         get() = shadeInteractor.isAnyExpanded.value
 
@@ -60,15 +65,26 @@
         lockIconViewController.dozeTimeTick()
     }
 
+    @Deprecated("Not supported by scenes")
     override fun blockExpansionForCurrentTouch() {
         // TODO("b/324280998") Implement replacement or delete
     }
 
     override fun resetViews(animate: Boolean) {
+        val loggingReason = "ShadeLockscreenInteractorImpl.resetViews"
         // The existing comment to the only call to this claims it only calls it to collapse QS
-        changeToShadeScene()
+        if (shadeInteractor.shadeMode.value == ShadeMode.Dual) {
+            // TODO(b/356596436): Hide without animation if !animate.
+            sceneInteractor.hideOverlay(
+                overlay = Overlays.QuickSettingsShade,
+                loggingReason = loggingReason,
+            )
+        } else {
+            shadeInteractor.expandNotificationShade(loggingReason)
+        }
     }
 
+    @Deprecated("Not supported by scenes")
     override fun setPulsing(pulsing: Boolean) {
         // Now handled elsewhere. Do nothing.
     }
@@ -76,22 +92,30 @@
     override fun transitionToExpandedShade(delay: Long) {
         backgroundScope.launch {
             delay(delay)
-            withContext(mainDispatcher) { changeToShadeScene() }
+            withContext(mainDispatcher) {
+                shadeInteractor.expandNotificationShade(
+                    "ShadeLockscreenInteractorImpl.transitionToExpandedShade"
+                )
+            }
         }
     }
 
+    @Deprecated("Not supported by scenes")
     override fun resetViewGroupFade() {
         // Now handled elsewhere. Do nothing.
     }
 
+    @Deprecated("Not supported by scenes")
     override fun setKeyguardTransitionProgress(keyguardAlpha: Float, keyguardTranslationY: Int) {
         // Now handled elsewhere. Do nothing.
     }
 
+    @Deprecated("Not supported by scenes")
     override fun setOverStretchAmount(amount: Float) {
         // Now handled elsewhere. Do nothing.
     }
 
+    @Deprecated("TODO(b/325072511) delete this")
     override fun setKeyguardStatusBarAlpha(alpha: Float) {
         // TODO(b/325072511) delete this
     }
@@ -100,11 +124,4 @@
         sceneInteractor.changeScene(Scenes.Lockscreen, "showAodUi", sceneState = KeyguardState.AOD)
         // TODO(b/330311871) implement transition to AOD
     }
-
-    private fun changeToShadeScene() {
-        sceneInteractor.changeScene(
-            SceneFamilies.NotifShade,
-            "ShadeLockscreenInteractorImpl.expandToNotifications",
-        )
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt
index 77ae679..caa4513 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt
@@ -51,6 +51,10 @@
      */
     val isShadeLayoutWide: StateFlow<Boolean>
 
+    /** Convenience shortcut for querying whether the current [shadeMode] is [ShadeMode.Dual]. */
+    val isDualShade: Boolean
+        get() = shadeMode.value is ShadeMode.Dual
+
     /**
      * The fraction between [0..1] (i.e., percentage) of screen width to consider the threshold
      * between "top-left" and "top-right" for the purposes of dual-shade invocation.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/TEST_MAPPING b/packages/SystemUI/src/com/android/systemui/statusbar/TEST_MAPPING
index 718c1c0f..3232684 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/TEST_MAPPING
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/TEST_MAPPING
@@ -1,18 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "CtsNotificationTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.LargeTest"
-        }
-      ]
+      "name": "CtsNotificationTestCases_notification"
     }
   ],
   "postsubmit": [
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpManagerPhone.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpManagerPhone.java
index 0e7beb9d..02a29e2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpManagerPhone.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.statusbar.notification;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -47,6 +47,8 @@
 import com.android.systemui.statusbar.notification.data.repository.HeadsUpRowRepository;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
+import com.android.systemui.statusbar.phone.ExpandHeadsUpOnInlineReply;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
 import com.android.systemui.statusbar.policy.AnimationStateHandler;
 import com.android.systemui.statusbar.policy.AvalancheController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpNotificationViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpNotificationViewControllerEmptyImpl.kt
similarity index 88%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpNotificationViewControllerEmptyImpl.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpNotificationViewControllerEmptyImpl.kt
index 9f76429..021d301 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpNotificationViewControllerEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpNotificationViewControllerEmptyImpl.kt
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone
+package com.android.systemui.statusbar.notification
 
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
-import com.android.systemui.statusbar.phone.HeadsUpTouchHelper.HeadsUpNotificationViewController
+import com.android.systemui.statusbar.notification.HeadsUpTouchHelper.HeadsUpNotificationViewController
 
 /** Empty impl of [HeadsUpNotificationViewController] for use with Scene Container */
 class HeadsUpNotificationViewControllerEmptyImpl : HeadsUpNotificationViewController {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpTouchHelper.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpTouchHelper.java
index 26bd7ac..0927a72 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpTouchHelper.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -11,10 +11,10 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.statusbar.notification;
 
 import android.content.Context;
 import android.os.RemoteException;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt
index 2b0d2aa..63c9e8b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt
@@ -16,7 +16,7 @@
 package com.android.systemui.statusbar.notification.data
 
 import com.android.systemui.statusbar.notification.data.repository.HeadsUpRepository
-import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
+import com.android.systemui.statusbar.notification.HeadsUpManagerPhone
 import dagger.Binds
 import dagger.Module
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
index 637cadd..920541d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
@@ -44,7 +44,7 @@
                     viewModel,
                     clearAllNotifications,
                     launchNotificationSettings,
-                    launchNotificationHistory
+                    launchNotificationHistory,
                 )
             }
         }
@@ -55,21 +55,15 @@
         viewModel: FooterViewModel,
         clearAllNotifications: View.OnClickListener,
         launchNotificationSettings: View.OnClickListener,
-        launchNotificationHistory: View.OnClickListener
+        launchNotificationHistory: View.OnClickListener,
     ) = coroutineScope {
-        launch {
-            bindClearAllButton(
-                footer,
-                viewModel,
-                clearAllNotifications,
-            )
-        }
+        launch { bindClearAllButton(footer, viewModel, clearAllNotifications) }
         launch {
             bindManageOrHistoryButton(
                 footer,
                 viewModel,
                 launchNotificationSettings,
-                launchNotificationHistory
+                launchNotificationHistory,
             )
         }
         launch { bindMessage(footer, viewModel) }
@@ -80,8 +74,6 @@
         viewModel: FooterViewModel,
         clearAllNotifications: View.OnClickListener,
     ) = coroutineScope {
-        footer.setClearAllButtonClickListener(clearAllNotifications)
-
         launch {
             viewModel.clearAllButton.labelId.collect { textId ->
                 footer.setClearAllButtonText(textId)
@@ -96,18 +88,21 @@
 
         launch {
             viewModel.clearAllButton.isVisible.collect { isVisible ->
+                if (isVisible.value) {
+                    footer.setClearAllButtonClickListener(clearAllNotifications)
+                } else {
+                    // When the button isn't visible, it also shouldn't react to clicks. This is
+                    // necessary because when the clear all button is not visible, it's actually
+                    // just the alpha that becomes 0 so it can still be tapped.
+                    footer.setClearAllButtonClickListener(null)
+                }
+
                 if (isVisible.isAnimating) {
-                    footer.setClearAllButtonVisible(
-                        isVisible.value,
-                        /* animate = */ true,
-                    ) { _ ->
+                    footer.setClearAllButtonVisible(isVisible.value, /* animate= */ true) { _ ->
                         isVisible.stopAnimating()
                     }
                 } else {
-                    footer.setClearAllButtonVisible(
-                        isVisible.value,
-                        /* animate = */ false,
-                    )
+                    footer.setClearAllButtonVisible(isVisible.value, /* animate= */ false)
                 }
             }
         }
@@ -143,22 +138,24 @@
 
         launch {
             viewModel.manageOrHistoryButton.isVisible.collect { isVisible ->
-                // NOTE: This visibility change is never animated.
+                // NOTE: This visibility change is never animated. We also don't need to do anything
+                // special about the onClickListener here, since we're changing the visibility to
+                // GONE so it won't be clickable anyway.
                 footer.setManageOrHistoryButtonVisible(isVisible.value)
             }
         }
     }
 
-    private suspend fun bindMessage(
-        footer: FooterView,
-        viewModel: FooterViewModel,
-    ) = coroutineScope {
-        // Bind the resource IDs
-        footer.setMessageString(viewModel.message.messageId)
-        footer.setMessageIcon(viewModel.message.iconId)
+    private suspend fun bindMessage(footer: FooterView, viewModel: FooterViewModel) =
+        coroutineScope {
+            // Bind the resource IDs
+            footer.setMessageString(viewModel.message.messageId)
+            footer.setMessageIcon(viewModel.message.iconId)
 
-        launch {
-            viewModel.message.isVisible.collect { visible -> footer.setFooterLabelVisible(visible) }
+            launch {
+                viewModel.message.isVisible.collect { visible ->
+                    footer.setFooterLabelVisible(visible)
+                }
+            }
         }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 1214440a..7543f3b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -120,7 +120,7 @@
 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape;
 import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView;
 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
-import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
+import com.android.systemui.statusbar.notification.HeadsUpTouchHelper;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.HeadsUpUtil;
 import com.android.systemui.statusbar.policy.ScrollAdapter;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index bcdc3bc..e5f63c1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -129,9 +129,9 @@
 import com.android.systemui.statusbar.notification.shared.GroupHunAnimationFix;
 import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder;
 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
-import com.android.systemui.statusbar.phone.HeadsUpNotificationViewControllerEmptyImpl;
-import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
-import com.android.systemui.statusbar.phone.HeadsUpTouchHelper.HeadsUpNotificationViewController;
+import com.android.systemui.statusbar.notification.HeadsUpNotificationViewControllerEmptyImpl;
+import com.android.systemui.statusbar.notification.HeadsUpTouchHelper;
+import com.android.systemui.statusbar.notification.HeadsUpTouchHelper.HeadsUpNotificationViewController;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
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 3e42413..8d7007b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
@@ -17,6 +17,7 @@
 
 package com.android.systemui.statusbar.notification.stack.ui.viewmodel
 
+import com.android.compose.animation.scene.ContentKey
 import com.android.compose.animation.scene.ObservableTransitionState.Idle
 import com.android.compose.animation.scene.ObservableTransitionState.Transition
 import com.android.compose.animation.scene.ObservableTransitionState.Transition.ChangeScene
@@ -26,7 +27,7 @@
 import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
-import com.android.systemui.scene.shared.model.SceneFamilies
+import com.android.systemui.scene.shared.model.Overlays
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shade.shared.model.ShadeMode
@@ -46,7 +47,6 @@
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.mapNotNull
 
 /** ViewModel which represents the state of the NSSL/Controller in the world of flexiglass */
@@ -91,7 +91,7 @@
     ): Float {
         return if (fullyExpandedDuringSceneChange(change)) {
             1f
-        } else if (change.isBetween({ it == Scenes.Gone }, { it in SceneFamilies.NotifShade })) {
+        } else if (change.isBetween({ it == Scenes.Gone }, { it == Scenes.Shade })) {
             shadeExpansion
         } else if (change.isBetween({ it == Scenes.Gone }, { it == Scenes.QuickSettings })) {
             // during QS expansion, increase fraction at same rate as scrim alpha,
@@ -99,6 +99,22 @@
             (qsExpansion / EXPANSION_FOR_MAX_SCRIM_ALPHA - EXPANSION_FOR_DELAYED_STACK_FADE_IN)
                 .coerceIn(0f, 1f)
         } else {
+            // TODO(b/356596436): If notification shade overlay is open, we'll reach this point and
+            //  the expansion fraction in that case should be `shadeExpansion`.
+            0f
+        }
+    }
+
+    private fun expandFractionDuringOverlayTransition(
+        transition: Transition,
+        currentScene: SceneKey,
+        shadeExpansion: Float,
+    ): Float {
+        return if (currentScene == Scenes.Lockscreen) {
+            1f
+        } else if (transition.isTransitioningFromOrTo(Overlays.NotificationsShade)) {
+            shadeExpansion
+        } else {
             0f
         }
     }
@@ -114,18 +130,35 @@
                 shadeInteractor.shadeMode,
                 shadeInteractor.qsExpansion,
                 sceneInteractor.transitionState,
-                sceneInteractor.resolveSceneFamily(SceneFamilies.QuickSettings),
-            ) { shadeExpansion, _, qsExpansion, transitionState, _ ->
+            ) { shadeExpansion, _, qsExpansion, transitionState ->
                 when (transitionState) {
-                    is Idle -> if (expandedInScene(transitionState.currentScene)) 1f else 0f
+                    is Idle ->
+                        if (
+                            expandedInScene(transitionState.currentScene) ||
+                                Overlays.NotificationsShade in transitionState.currentOverlays
+                        ) {
+                            1f
+                        } else {
+                            0f
+                        }
                     is ChangeScene ->
                         expandFractionDuringSceneChange(
-                            transitionState,
-                            shadeExpansion,
-                            qsExpansion,
+                            change = transitionState,
+                            shadeExpansion = shadeExpansion,
+                            qsExpansion = qsExpansion,
                         )
-                    is Transition.ShowOrHideOverlay,
-                    is Transition.ReplaceOverlay -> TODO("b/359173565: Handle overlay transitions")
+                    is Transition.ShowOrHideOverlay ->
+                        expandFractionDuringOverlayTransition(
+                            transition = transitionState,
+                            currentScene = transitionState.currentScene,
+                            shadeExpansion = shadeExpansion,
+                        )
+                    is Transition.ReplaceOverlay ->
+                        expandFractionDuringOverlayTransition(
+                            transition = transitionState,
+                            currentScene = transitionState.currentScene,
+                            shadeExpansion = shadeExpansion,
+                        )
                 }
             }
             .distinctUntilChanged()
@@ -166,14 +199,14 @@
 
     fun shadeScrimShape(
         cornerRadius: Flow<Int>,
-        viewLeftOffset: Flow<Int>
+        viewLeftOffset: Flow<Int>,
     ): Flow<ShadeScrimShape?> =
         combine(shadeScrimClipping, cornerRadius, viewLeftOffset) { clipping, radius, leftOffset ->
                 if (clipping == null) return@combine null
                 ShadeScrimShape(
                     bounds = clipping.bounds.minus(leftOffset = leftOffset),
                     topRadius = radius.takeIf { clipping.rounding.isTopRounded } ?: 0,
-                    bottomRadius = radius.takeIf { clipping.rounding.isBottomRounded } ?: 0
+                    bottomRadius = radius.takeIf { clipping.rounding.isBottomRounded } ?: 0,
                 )
             }
             .dumpWhileCollecting("shadeScrimShape")
@@ -209,10 +242,10 @@
 
     /** Whether the notification stack is scrollable or not. */
     val isScrollable: Flow<Boolean> =
-        sceneInteractor.currentScene
-            .map {
-                sceneInteractor.isSceneInFamily(it, SceneFamilies.NotifShade) ||
-                    it == Scenes.Lockscreen
+        combine(sceneInteractor.currentScene, sceneInteractor.currentOverlays) {
+                currentScene,
+                currentOverlays ->
+                currentScene.showsNotifications() || currentOverlays.any { it.showsNotifications() }
             }
             .dumpWhileCollecting("isScrollable")
 
@@ -242,13 +275,20 @@
         }
     }
 
+    private fun ContentKey.showsNotifications(): Boolean {
+        return when (this) {
+            Overlays.NotificationsShade,
+            Scenes.Lockscreen,
+            Scenes.Shade -> true
+            else -> false
+        }
+    }
+
     @AssistedFactory
     interface Factory {
         fun create(): NotificationScrollViewModel
     }
 }
 
-private fun ChangeScene.isBetween(
-    a: (SceneKey) -> Boolean,
-    b: (SceneKey) -> Boolean,
-): Boolean = (a(fromScene) && b(toScene)) || (b(fromScene) && a(toScene))
+private fun ChangeScene.isBetween(a: (SceneKey) -> Boolean, b: (SceneKey) -> Boolean): Boolean =
+    (a(fromScene) && b(toScene)) || (b(fromScene) && a(toScene))
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 7227b93..50e9249 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -1511,8 +1511,6 @@
         mNotificationShadeWindowController.fetchWindowRootView();
         getNotificationShadeWindowViewController().setupExpandedStatusBar();
         getNotificationShadeWindowViewController().setupCommunalHubLayout();
-        mShadeController.setNotificationShadeWindowViewController(
-                getNotificationShadeWindowViewController());
         mBackActionInteractor.setup(mQsController, mShadeSurface);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpModule.kt
index 0d0f2cd..7919c84 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpModule.kt
@@ -1,6 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.android.systemui.statusbar.phone
 
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.HeadsUpManagerPhone
 import com.android.systemui.statusbar.policy.HeadsUpManager
 import dagger.Binds
 import dagger.Module
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
index 45aee5b..a658115 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
@@ -30,6 +30,7 @@
 import android.view.WindowInsets;
 
 import androidx.annotation.VisibleForTesting;
+
 import com.android.compose.animation.scene.ObservableTransitionState;
 import com.android.internal.policy.SystemBarUtils;
 import com.android.systemui.Dumpable;
@@ -69,7 +70,9 @@
     private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
 
     private boolean mIsStatusBarExpanded = false;
-    private boolean mIsIdleOnGone = true;
+    // Whether the scene container has no UI to render, i.e. is in idle state on the Gone scene and
+    // without any overlays to display.
+    private boolean mIsSceneContainerUiEmpty = true;
     private boolean mIsRemoteUserInteractionOngoing = false;
     private boolean mShouldAdjustInsets = false;
     private View mNotificationShadeWindowView;
@@ -134,7 +137,7 @@
         if (SceneContainerFlag.isEnabled()) {
             javaAdapter.alwaysCollectFlow(
                     sceneInteractor.get().getTransitionState(),
-                    this::onSceneChanged);
+                    this::onSceneContainerTransition);
             javaAdapter.alwaysCollectFlow(
                     sceneInteractor.get().isRemoteUserInteractionOngoing(),
                     this::onRemoteUserInteractionOngoingChanged);
@@ -172,11 +175,13 @@
         }
     }
 
-    private void onSceneChanged(ObservableTransitionState transitionState) {
-        boolean isIdleOnGone = transitionState.isIdle(Scenes.Gone);
-        if (isIdleOnGone != mIsIdleOnGone) {
-            mIsIdleOnGone = isIdleOnGone;
-            if (!isIdleOnGone) {
+    private void onSceneContainerTransition(ObservableTransitionState transitionState) {
+        boolean isSceneContainerUiEmpty = transitionState.isIdle(Scenes.Gone)
+                && ((ObservableTransitionState.Idle) transitionState).getCurrentOverlays()
+                .isEmpty();
+        if (isSceneContainerUiEmpty != mIsSceneContainerUiEmpty) {
+            mIsSceneContainerUiEmpty = isSceneContainerUiEmpty;
+            if (!isSceneContainerUiEmpty) {
                 // make sure our state is sensible
                 mForceCollapsedUntilLayout = false;
             }
@@ -296,7 +301,7 @@
         // underneath.
         return mIsStatusBarExpanded
                 || (SceneContainerFlag.isEnabled()
-                && (!mIsIdleOnGone || mIsRemoteUserInteractionOngoing))
+                && (!mIsSceneContainerUiEmpty || mIsRemoteUserInteractionOngoing))
                 || mPrimaryBouncerInteractor.isShowing().getValue()
                 || mAlternateBouncerInteractor.isVisibleState()
                 || mUnlockedScreenOffAnimationController.isAnimationPlaying();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index ca94363..c089092 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -31,7 +31,6 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.settingslib.bluetooth.BluetoothCallback;
-import com.android.settingslib.bluetooth.BluetoothUtils;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.settingslib.bluetooth.LocalBluetoothProfile;
@@ -72,7 +71,6 @@
     private final LocalBluetoothManager mLocalBluetoothManager;
     private final UserManager mUserManager;
     private final int mCurrentUser;
-    private final Context mContext;
     @GuardedBy("mConnectedDevices")
     private final List<CachedBluetoothDevice> mConnectedDevices = new ArrayList<>();
 
@@ -101,7 +99,6 @@
             @Main Looper mainLooper,
             @Nullable LocalBluetoothManager localBluetoothManager,
             @Nullable BluetoothAdapter bluetoothAdapter) {
-        mContext = context;
         mDumpManager = dumpManager;
         mLogger = logger;
         mBluetoothRepository = bluetoothRepository;
@@ -265,21 +262,9 @@
     }
 
     private Collection<CachedBluetoothDevice> getDevices() {
-        Collection<CachedBluetoothDevice> devices =
-                mLocalBluetoothManager != null
-                        ? mLocalBluetoothManager.getCachedDeviceManager().getCachedDevicesCopy()
-                        : Collections.emptyList();
-        if (com.android.settingslib.flags.Flags.enableHideExclusivelyManagedBluetoothDevice()) {
-            // When the device is exclusively managed by its owner app it needs to be hidden.
-            devices =
-                    devices.stream()
-                            .filter(
-                                    device ->
-                                            !BluetoothUtils.isExclusivelyManagedBluetoothDevice(
-                                                    mContext, device.getDevice()))
-                            .toList();
-        }
-        return devices;
+        return mLocalBluetoothManager != null
+                ? mLocalBluetoothManager.getCachedDeviceManager().getCachedDevicesCopy()
+                : Collections.emptyList();
     }
 
     private void updateConnected() {
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 a3b1867..411ff8b 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
@@ -24,8 +24,6 @@
 import com.android.systemui.inputdevice.tutorial.ui.composable.rememberColorFilterProperty
 import com.android.systemui.res.R
 import com.android.systemui.touchpad.tutorial.ui.gesture.BackGestureMonitor
-import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState
-import com.android.systemui.touchpad.tutorial.ui.gesture.TouchpadGestureMonitor
 
 @Composable
 fun BackGestureTutorialScreen(
@@ -49,14 +47,11 @@
                 )
         )
     val gestureMonitorProvider =
-        object : GestureMonitorProvider {
-            override fun createGestureMonitor(
-                gestureDistanceThresholdPx: Int,
-                gestureStateChangedCallback: (GestureState) -> Unit
-            ): TouchpadGestureMonitor {
-                return BackGestureMonitor(gestureDistanceThresholdPx, gestureStateChangedCallback)
+        DistanceBasedGestureMonitorProvider(
+            monitorFactory = { distanceThresholdPx, gestureStateCallback ->
+                BackGestureMonitor(distanceThresholdPx, gestureStateCallback)
             }
-        }
+        )
     GestureTutorialScreen(screenConfig, gestureMonitorProvider, onDoneButtonClicked, onBack)
 }
 
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 57d7c84..0ecbf70 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,6 +16,7 @@
 
 package com.android.systemui.touchpad.tutorial.ui.composable
 
+import android.content.res.Resources
 import androidx.activity.compose.BackHandler
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.BoxScope
@@ -39,12 +40,35 @@
 import com.android.systemui.touchpad.tutorial.ui.gesture.TouchpadGestureMonitor
 
 interface GestureMonitorProvider {
-    fun createGestureMonitor(
-        gestureDistanceThresholdPx: Int,
+
+    @Composable
+    fun rememberGestureMonitor(
+        resources: Resources,
         gestureStateChangedCallback: (GestureState) -> Unit
     ): TouchpadGestureMonitor
 }
 
+typealias gestureStateCallback = (GestureState) -> Unit
+
+class DistanceBasedGestureMonitorProvider(
+    val monitorFactory: (Int, gestureStateCallback) -> TouchpadGestureMonitor
+) : GestureMonitorProvider {
+
+    @Composable
+    override fun rememberGestureMonitor(
+        resources: Resources,
+        gestureStateChangedCallback: (GestureState) -> Unit
+    ): TouchpadGestureMonitor {
+        val distanceThresholdPx =
+            resources.getDimensionPixelSize(
+                com.android.internal.R.dimen.system_gestures_distance_threshold
+            )
+        return remember(distanceThresholdPx) {
+            monitorFactory(distanceThresholdPx, gestureStateChangedCallback)
+        }
+    }
+}
+
 fun GestureState.toTutorialActionState(): TutorialActionState {
     return when (this) {
         NOT_STARTED -> TutorialActionState.NOT_STARTED
@@ -62,19 +86,12 @@
 ) {
     BackHandler(onBack = onBack)
     var gestureState by remember { mutableStateOf(NOT_STARTED) }
-    val swipeDistanceThresholdPx =
-        LocalContext.current.resources.getDimensionPixelSize(
-            com.android.internal.R.dimen.system_gestures_distance_threshold
+    val gestureMonitor =
+        gestureMonitorProvider.rememberGestureMonitor(
+            resources = LocalContext.current.resources,
+            gestureStateChangedCallback = { gestureState = it }
         )
-    val gestureHandler =
-        remember(swipeDistanceThresholdPx) {
-            TouchpadGestureHandler(
-                gestureMonitorProvider.createGestureMonitor(
-                    swipeDistanceThresholdPx,
-                    gestureStateChangedCallback = { gestureState = it }
-                )
-            )
-        }
+    val gestureHandler = remember(gestureMonitor) { TouchpadGestureHandler(gestureMonitor) }
     TouchpadGesturesHandlingBox(gestureHandler, gestureState) {
         ActionTutorialContent(
             gestureState.toTutorialActionState(),
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
index d4eb0cd..f2fec5f 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
@@ -23,9 +23,7 @@
 import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialScreenConfig
 import com.android.systemui.inputdevice.tutorial.ui.composable.rememberColorFilterProperty
 import com.android.systemui.res.R
-import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState
 import com.android.systemui.touchpad.tutorial.ui.gesture.HomeGestureMonitor
-import com.android.systemui.touchpad.tutorial.ui.gesture.TouchpadGestureMonitor
 
 @Composable
 fun HomeGestureTutorialScreen(
@@ -49,14 +47,11 @@
                 )
         )
     val gestureMonitorProvider =
-        object : GestureMonitorProvider {
-            override fun createGestureMonitor(
-                gestureDistanceThresholdPx: Int,
-                gestureStateChangedCallback: (GestureState) -> Unit
-            ): TouchpadGestureMonitor {
-                return HomeGestureMonitor(gestureDistanceThresholdPx, gestureStateChangedCallback)
+        DistanceBasedGestureMonitorProvider(
+            monitorFactory = { distanceThresholdPx, gestureStateCallback ->
+                HomeGestureMonitor(distanceThresholdPx, gestureStateCallback)
             }
-        }
+        )
     GestureTutorialScreen(screenConfig, gestureMonitorProvider, onDoneButtonClicked, onBack)
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt
new file mode 100644
index 0000000..b2fb6cd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.content.res.Resources
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import com.airbnb.lottie.compose.rememberLottieDynamicProperties
+import com.android.compose.theme.LocalAndroidColorScheme
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialScreenConfig
+import com.android.systemui.inputdevice.tutorial.ui.composable.rememberColorFilterProperty
+import com.android.systemui.res.R
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState
+import com.android.systemui.touchpad.tutorial.ui.gesture.RecentAppsGestureMonitor
+import com.android.systemui.touchpad.tutorial.ui.gesture.TouchpadGestureMonitor
+
+@Composable
+fun RecentAppsGestureTutorialScreen(
+    onDoneButtonClicked: () -> Unit,
+    onBack: () -> Unit,
+) {
+    val screenConfig =
+        TutorialScreenConfig(
+            colors = rememberScreenColors(),
+            strings =
+                TutorialScreenConfig.Strings(
+                    titleResId = R.string.touchpad_recent_apps_gesture_action_title,
+                    bodyResId = R.string.touchpad_recent_apps_gesture_guidance,
+                    titleSuccessResId = R.string.touchpad_recent_apps_gesture_success_title,
+                    bodySuccessResId = R.string.touchpad_recent_apps_gesture_success_body
+                ),
+            animations =
+                TutorialScreenConfig.Animations(
+                    educationResId = R.raw.trackpad_recent_apps_edu,
+                    successResId = R.raw.trackpad_recent_apps_success
+                )
+        )
+    val gestureMonitorProvider =
+        object : GestureMonitorProvider {
+            @Composable
+            override fun rememberGestureMonitor(
+                resources: Resources,
+                gestureStateChangedCallback: (GestureState) -> Unit
+            ): TouchpadGestureMonitor {
+                val distanceThresholdPx =
+                    resources.getDimensionPixelSize(
+                        com.android.internal.R.dimen.system_gestures_distance_threshold
+                    )
+                val velocityThresholdPxPerMs =
+                    resources.getDimension(R.dimen.touchpad_recent_apps_gesture_velocity_threshold)
+                return remember(distanceThresholdPx, velocityThresholdPxPerMs) {
+                    RecentAppsGestureMonitor(
+                        distanceThresholdPx,
+                        gestureStateChangedCallback,
+                        velocityThresholdPxPerMs
+                    )
+                }
+            }
+        }
+    GestureTutorialScreen(screenConfig, gestureMonitorProvider, onDoneButtonClicked, onBack)
+}
+
+@Composable
+private fun rememberScreenColors(): TutorialScreenConfig.Colors {
+    val secondaryFixedDim = LocalAndroidColorScheme.current.secondaryFixedDim
+    val onSecondaryFixed = LocalAndroidColorScheme.current.onSecondaryFixed
+    val onSecondaryFixedVariant = LocalAndroidColorScheme.current.onSecondaryFixedVariant
+    val dynamicProperties =
+        rememberLottieDynamicProperties(
+            rememberColorFilterProperty(".secondaryFixedDim", secondaryFixedDim),
+            rememberColorFilterProperty(".onSecondaryFixed", onSecondaryFixed),
+            rememberColorFilterProperty(".onSecondaryFixedVariant", onSecondaryFixedVariant)
+        )
+    val screenColors =
+        remember(dynamicProperties) {
+            TutorialScreenConfig.Colors(
+                background = onSecondaryFixed,
+                title = secondaryFixedDim,
+                animationColors = dynamicProperties,
+            )
+        }
+    return screenColors
+}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt
index 65b452a..5a77c04 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt
@@ -41,7 +41,7 @@
 fun TutorialSelectionScreen(
     onBackTutorialClicked: () -> Unit,
     onHomeTutorialClicked: () -> Unit,
-    onActionKeyTutorialClicked: () -> Unit,
+    onRecentAppsTutorialClicked: () -> Unit,
     onDoneButtonClicked: () -> Unit,
 ) {
     Column(
@@ -55,7 +55,7 @@
         TutorialSelectionButtons(
             onBackTutorialClicked = onBackTutorialClicked,
             onHomeTutorialClicked = onHomeTutorialClicked,
-            onActionKeyTutorialClicked = onActionKeyTutorialClicked,
+            onRecentAppsTutorialClicked = onRecentAppsTutorialClicked,
             modifier = Modifier.padding(60.dp)
         )
         DoneButton(
@@ -69,7 +69,7 @@
 private fun TutorialSelectionButtons(
     onBackTutorialClicked: () -> Unit,
     onHomeTutorialClicked: () -> Unit,
-    onActionKeyTutorialClicked: () -> Unit,
+    onRecentAppsTutorialClicked: () -> Unit,
     modifier: Modifier = Modifier
 ) {
     Row(
@@ -90,8 +90,8 @@
             modifier = Modifier.weight(1f)
         )
         TutorialButton(
-            text = stringResource(R.string.touchpad_tutorial_action_key_button),
-            onClick = onActionKeyTutorialClicked,
+            text = stringResource(R.string.touchpad_tutorial_recent_apps_gesture_button),
+            onClick = onRecentAppsTutorialClicked,
             color = MaterialTheme.colorScheme.tertiary,
             modifier = Modifier.weight(1f)
         )
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 821b51a..46ea352 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
@@ -32,10 +32,12 @@
 import com.android.systemui.inputdevice.tutorial.ui.composable.ActionKeyTutorialScreen
 import com.android.systemui.touchpad.tutorial.ui.composable.BackGestureTutorialScreen
 import com.android.systemui.touchpad.tutorial.ui.composable.HomeGestureTutorialScreen
+import com.android.systemui.touchpad.tutorial.ui.composable.RecentAppsGestureTutorialScreen
 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.RECENT_APPS_GESTURE
 import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.TUTORIAL_SELECTION
 import com.android.systemui.touchpad.tutorial.ui.viewmodel.TouchpadTutorialViewModel
 import javax.inject.Inject
@@ -84,7 +86,7 @@
             TutorialSelectionScreen(
                 onBackTutorialClicked = { vm.goTo(BACK_GESTURE) },
                 onHomeTutorialClicked = { vm.goTo(HOME_GESTURE) },
-                onActionKeyTutorialClicked = { vm.goTo(ACTION_KEY) },
+                onRecentAppsTutorialClicked = { vm.goTo(RECENT_APPS_GESTURE) },
                 onDoneButtonClicked = closeTutorial
             )
         BACK_GESTURE ->
@@ -97,6 +99,11 @@
                 onDoneButtonClicked = { vm.goTo(TUTORIAL_SELECTION) },
                 onBack = { vm.goTo(TUTORIAL_SELECTION) },
             )
+        RECENT_APPS_GESTURE ->
+            RecentAppsGestureTutorialScreen(
+                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) },
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 43266ad..599e1b1 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
@@ -64,5 +64,6 @@
     TUTORIAL_SELECTION,
     BACK_GESTURE,
     HOME_GESTURE,
+    RECENT_APPS_GESTURE,
     ACTION_KEY,
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 52fde7e..4005e10 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -494,7 +494,7 @@
 
     @Test
     public void testIgnoresSimStateCallback_rebroadcast() {
-        Intent intent = new Intent(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
+        Intent intent = defaultSimStateChangedIntent();
 
         mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext(), intent);
         mTestableLooper.processAllMessages();
@@ -515,7 +515,8 @@
 
     @Test
     public void testTelephonyCapable_SimState_Absent() {
-        Intent intent = new Intent(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
+
+        Intent intent = defaultSimStateChangedIntent();
         intent.putExtra(Intent.EXTRA_SIM_STATE,
                 Intent.SIM_STATE_ABSENT);
         mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext(),
@@ -526,7 +527,7 @@
 
     @Test
     public void testTelephonyCapable_SimState_CardIOError() {
-        Intent intent = new Intent(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
+        Intent intent = defaultSimStateChangedIntent();
         intent.putExtra(Intent.EXTRA_SIM_STATE,
                 Intent.SIM_STATE_CARD_IO_ERROR);
         mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext(),
@@ -593,7 +594,7 @@
         ServiceState state = new ServiceState();
         state.setState(ServiceState.STATE_OUT_OF_SERVICE);
         state.fillInNotifierBundle(data);
-        Intent intent = new Intent(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
+        Intent intent = defaultSimStateChangedIntent();
         intent.putExtra(Intent.EXTRA_SIM_STATE
                 , Intent.SIM_STATE_NOT_READY);
         mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext()
@@ -608,7 +609,7 @@
         ServiceState state = new ServiceState();
         state.setState(ServiceState.STATE_OUT_OF_SERVICE);
         state.fillInNotifierBundle(data);
-        Intent intent = new Intent(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
+        Intent intent = defaultSimStateChangedIntent();
         intent.putExtra(Intent.EXTRA_SIM_STATE
                 , Intent.SIM_STATE_READY);
         mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext()
@@ -649,7 +650,7 @@
         ServiceState state = new ServiceState();
         state.setState(ServiceState.STATE_IN_SERVICE);
         state.fillInNotifierBundle(data);
-        Intent intentSimState = new Intent(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
+        Intent intentSimState = defaultSimStateChangedIntent();
         intentSimState.putExtra(Intent.EXTRA_SIM_STATE
                 , Intent.SIM_STATE_LOADED);
         mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext()
@@ -2256,6 +2257,12 @@
         Assert.assertFalse(mKeyguardUpdateMonitor.forceIsDismissibleIsKeepingDeviceUnlocked());
     }
 
+    private Intent defaultSimStateChangedIntent() {
+        Intent intent = new Intent(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
+        intent.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, 0);
+        return intent;
+    }
+
     private void verifyFingerprintAuthenticateNeverCalled() {
         verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), any());
         verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
index c451c32..400b3b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
@@ -174,6 +174,7 @@
         mMenuAnimationController = mMenuView.getMenuAnimationController();
 
         doNothing().when(mSpyContext).startActivity(any());
+        doNothing().when(mSpyContext).startActivityAsUser(any(), any());
         when(mSpyContext.getPackageManager()).thenReturn(mMockPackageManager);
 
         mLastAccessibilityButtonTargets =
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 76539d7..633efd8 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
@@ -63,21 +63,27 @@
     private val defaultDisplay =
         display(type = TYPE_INTERNAL, id = DEFAULT_DISPLAY, state = Display.STATE_ON)
 
-    private lateinit var displayRepository: DisplayRepositoryImpl
+    // This is Lazy as displays could be set before the instance is created, and we want to verify
+    // that the initial state (soon after construction) contains the expected ones set in every
+    // test.
+    private val displayRepository: DisplayRepositoryImpl by lazy {
+        DisplayRepositoryImpl(
+                displayManager,
+                testHandler,
+                TestScope(UnconfinedTestDispatcher()),
+                UnconfinedTestDispatcher(),
+            )
+            .also {
+                verify(displayManager, never()).registerDisplayListener(any(), any())
+                // It needs to be called, just once, for the initial value.
+                verify(displayManager).getDisplays()
+            }
+    }
 
     @Before
     fun setup() {
         setDisplays(listOf(defaultDisplay))
         setAllDisplaysIncludingDisabled(DEFAULT_DISPLAY)
-        displayRepository =
-            DisplayRepositoryImpl(
-                displayManager,
-                testHandler,
-                TestScope(UnconfinedTestDispatcher()),
-                UnconfinedTestDispatcher()
-            )
-        verify(displayManager, never()).registerDisplayListener(any(), any())
-        verify(displayManager, never()).getDisplays(any())
     }
 
     @Test
@@ -502,7 +508,7 @@
             .registerDisplayListener(
                 connectedDisplayListener.capture(),
                 eq(testHandler),
-                eq(DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED)
+                eq(DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED),
             )
         return flowValue
     }
@@ -522,7 +528,7 @@
                     DisplayManager.EVENT_FLAG_DISPLAY_ADDED or
                         DisplayManager.EVENT_FLAG_DISPLAY_CHANGED or
                         DisplayManager.EVENT_FLAG_DISPLAY_REMOVED
-                )
+                ),
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 597ffef..9e0d358 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -26,6 +26,7 @@
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
 import static com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR;
+import static com.android.systemui.Flags.FLAG_SIM_PIN_BOUNCER_RESET;
 import static com.android.systemui.keyguard.KeyguardViewMediator.DELAYED_KEYGUARD_ACTION;
 import static com.android.systemui.keyguard.KeyguardViewMediator.KEYGUARD_LOCK_AFTER_DELAY_DEFAULT;
 import static com.android.systemui.keyguard.KeyguardViewMediator.REBOOT_MAINLINE_UPDATE;
@@ -62,6 +63,7 @@
 import android.os.PowerManager;
 import android.os.PowerManager.WakeLock;
 import android.os.RemoteException;
+import android.platform.test.annotations.EnableFlags;
 import android.telephony.TelephonyManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -588,6 +590,35 @@
 
     @Test
     @TestableLooper.RunWithLooper(setAsMainLooper = true)
+    @EnableFlags(FLAG_SIM_PIN_BOUNCER_RESET)
+    public void resetStateLocked_whenSimNotReadyAndWasLockedPrior() {
+        // When showing and provisioned
+        mViewMediator.onSystemReady();
+        when(mUpdateMonitor.isDeviceProvisioned()).thenReturn(true);
+        mViewMediator.setShowingLocked(true, "");
+
+        // and a SIM becomes locked and requires a PIN
+        mViewMediator.mUpdateCallback.onSimStateChanged(
+                1 /* subId */,
+                0 /* slotId */,
+                TelephonyManager.SIM_STATE_PIN_REQUIRED);
+        TestableLooper.get(this).processAllMessages();
+
+        reset(mStatusBarKeyguardViewManager);
+
+        // but then disabled by a NOT_READY
+        mViewMediator.mUpdateCallback.onSimStateChanged(
+                1 /* subId */,
+                0 /* slotId */,
+                TelephonyManager.SIM_STATE_NOT_READY);
+        TestableLooper.get(this).processAllMessages();
+
+        // A call to reset the keyguard and bouncer was invoked
+        verify(mStatusBarKeyguardViewManager).reset(true);
+    }
+
+    @Test
+    @TestableLooper.RunWithLooper(setAsMainLooper = true)
     public void restoreBouncerWhenSimLockedAndKeyguardIsGoingAway_initiallyNotShowing() {
         // When showing and provisioned
         mViewMediator.onSystemReady();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
index b02cccc..4959224 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
@@ -146,9 +146,7 @@
         whenever(packageManager.resolveServiceAsUser(any(), anyInt(), anyInt()))
             .thenReturn(mock(ResolveInfo::class.java))
 
-        mSetFlagsRule.disableFlags(
-            com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR,
-        )
+        mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
 
         subject = createOverviewProxyService(context)
     }
@@ -283,6 +281,7 @@
             statusBarWinController,
             sysUiState,
             mock(),
+            mock(),
             userTracker,
             userManager,
             wakefulnessLifecycle,
@@ -294,7 +293,7 @@
             dumpManager,
             unfoldTransitionProgressForwarder,
             broadcastDispatcher,
-            keyboardTouchpadEduStatsInteractor
+            keyboardTouchpadEduStatsInteractor,
         )
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java
index f4d7a5b..d58b68b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java
@@ -47,7 +47,6 @@
 import androidx.test.filters.MediumTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.flags.FakeFeatureFlags;
 
 import com.google.common.util.concurrent.ListenableFuture;
 
@@ -79,7 +78,6 @@
     private static final ZonedDateTime CAPTURE_TIME =
             ZonedDateTime.of(LocalDateTime.of(2020, 12, 15, 13, 15), ZoneId.of("America/New_York"));
 
-    private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
     @Mock
     private ContentResolver mMockContentResolver;
 
@@ -125,7 +123,7 @@
     @Test
     public void testImageExport() throws ExecutionException, InterruptedException, IOException {
         ContentResolver contentResolver = mContext.getContentResolver();
-        ImageExporter exporter = new ImageExporter(contentResolver, mFeatureFlags);
+        ImageExporter exporter = new ImageExporter(contentResolver);
 
         UUID requestId = UUID.fromString("3c11da99-9284-4863-b1d5-6f3684976814");
         Bitmap original = createCheckerBitmap(10, 10, 10);
@@ -191,7 +189,7 @@
         // metadata are not affected by the specified file name.
         final String customizedFileName = "customized_file_name";
         ContentResolver contentResolver = mContext.getContentResolver();
-        ImageExporter exporter = new ImageExporter(contentResolver, mFeatureFlags);
+        ImageExporter exporter = new ImageExporter(contentResolver);
 
         UUID requestId = UUID.fromString("3c11da99-9284-4863-b1d5-6f3684976814");
         Bitmap original = createCheckerBitmap(10, 10, 10);
@@ -228,7 +226,7 @@
 
     @Test
     public void testSetUser() {
-        ImageExporter exporter = new ImageExporter(mMockContentResolver, mFeatureFlags);
+        ImageExporter exporter = new ImageExporter(mMockContentResolver);
 
         UserHandle imageUserHande = UserHandle.of(10);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java
index 886b32b..717f82d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java
@@ -31,12 +31,10 @@
 import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
 
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -74,7 +72,6 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
 import org.mockito.ArgumentMatcher;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
@@ -98,6 +95,10 @@
     private static final String BACKLINKS_TASK_APP_NAME = "Ultimate question app";
     private static final String BACKLINKS_TASK_PACKAGE_NAME = "backlinksTaskPackageName";
     private static final AssistContent EMPTY_ASSIST_CONTENT = new AssistContent();
+    private static final ResolveInfo BACKLINKS_TASK_RESOLVE_INFO =
+            createBacklinksTaskResolveInfo();
+    private static final RunningTaskInfo BACKLINKS_TASK_RUNNING_TASK_INFO =
+            createTaskInfoForBacklinksTask();
 
     @Mock
     private AppClipsCrossProcessHelper mAppClipsCrossProcessHelper;
@@ -111,26 +112,11 @@
     Context mMockedContext;
     @Mock
     private PackageManager mPackageManager;
-    private ArgumentCaptor<Intent> mPackageManagerLauncherIntentCaptor;
-    private ArgumentCaptor<Intent> mPackageManagerBacklinkIntentCaptor;
     private AppClipsViewModel mViewModel;
 
     @Before
     public void setUp() throws RemoteException {
         MockitoAnnotations.initMocks(this);
-        mPackageManagerLauncherIntentCaptor = ArgumentCaptor.forClass(Intent.class);
-        mPackageManagerBacklinkIntentCaptor = ArgumentCaptor.forClass(Intent.class);
-
-        // Set up mocking for backlinks.
-        when(mAtmService.getTasks(Integer.MAX_VALUE, false, false, DEFAULT_DISPLAY))
-                .thenReturn(List.of(createTaskInfoForBacklinksTask()));
-        ResolveInfo expectedResolveInfo = createBacklinksTaskResolveInfo();
-        when(mPackageManager.resolveActivity(mPackageManagerLauncherIntentCaptor.capture(),
-                anyInt())).thenReturn(expectedResolveInfo);
-        when(mPackageManager.queryIntentActivities(mPackageManagerBacklinkIntentCaptor.capture(),
-                eq(MATCH_DEFAULT_ONLY))).thenReturn(List.of(expectedResolveInfo));
-        when(mPackageManager.loadItemIcon(any(), any())).thenReturn(FAKE_DRAWABLE);
-        when(mMockedContext.getPackageManager()).thenReturn(mPackageManager);
 
         mViewModel = new AppClipsViewModel.Factory(mAppClipsCrossProcessHelper, mImageExporter,
                 mAtmService, mAssistContentRequester, mMockedContext,
@@ -208,19 +194,18 @@
     }
 
     @Test
-    public void triggerBacklinks_shouldUpdateBacklinks_withUri() {
+    public void triggerBacklinks_shouldUpdateBacklinks_withUri() throws RemoteException {
         Uri expectedUri = Uri.parse("https://developers.android.com");
         AssistContent contentWithUri = new AssistContent();
         contentWithUri.setWebUri(expectedUri);
         mockForAssistContent(contentWithUri, BACKLINKS_TASK_ID);
+        mockPackageManagerToResolveUri(expectedUri, BACKLINKS_TASK_RESOLVE_INFO);
+        mockBacklinksTaskForMainLauncherIntent();
+        mockAtmToReturnRunningTaskInfo(BACKLINKS_TASK_RUNNING_TASK_INFO);
 
         mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY);
         waitForIdleSync();
 
-        Intent queriedIntent = mPackageManagerBacklinkIntentCaptor.getValue();
-        assertThat(queriedIntent.getData()).isEqualTo(expectedUri);
-        assertThat(queriedIntent.getAction()).isEqualTo(ACTION_VIEW);
-
         BacklinksData result = (BacklinksData) mViewModel.mSelectedBacklinksLiveData.getValue();
         assertThat(result.getAppIcon()).isEqualTo(FAKE_DRAWABLE);
         ClipData clipData = result.getClipData();
@@ -234,14 +219,17 @@
     }
 
     @Test
-    public void triggerBacklinks_shouldUpdateBacklinks_withUriForDifferentApp() {
+    public void triggerBacklinks_shouldUpdateBacklinks_withUriForDifferentApp()
+            throws RemoteException {
+        // Mock for the screenshotted app so that it can be used for fallback backlink.
+        mockAtmToReturnRunningTaskInfo(BACKLINKS_TASK_RUNNING_TASK_INFO);
+        mockBacklinksTaskForMainLauncherIntent();
+
         Uri expectedUri = Uri.parse("https://android.com");
         AssistContent contentWithUri = new AssistContent();
         contentWithUri.setWebUri(expectedUri);
         mockForAssistContent(contentWithUri, BACKLINKS_TASK_ID);
 
-        // Reset PackageManager mocking done in setup.
-        reset(mPackageManager);
         String package2 = BACKLINKS_TASK_PACKAGE_NAME + 2;
         String appName2 = BACKLINKS_TASK_APP_NAME + 2;
         ResolveInfo resolveInfo2 = createBacklinksTaskResolveInfo();
@@ -250,14 +238,9 @@
         activityInfo2.packageName = package2;
         activityInfo2.applicationInfo.packageName = package2;
 
-        Intent app2LauncherIntent = new Intent(ACTION_MAIN).addCategory(
-                CATEGORY_LAUNCHER).setPackage(package2);
-        when(mPackageManager.resolveActivity(intentEquals(app2LauncherIntent), eq(/* flags= */ 0)))
-                .thenReturn(resolveInfo2);
-        Intent uriIntent = new Intent(ACTION_VIEW).setData(expectedUri);
-        when(mPackageManager.queryIntentActivities(intentEquals(uriIntent), eq(MATCH_DEFAULT_ONLY)))
-                .thenReturn(List.of(resolveInfo2));
-        when(mPackageManager.loadItemIcon(any(), any())).thenReturn(FAKE_DRAWABLE);
+        // Mock the different app resolve info so that backlinks resolves to this different app.
+        mockPackageManagerToResolveUri(expectedUri, resolveInfo2);
+        mockPmToResolveForMainLauncherIntent(resolveInfo2);
 
         mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY);
         waitForIdleSync();
@@ -273,30 +256,15 @@
         assertThat(mViewModel.getBacklinksLiveData().getValue().size()).isEqualTo(1);
     }
 
-    private static class IntentMatcher implements ArgumentMatcher<Intent> {
-        private final Intent mExpectedIntent;
-
-        IntentMatcher(Intent expectedIntent) {
-            mExpectedIntent = expectedIntent;
-        }
-
-        @Override
-        public boolean matches(Intent actualIntent) {
-            return actualIntent != null && mExpectedIntent.filterEquals(actualIntent);
-        }
-    }
-
-    private static Intent intentEquals(Intent intent) {
-        return argThat(new IntentMatcher(intent));
-    }
-
     @Test
-    public void triggerBacklinks_withNonResolvableUri_usesMainLauncherIntent() {
+    public void triggerBacklinks_withNonResolvableUri_usesMainLauncherIntent()
+            throws RemoteException {
         Uri expectedUri = Uri.parse("https://developers.android.com");
         AssistContent contentWithUri = new AssistContent();
         contentWithUri.setWebUri(expectedUri);
         mockForAssistContent(contentWithUri, BACKLINKS_TASK_ID);
-        resetPackageManagerMockingForUsingFallbackBacklinks();
+        mockBacklinksTaskForMainLauncherIntent();
+        mockAtmToReturnRunningTaskInfo(BACKLINKS_TASK_RUNNING_TASK_INFO);
 
         mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY);
         waitForIdleSync();
@@ -305,18 +273,19 @@
     }
 
     @Test
-    public void triggerBacklinks_shouldUpdateBacklinks_withAppProvidedIntent() {
+    public void triggerBacklinks_shouldUpdateBacklinks_withAppProvidedIntent()
+            throws RemoteException {
         Intent expectedIntent = new Intent().setPackage(BACKLINKS_TASK_PACKAGE_NAME);
         AssistContent contentWithAppProvidedIntent = new AssistContent();
         contentWithAppProvidedIntent.setIntent(expectedIntent);
         mockForAssistContent(contentWithAppProvidedIntent, BACKLINKS_TASK_ID);
+        mockQueryIntentActivities(expectedIntent, BACKLINKS_TASK_RESOLVE_INFO);
+        mockBacklinksTaskForMainLauncherIntent();
+        mockAtmToReturnRunningTaskInfo(BACKLINKS_TASK_RUNNING_TASK_INFO);
 
         mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY);
         waitForIdleSync();
 
-        Intent queriedIntent = mPackageManagerBacklinkIntentCaptor.getValue();
-        assertThat(queriedIntent.getPackage()).isEqualTo(expectedIntent.getPackage());
-
         BacklinksData result = (BacklinksData) mViewModel.mSelectedBacklinksLiveData.getValue();
         assertThat(result.getAppIcon()).isEqualTo(FAKE_DRAWABLE);
         ClipData clipData = result.getClipData();
@@ -328,12 +297,14 @@
     }
 
     @Test
-    public void triggerBacklinks_withNonResolvableAppProvidedIntent_usesMainLauncherIntent() {
+    public void triggerBacklinks_withNonResolvableAppProvidedIntent_usesMainLauncherIntent()
+            throws RemoteException {
         Intent expectedIntent = new Intent().setPackage(BACKLINKS_TASK_PACKAGE_NAME);
         AssistContent contentWithAppProvidedIntent = new AssistContent();
         contentWithAppProvidedIntent.setIntent(expectedIntent);
         mockForAssistContent(contentWithAppProvidedIntent, BACKLINKS_TASK_ID);
-        resetPackageManagerMockingForUsingFallbackBacklinks();
+        mockBacklinksTaskForMainLauncherIntent();
+        mockAtmToReturnRunningTaskInfo(BACKLINKS_TASK_RUNNING_TASK_INFO);
 
         mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY);
         waitForIdleSync();
@@ -342,25 +313,28 @@
     }
 
     @Test
-    public void triggerBacklinks_shouldUpdateBacklinks_withMainLauncherIntent() {
+    public void triggerBacklinks_shouldUpdateBacklinks_withMainLauncherIntent()
+            throws RemoteException {
         mockForAssistContent(EMPTY_ASSIST_CONTENT, BACKLINKS_TASK_ID);
+        mockBacklinksTaskForMainLauncherIntent();
+        mockAtmToReturnRunningTaskInfo(BACKLINKS_TASK_RUNNING_TASK_INFO);
 
         mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY);
         waitForIdleSync();
 
-        Intent queriedIntent = mPackageManagerLauncherIntentCaptor.getValue();
-        assertThat(queriedIntent.getPackage()).isEqualTo(BACKLINKS_TASK_PACKAGE_NAME);
-        assertThat(queriedIntent.getAction()).isEqualTo(ACTION_MAIN);
-        assertThat(queriedIntent.getCategories()).containsExactly(CATEGORY_LAUNCHER);
-
         verifyMainLauncherBacklinksIntent();
     }
 
     @Test
-    public void triggerBacklinks_withNonResolvableMainLauncherIntent_noBacklinksAvailable() {
-        reset(mPackageManager);
+    public void triggerBacklinks_withNonResolvableMainLauncherIntent_noBacklinksAvailable()
+            throws RemoteException {
         mockForAssistContent(EMPTY_ASSIST_CONTENT, BACKLINKS_TASK_ID);
 
+        // Mock ATM service so we return task info but don't mock PM to resolve the task intent.
+        when(mAtmService.getTasks(Integer.MAX_VALUE, /* filterOnlyVisibleRecents= */
+                false, /* keepIntentExtras= */ false, DEFAULT_DISPLAY)).thenReturn(
+                List.of(BACKLINKS_TASK_RUNNING_TASK_INFO));
+
         mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY);
         waitForIdleSync();
 
@@ -371,11 +345,9 @@
     @Test
     public void triggerBacklinks_nonStandardActivityIgnored_noBacklinkAvailable()
             throws RemoteException {
-        reset(mAtmService);
         RunningTaskInfo taskInfo = createTaskInfoForBacklinksTask();
         taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_HOME);
-        when(mAtmService.getTasks(Integer.MAX_VALUE, false, false, DEFAULT_DISPLAY))
-                .thenReturn(List.of(taskInfo));
+        mockAtmToReturnRunningTaskInfo(taskInfo);
 
         mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY);
         waitForIdleSync();
@@ -399,9 +371,9 @@
     public void triggerBacklinks_multipleAppsOnScreen_multipleBacklinksAvailable()
             throws RemoteException {
         // Set up mocking for multiple backlinks.
-        reset(mAtmService, mPackageManager);
-        RunningTaskInfo runningTaskInfo1 = createTaskInfoForBacklinksTask();
         ResolveInfo resolveInfo1 = createBacklinksTaskResolveInfo();
+        RunningTaskInfo runningTaskInfo1 = createTaskInfoForBacklinksTask();
+        runningTaskInfo1.topActivityInfo = resolveInfo1.activityInfo;
 
         int taskId2 = BACKLINKS_TASK_ID + 2;
         String package2 = BACKLINKS_TASK_PACKAGE_NAME + 2;
@@ -418,27 +390,23 @@
         runningTaskInfo2.topActivityInfo = resolveInfo2.activityInfo;
         runningTaskInfo2.baseIntent = new Intent().setComponent(runningTaskInfo2.topActivity);
 
-        // For each task, the logic queries PM 3 times, twice for verifying if an app can be
-        // launched via launcher and once with the data provided in backlink intent.
-        when(mPackageManager.resolveActivity(any(), anyInt())).thenReturn(resolveInfo1,
-                resolveInfo1, resolveInfo2, resolveInfo2);
-        when(mPackageManager.queryIntentActivities(any(Intent.class), eq(MATCH_DEFAULT_ONLY)))
-                .thenReturn(List.of(resolveInfo1)).thenReturn(List.of(resolveInfo2));
-        when(mPackageManager.loadItemIcon(any(), any())).thenReturn(FAKE_DRAWABLE);
-        when(mAtmService.getTasks(Integer.MAX_VALUE, false, false, DEFAULT_DISPLAY))
-                .thenReturn(List.of(runningTaskInfo1, runningTaskInfo2));
+        mockAtmToReturnRunningTaskInfo(runningTaskInfo1, runningTaskInfo2);
+        mockPmToResolveForMainLauncherIntent(resolveInfo1);
+        mockPmToResolveForMainLauncherIntent(resolveInfo2);
 
         // Using app provided web uri for the first backlink.
         Uri expectedUri = Uri.parse("https://developers.android.com");
         AssistContent contentWithUri = new AssistContent();
         contentWithUri.setWebUri(expectedUri);
         mockForAssistContent(contentWithUri, BACKLINKS_TASK_ID);
+        mockPackageManagerToResolveUri(expectedUri, resolveInfo1);
 
         // Using app provided intent for the second backlink.
         Intent expectedIntent = new Intent().setPackage(package2);
         AssistContent contentWithAppProvidedIntent = new AssistContent();
         contentWithAppProvidedIntent.setIntent(expectedIntent);
         mockForAssistContent(contentWithAppProvidedIntent, taskId2);
+        mockQueryIntentActivities(expectedIntent, resolveInfo2);
 
         // Set up complete, trigger the backlinks action.
         mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY);
@@ -460,11 +428,12 @@
     @Test
     public void triggerBacklinks_singleCrossProfileApp_shouldIndicateError()
             throws RemoteException {
-        reset(mAtmService);
         RunningTaskInfo taskInfo = createTaskInfoForBacklinksTask();
         taskInfo.userId = UserHandle.myUserId() + 1;
-        when(mAtmService.getTasks(Integer.MAX_VALUE, false, false, DEFAULT_DISPLAY))
-                .thenReturn(List.of(taskInfo));
+        when(mAtmService.getTasks(Integer.MAX_VALUE, /* filterOnlyVisibleRecents= */
+                false, /* keepIntentExtra */ false, DEFAULT_DISPLAY)).thenReturn(List.of(taskInfo));
+        when(mPackageManager.loadItemIcon(taskInfo.topActivityInfo,
+                taskInfo.topActivityInfo.applicationInfo)).thenReturn(FAKE_DRAWABLE);
 
         mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY);
         waitForIdleSync();
@@ -478,13 +447,13 @@
             throws RemoteException {
         // Set up mocking for multiple backlinks.
         mockForAssistContent(EMPTY_ASSIST_CONTENT, BACKLINKS_TASK_ID);
-        reset(mAtmService);
-        RunningTaskInfo runningTaskInfo1 = createTaskInfoForBacklinksTask();
         RunningTaskInfo runningTaskInfo2 = createTaskInfoForBacklinksTask();
         runningTaskInfo2.userId = UserHandle.myUserId() + 1;
 
-        when(mAtmService.getTasks(anyInt(), anyBoolean(), anyBoolean(), anyInt()))
-                .thenReturn(List.of(runningTaskInfo1, runningTaskInfo2));
+        mockAtmToReturnRunningTaskInfo(BACKLINKS_TASK_RUNNING_TASK_INFO, runningTaskInfo2);
+        when(mPackageManager.loadItemIcon(runningTaskInfo2.topActivityInfo,
+                runningTaskInfo2.topActivityInfo.applicationInfo)).thenReturn(FAKE_DRAWABLE);
+        mockBacklinksTaskForMainLauncherIntent();
 
         // Set up complete, trigger the backlinks action.
         mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY);
@@ -497,22 +466,6 @@
         assertThat(actualBacklinks.get(1)).isInstanceOf(CrossProfileError.class);
     }
 
-    private void resetPackageManagerMockingForUsingFallbackBacklinks() {
-        ResolveInfo backlinksTaskResolveInfo = createBacklinksTaskResolveInfo();
-        reset(mPackageManager);
-        when(mPackageManager.loadItemIcon(any(), any())).thenReturn(FAKE_DRAWABLE);
-        when(mPackageManager.resolveActivity(any(Intent.class), anyInt()))
-                // Firstly, the logic queries whether a package has a launcher activity, this should
-                // resolve otherwise the logic filters out the task.
-                .thenReturn(backlinksTaskResolveInfo)
-                // Secondly, the logic builds a fallback main launcher intent, this should also
-                // resolve for the fallback intent to build correctly.
-                .thenReturn(backlinksTaskResolveInfo)
-                // Lastly, logic queries with the backlinks intent, this should not resolve for the
-                // logic to use the fallback intent.
-                .thenReturn(null);
-    }
-
     private void verifyMainLauncherBacklinksIntent() {
         BacklinksData result = (BacklinksData) mViewModel.mSelectedBacklinksLiveData.getValue();
         assertThat(result.getAppIcon()).isEqualTo(FAKE_DRAWABLE);
@@ -540,6 +493,59 @@
         }).when(mAssistContentRequester).requestAssistContent(eq(taskId), any());
     }
 
+    private void mockPackageManagerToResolveUri(Uri uriToResolve, ResolveInfo resolveInfoToReturn) {
+        Intent uriIntent = new Intent(ACTION_VIEW).setData(uriToResolve);
+        mockQueryIntentActivities(uriIntent, resolveInfoToReturn);
+        mockPmToLoadAppIcon(resolveInfoToReturn);
+    }
+
+    private void mockQueryIntentActivities(Intent expectedIntent, ResolveInfo resolveInfoToReturn) {
+        when(mPackageManager.queryIntentActivities(intentEquals(expectedIntent),
+                eq(MATCH_DEFAULT_ONLY)))
+                .thenReturn(List.of(resolveInfoToReturn));
+    }
+
+    private void mockBacklinksTaskForMainLauncherIntent() {
+        mockPmToResolveForMainLauncherIntent(BACKLINKS_TASK_RESOLVE_INFO);
+    }
+
+    private void mockPmToResolveForMainLauncherIntent(ResolveInfo resolveInfo) {
+        Intent intent = new Intent(ACTION_MAIN).addCategory(CATEGORY_LAUNCHER).setPackage(
+                resolveInfo.activityInfo.packageName);
+        when(mPackageManager.resolveActivity(intentEquals(intent), eq(/* flags= */ 0))).thenReturn(
+                resolveInfo);
+        mockPmToLoadAppIcon(resolveInfo);
+    }
+
+    private void mockPmToLoadAppIcon(ResolveInfo resolveInfo) {
+        when(mPackageManager.loadItemIcon(resolveInfo.activityInfo,
+                resolveInfo.activityInfo.applicationInfo)).thenReturn(FAKE_DRAWABLE);
+    }
+
+    private void mockAtmToReturnRunningTaskInfo(RunningTaskInfo... taskInfos)
+            throws RemoteException {
+        when(mAtmService.getTasks(Integer.MAX_VALUE, /* filterOnlyVisibleRecents= */
+                false, /* keepIntentExtras= */ false, DEFAULT_DISPLAY)).thenReturn(
+                List.of(taskInfos));
+    }
+
+    private static Intent intentEquals(Intent intent) {
+        return argThat(new IntentMatcher(intent));
+    }
+
+    private static class IntentMatcher implements ArgumentMatcher<Intent> {
+        private final Intent mExpectedIntent;
+
+        IntentMatcher(Intent expectedIntent) {
+            mExpectedIntent = expectedIntent;
+        }
+
+        @Override
+        public boolean matches(Intent actualIntent) {
+            return actualIntent != null && mExpectedIntent.filterEquals(actualIntent);
+        }
+    }
+
     private static ResolveInfo createBacklinksTaskResolveInfo() {
         ActivityInfo activityInfo = new ActivityInfo();
         activityInfo.applicationInfo = new ApplicationInfo();
@@ -558,7 +564,7 @@
         taskInfo.isRunning = true;
         taskInfo.numActivities = 1;
         taskInfo.topActivity = new ComponentName(BACKLINKS_TASK_PACKAGE_NAME, "backlinksClass");
-        taskInfo.topActivityInfo = createBacklinksTaskResolveInfo().activityInfo;
+        taskInfo.topActivityInfo = BACKLINKS_TASK_RESOLVE_INFO.activityInfo;
         taskInfo.baseIntent = new Intent().setComponent(taskInfo.topActivity);
         taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
         taskInfo.userId = UserHandle.myUserId();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
index 2e2ac3e..a0ecb80 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
@@ -231,7 +231,7 @@
                         "",
                         "",
                         UserInfo.FLAG_MANAGED_PROFILE,
-                        UserManager.USER_TYPE_PROFILE_MANAGED
+                        UserManager.USER_TYPE_PROFILE_MANAGED,
                     )
                 infoProfile.profileGroupId = id
                 listOf(info, infoProfile)
@@ -261,7 +261,7 @@
                         "",
                         "",
                         UserInfo.FLAG_MANAGED_PROFILE or UserInfo.FLAG_QUIET_MODE,
-                        UserManager.USER_TYPE_PROFILE_MANAGED
+                        UserManager.USER_TYPE_PROFILE_MANAGED,
                     )
                 infoProfile.profileGroupId = id
                 listOf(info, infoProfile)
@@ -291,7 +291,7 @@
                         "",
                         "",
                         UserInfo.FLAG_MANAGED_PROFILE,
-                        UserManager.USER_TYPE_PROFILE_MANAGED
+                        UserManager.USER_TYPE_PROFILE_MANAGED,
                     )
                 infoProfile.profileGroupId = id
                 listOf(info, infoProfile)
@@ -423,7 +423,7 @@
                         "",
                         "",
                         UserInfo.FLAG_MANAGED_PROFILE,
-                        UserManager.USER_TYPE_PROFILE_MANAGED
+                        UserManager.USER_TYPE_PROFILE_MANAGED,
                     )
                 infoProfile.profileGroupId = id
                 listOf(info, infoProfile)
@@ -469,6 +469,24 @@
             assertThat(callback.calledOnProfilesChanged).isEqualTo(0)
         }
 
+    @Test
+    fun testisUserSwitching() =
+        testScope.runTest {
+            tracker.initialize(0)
+            val newID = 5
+            val profileID = newID + 10
+
+            val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
+            verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
+            assertThat(tracker.isUserSwitching).isFalse()
+
+            captor.value.onUserSwitching(newID, userSwitchingReply)
+            assertThat(tracker.isUserSwitching).isTrue()
+
+            captor.value.onUserSwitchComplete(newID)
+            assertThat(tracker.isUserSwitching).isFalse()
+        }
+
     private class TestCallback : UserTracker.Callback {
         var calledOnUserChanging = 0
         var calledOnUserChanged = 0
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 e0c4ab7..4bd0c75 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -167,7 +167,7 @@
 import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
-import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
+import com.android.systemui.statusbar.notification.HeadsUpTouchHelper;
 import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
 import com.android.systemui.statusbar.phone.KeyguardBottomAreaViewController;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
index 0217238..905301e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
@@ -117,11 +117,11 @@
                 deviceProvisionedController,
                 notificationShadeWindowController,
                 0,
+                Lazy { nswvc },
                 Lazy { npvc },
                 Lazy { assistManager },
                 Lazy { gutsManager },
             )
-        shadeController.setNotificationShadeWindowViewController(nswvc)
         shadeController.setVisibilityListener(mock())
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
index 0846ced..fc2ad60 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
@@ -244,23 +244,23 @@
     }
 
     @Test
-    fun dualCarrier_disablesCarrierIconsInStatusIcons() {
+    fun dualCarrier_disablesCarrierIconsInStatusIcons_qs() {
         whenever(mShadeCarrierGroupController.isSingleCarrier).thenReturn(false)
 
         makeShadeVisible()
         shadeHeaderController.qsExpandedFraction = 1.0f
 
-        verify(statusIcons).addIgnoredSlots(carrierIconSlots)
+        verify(statusIcons, times(2)).addIgnoredSlots(carrierIconSlots)
     }
 
     @Test
-    fun dualCarrier_enablesCarrierIconsInStatusIcons_qsExpanded() {
+    fun dualCarrier_disablesCarrierIconsInStatusIcons_qqs() {
         whenever(mShadeCarrierGroupController.isSingleCarrier).thenReturn(false)
 
         makeShadeVisible()
         shadeHeaderController.qsExpandedFraction = 0.0f
 
-        verify(statusIcons, times(2)).removeIgnoredSlots(carrierIconSlots)
+        verify(statusIcons, times(2)).addIgnoredSlots(carrierIconSlots)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
index b65a902..a1750cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.shade.domain.interactor
 
+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.compose.animation.scene.ObservableTransitionState
@@ -27,16 +29,18 @@
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Overlays
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.shadeTestUtil
+import com.android.systemui.shade.shared.flag.DualShade
 import com.android.systemui.testKosmos
-import com.google.common.truth.Truth
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -53,7 +57,12 @@
     private val sceneInteractor = kosmos.sceneInteractor
     private val shadeTestUtil = kosmos.shadeTestUtil
 
-    private val underTest = kosmos.shadeInteractorSceneContainerImpl
+    private lateinit var underTest: ShadeInteractorSceneContainerImpl
+
+    @Before
+    fun setUp() {
+        underTest = kosmos.shadeInteractorSceneContainerImpl
+    }
 
     @Test
     fun qsExpansionWhenInSplitShadeAndQsExpanded() =
@@ -80,7 +89,7 @@
             keyguardRepository.setStatusBarState(StatusBarState.SHADE)
 
             // THEN legacy shade expansion is passed through
-            Truth.assertThat(actual).isEqualTo(.3f)
+            assertThat(actual).isEqualTo(.3f)
         }
 
     @Test
@@ -109,7 +118,7 @@
             runCurrent()
 
             // THEN shade expansion is zero
-            Truth.assertThat(actual).isEqualTo(.7f)
+            assertThat(actual).isEqualTo(.7f)
         }
 
     @Test
@@ -134,7 +143,7 @@
             runCurrent()
 
             // THEN QS is not fullscreen
-            Truth.assertThat(actual).isFalse()
+            assertThat(actual).isFalse()
         }
 
     @Test
@@ -152,7 +161,7 @@
             runCurrent()
 
             // THEN QS is not fullscreen
-            Truth.assertThat(actual).isFalse()
+            assertThat(actual).isFalse()
         }
 
     @Test
@@ -171,7 +180,7 @@
             runCurrent()
 
             // THEN QS is not fullscreen
-            Truth.assertThat(actual).isFalse()
+            assertThat(actual).isFalse()
         }
 
     @Test
@@ -189,7 +198,7 @@
             runCurrent()
 
             // THEN QS is fullscreen
-            Truth.assertThat(actual).isTrue()
+            assertThat(actual).isTrue()
         }
 
     @Test
@@ -206,7 +215,7 @@
             sceneInteractor.setTransitionState(transitionState)
 
             // THEN expansion is 1
-            Truth.assertThat(expansionAmount).isEqualTo(1f)
+            assertThat(expansionAmount).isEqualTo(1f)
         }
 
     @Test
@@ -224,7 +233,7 @@
             sceneInteractor.setTransitionState(transitionState)
 
             // THEN expansion is 0
-            Truth.assertThat(expansionAmount).isEqualTo(0f)
+            assertThat(expansionAmount).isEqualTo(0f)
         }
 
     @Test
@@ -251,19 +260,19 @@
             sceneInteractor.setTransitionState(transitionState)
 
             // THEN expansion is 0
-            Truth.assertThat(expansionAmount).isEqualTo(0f)
+            assertThat(expansionAmount).isEqualTo(0f)
 
             // WHEN transition state is partially to the scene
             progress.value = .4f
 
             // THEN expansion matches the progress
-            Truth.assertThat(expansionAmount).isEqualTo(.4f)
+            assertThat(expansionAmount).isEqualTo(.4f)
 
             // WHEN transition completes
             progress.value = 1f
 
             // THEN expansion is 1
-            Truth.assertThat(expansionAmount).isEqualTo(1f)
+            assertThat(expansionAmount).isEqualTo(1f)
         }
 
     @Test
@@ -290,19 +299,19 @@
             sceneInteractor.setTransitionState(transitionState)
 
             // THEN expansion is 1
-            Truth.assertThat(expansionAmount).isEqualTo(1f)
+            assertThat(expansionAmount).isEqualTo(1f)
 
             // WHEN transition state is partially to the scene
             progress.value = .4f
 
             // THEN expansion reflects the progress
-            Truth.assertThat(expansionAmount).isEqualTo(.6f)
+            assertThat(expansionAmount).isEqualTo(.6f)
 
             // WHEN transition completes
             progress.value = 1f
 
             // THEN expansion is 0
-            Truth.assertThat(expansionAmount).isEqualTo(0f)
+            assertThat(expansionAmount).isEqualTo(0f)
         }
 
     fun isQsBypassingShade_goneToQs() =
@@ -326,7 +335,7 @@
             runCurrent()
 
             // THEN qs is bypassing shade
-            Truth.assertThat(actual).isTrue()
+            assertThat(actual).isTrue()
         }
 
     fun isQsBypassingShade_shadeToQs() =
@@ -350,7 +359,7 @@
             runCurrent()
 
             // THEN qs is not bypassing shade
-            Truth.assertThat(actual).isFalse()
+            assertThat(actual).isFalse()
         }
 
     @Test
@@ -376,19 +385,19 @@
             sceneInteractor.setTransitionState(transitionState)
 
             // THEN expansion is 0
-            Truth.assertThat(expansionAmount).isEqualTo(0f)
+            assertThat(expansionAmount).isEqualTo(0f)
 
             // WHEN transition state is partially complete
             progress.value = .4f
 
             // THEN expansion is still 0
-            Truth.assertThat(expansionAmount).isEqualTo(0f)
+            assertThat(expansionAmount).isEqualTo(0f)
 
             // WHEN transition completes
             progress.value = 1f
 
             // THEN expansion is still 0
-            Truth.assertThat(expansionAmount).isEqualTo(0f)
+            assertThat(expansionAmount).isEqualTo(0f)
         }
 
     @Test
@@ -405,7 +414,7 @@
             sceneInteractor.setTransitionState(transitionState)
 
             // THEN interacting is false
-            Truth.assertThat(interacting).isFalse()
+            assertThat(interacting).isFalse()
         }
 
     @Test
@@ -432,19 +441,19 @@
             sceneInteractor.setTransitionState(transitionState)
 
             // THEN interacting is false
-            Truth.assertThat(interacting).isFalse()
+            assertThat(interacting).isFalse()
 
             // WHEN transition state is partially to the scene
             progress.value = .4f
 
             // THEN interacting is false
-            Truth.assertThat(interacting).isFalse()
+            assertThat(interacting).isFalse()
 
             // WHEN transition completes
             progress.value = 1f
 
             // THEN interacting is false
-            Truth.assertThat(interacting).isFalse()
+            assertThat(interacting).isFalse()
         }
 
     @Test
@@ -471,19 +480,19 @@
             sceneInteractor.setTransitionState(transitionState)
 
             // THEN interacting is true
-            Truth.assertThat(interacting).isTrue()
+            assertThat(interacting).isTrue()
 
             // WHEN transition state is partially to the scene
             progress.value = .4f
 
             // THEN interacting is true
-            Truth.assertThat(interacting).isTrue()
+            assertThat(interacting).isTrue()
 
             // WHEN transition completes
             progress.value = 1f
 
             // THEN interacting is true
-            Truth.assertThat(interacting).isTrue()
+            assertThat(interacting).isTrue()
         }
 
     @Test
@@ -510,19 +519,19 @@
             sceneInteractor.setTransitionState(transitionState)
 
             // THEN interacting is false
-            Truth.assertThat(interacting).isFalse()
+            assertThat(interacting).isFalse()
 
             // WHEN transition state is partially to the scene
             progress.value = .4f
 
             // THEN interacting is false
-            Truth.assertThat(interacting).isFalse()
+            assertThat(interacting).isFalse()
 
             // WHEN transition completes
             progress.value = 1f
 
             // THEN interacting is false
-            Truth.assertThat(interacting).isFalse()
+            assertThat(interacting).isFalse()
         }
 
     @Test
@@ -549,19 +558,19 @@
             sceneInteractor.setTransitionState(transitionState)
 
             // THEN interacting is true
-            Truth.assertThat(interacting).isTrue()
+            assertThat(interacting).isTrue()
 
             // WHEN transition state is partially to the scene
             progress.value = .4f
 
             // THEN interacting is true
-            Truth.assertThat(interacting).isTrue()
+            assertThat(interacting).isTrue()
 
             // WHEN transition completes
             progress.value = 1f
 
             // THEN interacting is true
-            Truth.assertThat(interacting).isTrue()
+            assertThat(interacting).isTrue()
         }
 
     @Test
@@ -572,7 +581,6 @@
             val interacting by collectLastValue(interactingFlow)
 
             // WHEN transition state is starting to between different scenes
-            val progress = MutableStateFlow(0f)
             val transitionState =
                 MutableStateFlow<ObservableTransitionState>(
                     ObservableTransitionState.Transition(
@@ -589,4 +597,94 @@
             // THEN interacting is false
             assertThat(interacting).isFalse()
         }
+
+    @Test
+    @EnableFlags(DualShade.FLAG_NAME)
+    fun expandNotificationShade_dualShadeEnabled_opensOverlay() =
+        testScope.runTest {
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+            assertThat(currentOverlays).isEmpty()
+
+            underTest.expandNotificationShade("reason")
+
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+            assertThat(currentOverlays).containsExactly(Overlays.NotificationsShade)
+        }
+
+    @Test
+    @DisableFlags(DualShade.FLAG_NAME)
+    fun expandNotificationShade_dualShadeDisabled_switchesToShadeScene() =
+        testScope.runTest {
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+            assertThat(currentOverlays).isEmpty()
+
+            underTest.expandNotificationShade("reason")
+
+            assertThat(currentScene).isEqualTo(Scenes.Shade)
+            assertThat(currentOverlays).isEmpty()
+        }
+
+    @Test
+    @EnableFlags(DualShade.FLAG_NAME)
+    fun expandNotificationShade_dualShadeEnabledAndQuickSettingsOpen_replacesOverlay() =
+        testScope.runTest {
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+            underTest.expandQuickSettingsShade("reason")
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+            assertThat(currentOverlays).containsExactly(Overlays.QuickSettingsShade)
+
+            underTest.expandNotificationShade("reason")
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+            assertThat(currentOverlays).containsExactly(Overlays.NotificationsShade)
+        }
+
+    @Test
+    @EnableFlags(DualShade.FLAG_NAME)
+    fun expandQuickSettingsShade_dualShadeEnabled_opensOverlay() =
+        testScope.runTest {
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+            assertThat(currentOverlays).isEmpty()
+
+            underTest.expandQuickSettingsShade("reason")
+
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+            assertThat(currentOverlays).containsExactly(Overlays.QuickSettingsShade)
+        }
+
+    @Test
+    @DisableFlags(DualShade.FLAG_NAME)
+    fun expandQuickSettingsShade_dualShadeDisabled_switchesToQuickSettingsScene() =
+        testScope.runTest {
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+            assertThat(currentOverlays).isEmpty()
+
+            underTest.expandQuickSettingsShade("reason")
+
+            assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
+            assertThat(currentOverlays).isEmpty()
+        }
+
+    @Test
+    @EnableFlags(DualShade.FLAG_NAME)
+    fun expandQuickSettingsShade_dualShadeEnabledAndNotificationsOpen_replacesOverlay() =
+        testScope.runTest {
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+            underTest.expandNotificationShade("reason")
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+            assertThat(currentOverlays).containsExactly(Overlays.NotificationsShade)
+
+            underTest.expandQuickSettingsShade("reason")
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+            assertThat(currentOverlays).containsExactly(Overlays.QuickSettingsShade)
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
index b4f4138..76bb8de 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
@@ -43,7 +43,7 @@
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper.FullScreenIntentDecisionImpl
 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider
 import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback
-import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
+import com.android.systemui.statusbar.notification.HeadsUpManagerPhone
 import com.android.systemui.statusbar.phone.NotificationGroupTestHelper
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
 import com.android.systemui.util.concurrency.FakeExecutor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 3df4a67..30556be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -103,7 +103,7 @@
 import com.android.systemui.statusbar.notification.stack.NotificationSwipeHelper.NotificationCallback;
 import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder;
 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
-import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
+import com.android.systemui.statusbar.notification.HeadsUpTouchHelper;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index c7c08a9..af04309 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -467,13 +467,12 @@
                     mDeviceProvisionedController,
                     mNotificationShadeWindowController,
                     0,
+                    () -> mNotificationShadeWindowViewController,
                     () -> mNotificationPanelViewController,
                     () -> mAssistManager,
                     () -> mNotificationGutsManager
             ));
         }
-        mShadeController.setNotificationShadeWindowViewController(
-                mNotificationShadeWindowViewController);
         mShadeController.setNotificationPresenter(mNotificationPresenter);
 
         when(mOperatorNameViewControllerFactory.create(any()))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
index 93071bd..2588f1f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
@@ -14,10 +14,6 @@
 
 package com.android.systemui.statusbar.policy;
 
-import static android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf;
-
-import static com.android.settingslib.flags.Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE;
-
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
@@ -25,7 +21,6 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
@@ -35,11 +30,7 @@
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.FlagsParameterization;
+import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 
@@ -65,31 +56,16 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.Executor;
 
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
-import platform.test.runner.parameterized.Parameters;
-
-@RunWith(ParameterizedAndroidJunit4.class)
+@RunWith(AndroidTestingRunner.class)
 @RunWithLooper
 @SmallTest
 public class BluetoothControllerImplTest extends SysuiTestCase {
 
-    @Parameters(name = "{0}")
-    public static List<FlagsParameterization> getParams() {
-        return allCombinationsOf(FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE);
-    }
-
-    private static final String TEST_EXCLUSIVE_MANAGER = "com.test.manager";
-
-    @Mock
-    private PackageManager mPackageManager;
-
     private UserTracker mUserTracker;
     private LocalBluetoothManager mMockBluetoothManager;
     private CachedBluetoothDeviceManager mMockDeviceManager;
@@ -102,21 +78,14 @@
 
     private FakeExecutor mBackgroundExecutor;
 
-    public BluetoothControllerImplTest(FlagsParameterization flags) {
-        super();
-        mSetFlagsRule.setFlagsParameterization(flags);
-    }
-
     @Before
     public void setup() throws Exception {
-        MockitoAnnotations.initMocks(this);
         mTestableLooper = TestableLooper.get(this);
         mMockBluetoothManager = mDependency.injectMockDependency(LocalBluetoothManager.class);
         mDevices = new ArrayList<>();
         mUserTracker = mock(UserTracker.class);
         mMockDeviceManager = mock(CachedBluetoothDeviceManager.class);
         mMockAdapter = mock(BluetoothAdapter.class);
-        mContext.setMockPackageManager(mPackageManager);
         when(mMockDeviceManager.getCachedDevicesCopy()).thenReturn(mDevices);
         when(mMockBluetoothManager.getCachedDeviceManager()).thenReturn(mMockDeviceManager);
         mMockLocalAdapter = mock(LocalBluetoothAdapter.class);
@@ -146,7 +115,6 @@
         CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
         when(device.isConnected()).thenReturn(true);
         when(device.getMaxConnectionState()).thenReturn(BluetoothProfile.STATE_CONNECTED);
-        when(device.getDevice()).thenReturn(mock(BluetoothDevice.class));
 
         mDevices.add(device);
         when(mMockLocalAdapter.getConnectionState())
@@ -172,12 +140,10 @@
     public void getConnectedDevices_onlyReturnsConnected() {
         CachedBluetoothDevice device1Disconnected = mock(CachedBluetoothDevice.class);
         when(device1Disconnected.isConnected()).thenReturn(false);
-        when(device1Disconnected.getDevice()).thenReturn(mock(BluetoothDevice.class));
         mDevices.add(device1Disconnected);
 
         CachedBluetoothDevice device2Connected = mock(CachedBluetoothDevice.class);
         when(device2Connected.isConnected()).thenReturn(true);
-        when(device2Connected.getDevice()).thenReturn(mock(BluetoothDevice.class));
         mDevices.add(device2Connected);
 
         mBluetoothControllerImpl.onDeviceAdded(device1Disconnected);
@@ -189,46 +155,6 @@
     }
 
     @Test
-    @EnableFlags(FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
-    public void getConnectedDevice_exclusivelyManagedDevice_doNotReturn()
-            throws PackageManager.NameNotFoundException {
-        CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class);
-        when(cachedDevice.isConnected()).thenReturn(true);
-        BluetoothDevice device = mock(BluetoothDevice.class);
-        when(device.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
-                TEST_EXCLUSIVE_MANAGER.getBytes());
-        when(cachedDevice.getDevice()).thenReturn(device);
-        doReturn(new ApplicationInfo()).when(mPackageManager).getApplicationInfo(
-                TEST_EXCLUSIVE_MANAGER, 0);
-
-        mDevices.add(cachedDevice);
-        mBluetoothControllerImpl.onDeviceAdded(cachedDevice);
-
-        assertThat(mBluetoothControllerImpl.getConnectedDevices()).isEmpty();
-    }
-
-    @Test
-    @DisableFlags(FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
-    public void getConnectedDevice_exclusivelyManagedDevice_returnsConnected()
-            throws PackageManager.NameNotFoundException {
-        CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class);
-        when(cachedDevice.isConnected()).thenReturn(true);
-        BluetoothDevice device = mock(BluetoothDevice.class);
-        when(device.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
-                TEST_EXCLUSIVE_MANAGER.getBytes());
-        when(cachedDevice.getDevice()).thenReturn(device);
-        doReturn(new ApplicationInfo()).when(mPackageManager).getApplicationInfo(
-                TEST_EXCLUSIVE_MANAGER, 0);
-
-        mDevices.add(cachedDevice);
-        mBluetoothControllerImpl.onDeviceAdded(cachedDevice);
-
-        assertThat(mBluetoothControllerImpl.getConnectedDevices()).hasSize(1);
-        assertThat(mBluetoothControllerImpl.getConnectedDevices().get(0))
-                .isEqualTo(cachedDevice);
-    }
-
-    @Test
     public void testOnBluetoothStateChange_updatesBluetoothState() {
         mBluetoothControllerImpl.onBluetoothStateChanged(BluetoothAdapter.STATE_OFF);
 
@@ -259,7 +185,6 @@
 
         assertFalse(mBluetoothControllerImpl.isBluetoothConnected());
         CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
-        when(device.getDevice()).thenReturn(mock(BluetoothDevice.class));
         mDevices.add(device);
         when(device.isConnected()).thenReturn(true);
         when(device.getMaxConnectionState()).thenReturn(BluetoothProfile.STATE_CONNECTED);
@@ -478,7 +403,6 @@
     private CachedBluetoothDevice createBluetoothDevice(
             int profile, boolean isConnected, boolean isActive) {
         CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
-        when(device.getDevice()).thenReturn(mock(BluetoothDevice.class));
         mDevices.add(device);
         when(device.isActiveDevice(profile)).thenReturn(isActive);
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
index d920c4f..574bbcd 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
@@ -17,14 +17,12 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
-import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
 import com.android.systemui.keyguard.data.repository.keyguardRepository
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.domain.resolver.notifShadeSceneFamilyResolver
-import com.android.systemui.scene.domain.resolver.quickSettingsSceneFamilyResolver
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 
@@ -37,12 +35,9 @@
             dismissInteractor = keyguardDismissInteractor,
             applicationScope = testScope.backgroundScope,
             sceneInteractor = { sceneInteractor },
-            deviceEntryInteractor = { deviceEntryInteractor },
-            quickSettingsSceneFamilyResolver = { quickSettingsSceneFamilyResolver },
-            notifShadeSceneFamilyResolver = { notifShadeSceneFamilyResolver },
+            deviceUnlockedInteractor = { deviceUnlockedInteractor },
             powerInteractor = powerInteractor,
             alternateBouncerInteractor = alternateBouncerInteractor,
-            keyguardInteractor = { keyguardInteractor },
             shadeInteractor = { shadeInteractor },
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelKosmos.kt
index 8fc40e4..6ced8c3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelKosmos.kt
@@ -18,8 +18,12 @@
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.fakeQsSceneAdapter: FakeQSSceneAdapter by Fixture { FakeQSSceneAdapter({ mock() }) }
 
 val Kosmos.quickSettingsShadeOverlayActionsViewModel:
     QuickSettingsShadeOverlayActionsViewModel by Fixture {
-    QuickSettingsShadeOverlayActionsViewModel()
+    QuickSettingsShadeOverlayActionsViewModel(quickSettingsContainerViewModel)
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/resolver/SceneResolverKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/resolver/SceneResolverKosmos.kt
index d17b575..8b12425 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/resolver/SceneResolverKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/resolver/SceneResolverKosmos.kt
@@ -24,16 +24,10 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.scene.shared.model.SceneFamilies
-import com.android.systemui.shade.domain.interactor.shadeModeInteractor
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 
 val Kosmos.sceneFamilyResolvers: Map<SceneKey, SceneResolver>
-    get() =
-        mapOf(
-            SceneFamilies.Home to homeSceneFamilyResolver,
-            SceneFamilies.NotifShade to notifShadeSceneFamilyResolver,
-            SceneFamilies.QuickSettings to quickSettingsSceneFamilyResolver,
-        )
+    get() = mapOf(SceneFamilies.Home to homeSceneFamilyResolver)
 
 val Kosmos.homeSceneFamilyResolver by
     Kosmos.Fixture {
@@ -43,19 +37,3 @@
             keyguardEnabledInteractor = keyguardEnabledInteractor,
         )
     }
-
-val Kosmos.notifShadeSceneFamilyResolver by
-    Kosmos.Fixture {
-        NotifShadeSceneFamilyResolver(
-            applicationScope = applicationCoroutineScope,
-            shadeModeInteractor = shadeModeInteractor,
-        )
-    }
-
-val Kosmos.quickSettingsSceneFamilyResolver by
-    Kosmos.Fixture {
-        QuickSettingsSceneFamilyResolver(
-            applicationScope = applicationCoroutineScope,
-            shadeModeInteractor = shadeModeInteractor,
-        )
-    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
index f3d5b7d..cd76a74 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
@@ -33,6 +33,7 @@
     private var _userHandle: UserHandle = UserHandle.of(_userId),
     private var _userInfo: UserInfo = mock(),
     private var _userProfiles: List<UserInfo> = emptyList(),
+    private var _isUserSwitching: Boolean = false,
     userContentResolverProvider: () -> ContentResolver = { MockContentResolver() },
     userContext: Context = mock(),
     private val onCreateCurrentUserContext: (Context) -> Context = { mock() },
@@ -51,6 +52,9 @@
     override val userProfiles: List<UserInfo>
         get() = _userProfiles
 
+    override val isUserSwitching: Boolean
+        get() = _isUserSwitching
+
     // userContentResolver is lazy because Ravenwood doesn't support MockContentResolver()
     // and we still want to allow people use this class for tests that don't use it.
     override val userContentResolver: ContentResolver by lazy { userContentResolverProvider() }
@@ -86,11 +90,13 @@
     }
 
     fun onUserChanging(userId: Int = _userId) {
+        _isUserSwitching = true
         val copy = callbacks.toList()
         copy.forEach { it.onUserChanging(userId, userContext) {} }
     }
 
     fun onUserChanged(userId: Int = _userId) {
+        _isUserSwitching = false
         val copy = callbacks.toList()
         copy.forEach { it.onUserChanged(userId, userContext) }
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
index 0bc4d54..ddcc6d6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
@@ -71,6 +71,7 @@
             deviceProvisionedController,
             mock<NotificationShadeWindowController>(),
             0,
+            { mock<NotificationShadeWindowViewController>() },
             { mock<NotificationPanelViewController>() },
             { mock<AssistManager>() },
             { mock<NotificationGutsManager>() },
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 04d930c..92075ea 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,
-            shadeRepository = shadeRepository,
+            shadeModeInteractor = shadeModeInteractor,
         )
     }
 val Kosmos.shadeInteractorLegacyImpl by
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/TestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/TestUtils.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/TestUtils.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/util/TestUtils.kt
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt
index 0458f53..b1e6fe2 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt
@@ -103,7 +103,7 @@
 
                 if (displayId == display.displayId) {
                     val currentRotation = display.rotation
-                    if (lastRotation.compareAndSet(lastRotation.get(), currentRotation)) {
+                    if (lastRotation.getAndSet(currentRotation) != currentRotation) {
                         listeners.forEach { it.onRotationChanged(currentRotation) }
                     }
                 }
diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
index 136738f..acb74d3 100644
--- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
+++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
@@ -1620,7 +1620,7 @@
                 }
                 ret.outputConfigs.add(entry);
             }
-            if (Flags.extension10Bit() && EFV_SUPPORTED) {
+            if (EFV_SUPPORTED) {
                 ret.colorSpace = sessionConfig.getColorSpace();
             } else {
                 ret.colorSpace = ColorSpaceProfiles.UNSPECIFIED;
@@ -2596,7 +2596,7 @@
 
     private static CameraOutputConfig getCameraOutputConfig(Camera2OutputConfigImpl output) {
         CameraOutputConfig ret = new CameraOutputConfig();
-        if (Flags.extension10Bit() && EFV_SUPPORTED) {
+        if (EFV_SUPPORTED) {
             ret.dynamicRangeProfile = output.getDynamicRangeProfile();
         } else {
             ret.dynamicRangeProfile = DynamicRangeProfiles.STANDARD;
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index 3c65f37..d1a3bf9 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -5,6 +5,10 @@
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
     default_applicable_licenses: ["frameworks_base_license"],
+
+    // OWNER: g/ravenwood
+    // Bug component: 25698
+    default_team: "trendy_team_framework_backstage_power",
 }
 
 filegroup {
@@ -337,8 +341,8 @@
 android_ravenwood_libgroup {
     name: "ravenwood-runtime",
     data: [
-        "framework-res",
-        "ravenwood-empty-res",
+        ":framework-res",
+        ":ravenwood-empty-res",
     ],
     libs: [
         "100-framework-minus-apex.ravenwood",
diff --git a/ravenwood/OWNERS b/ravenwood/OWNERS
index f7b13d1..8722ad9 100644
--- a/ravenwood/OWNERS
+++ b/ravenwood/OWNERS
@@ -1,5 +1,7 @@
+# Bug component: 25698
 set noparent
 
+omakoto@google.com
 topjohnwu@google.com
 hackbod@google.com  #{LAST_RESORT_SUGGESTION}
 
diff --git a/ravenwood/TEST_MAPPING b/ravenwood/TEST_MAPPING
index 7f9d9c2..3583b96 100644
--- a/ravenwood/TEST_MAPPING
+++ b/ravenwood/TEST_MAPPING
@@ -7,30 +7,17 @@
     { "name": "RavenwoodMockitoTest_device" },
     { "name": "RavenwoodBivalentTest_device" },
 
+    { "name": "RavenwoodBivalentInstTest_nonself_inst" },
+    { "name": "RavenwoodBivalentInstTest_self_inst_device" },
+
     // The sysui tests should match vendor/unbundled_google/packages/SystemUIGoogle/TEST_MAPPING
     {
-      "name": "SystemUIGoogleTests",
-      "options": [
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "SystemUIGoogleTests"
     }
   ],
   "presubmit-large": [
     {
-      "name": "SystemUITests",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        }
-      ]
+      "name": "SystemUITests"
     }
   ],
   "ravenwood-presubmit": [
@@ -138,6 +125,14 @@
       "host": true
     },
     {
+      "name": "RavenwoodBivalentInstTest_nonself_inst",
+      "host": true
+    },
+    {
+      "name": "RavenwoodBivalentInstTest_self_inst",
+      "host": true
+    },
+    {
       "name": "RavenwoodBivalentTest",
       "host": true
     },
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
index 68472c1..7a6f9e3 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
@@ -108,7 +108,7 @@
         Log.v(TAG, "onBeforeInnerRunnerStart: description=" + description);
 
         // Prepare the environment before the inner runner starts.
-        RavenwoodRunnerState.forRunner(runner).enterTestClass(description);
+        runner.mState.enterTestClass(description);
     }
 
     /**
@@ -119,7 +119,7 @@
         Log.v(TAG, "onAfterInnerRunnerFinished: description=" + description);
 
         RavenwoodTestStats.getInstance().onClassFinished(description);
-        RavenwoodRunnerState.forRunner(runner).exitTestClass();
+        runner.mState.exitTestClass();
     }
 
     /**
@@ -133,10 +133,10 @@
 
         if (scope == Scope.Instance && order == Order.Outer) {
             // Start of a test method.
-            RavenwoodRunnerState.forRunner(runner).enterTestMethod(description);
+            runner.mState.enterTestMethod(description);
         }
 
-        final var classDescription = RavenwoodRunnerState.forRunner(runner).getClassDescription();
+        final var classDescription = runner.mState.getClassDescription();
 
         // Class-level annotations are checked by the runner already, so we only check
         // method-level annotations here.
@@ -160,11 +160,11 @@
             Scope scope, Order order, Throwable th) {
         Log.v(TAG, "onAfter: description=" + description + ", " + scope + ", " + order + ", " + th);
 
-        final var classDescription = RavenwoodRunnerState.forRunner(runner).getClassDescription();
+        final var classDescription = runner.mState.getClassDescription();
 
         if (scope == Scope.Instance && order == Order.Outer) {
             // End of a test method.
-            RavenwoodRunnerState.forRunner(runner).exitTestMethod();
+            runner.mState.exitTestMethod();
             RavenwoodTestStats.getInstance().onTestFinished(classDescription, description,
                     th == null ? Result.Passed : Result.Failed);
         }
@@ -214,7 +214,7 @@
             Description description, RavenwoodRule rule) throws Throwable {
         Log.v(TAG, "onRavenwoodRuleEnter: description=" + description);
 
-        RavenwoodRunnerState.forRunner(runner).enterRavenwoodRule(rule);
+        runner.mState.enterRavenwoodRule(rule);
     }
 
 
@@ -225,6 +225,6 @@
             Description description, RavenwoodRule rule) throws Throwable {
         Log.v(TAG, "onRavenwoodRuleExit: description=" + description);
 
-        RavenwoodRunnerState.forRunner(runner).exitRavenwoodRule(rule);
+        runner.mState.exitRavenwoodRule(rule);
     }
 }
\ No newline at end of file
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodConfigState.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodConfigState.java
new file mode 100644
index 0000000..3535cb2
--- /dev/null
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodConfigState.java
@@ -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 android.platform.test.ravenwood;
+
+import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_EMPTY_RESOURCES_APK;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.annotation.Nullable;
+import android.app.ResourcesManager;
+import android.content.res.Resources;
+import android.view.DisplayAdjustments;
+
+import java.io.File;
+import java.util.HashMap;
+
+/**
+ * Used to store various states associated with {@link RavenwoodConfig} that's inly needed
+ * in junit-impl.
+ *
+ * We don't want to put it in junit-src to avoid having to recompile all the downstream
+ * dependencies after changing this class.
+ *
+ * All members must be called from the runner's main thread.
+ */
+public class RavenwoodConfigState {
+    private static final String TAG = "RavenwoodConfigState";
+
+    private final RavenwoodConfig mConfig;
+
+    public RavenwoodConfigState(RavenwoodConfig config) {
+        mConfig = config;
+    }
+
+    /** Map from path -> resources. */
+    private final HashMap<File, Resources> mCachedResources = new HashMap<>();
+
+    /**
+     * Load {@link Resources} from an APK, with cache.
+     */
+    public Resources loadResources(@Nullable File apkPath) {
+        var cached = mCachedResources.get(apkPath);
+        if (cached != null) {
+            return cached;
+        }
+
+        var fileToLoad = apkPath != null ? apkPath : new File(RAVENWOOD_EMPTY_RESOURCES_APK);
+
+        assertTrue("File " + fileToLoad + " doesn't exist.", fileToLoad.isFile());
+
+        final String path = fileToLoad.getAbsolutePath();
+        final var emptyPaths = new String[0];
+
+        ResourcesManager.getInstance().initializeApplicationPaths(path, emptyPaths);
+
+        final var ret = ResourcesManager.getInstance().getResources(null, path,
+                emptyPaths, emptyPaths, emptyPaths,
+                emptyPaths, null, null,
+                new DisplayAdjustments().getCompatibilityInfo(),
+                RavenwoodRuntimeEnvironmentController.class.getClassLoader(), null);
+
+        assertNotNull(ret);
+
+        mCachedResources.put(apkPath, ret);
+        return ret;
+    }
+}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java
index 48bed79..239c806 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java
@@ -60,6 +60,8 @@
     private final File mCacheDir;
     private final Supplier<Resources> mResourcesSupplier;
 
+    private RavenwoodContext mAppContext;
+
     @GuardedBy("mLock")
     private Resources mResources;
 
@@ -77,8 +79,8 @@
         mPackageName = packageName;
         mMainThread = mainThread;
         mResourcesSupplier = resourcesSupplier;
-        mFilesDir = createTempDir("files-dir");
-        mCacheDir = createTempDir("cache-dir");
+        mFilesDir = createTempDir(packageName + "_files-dir");
+        mCacheDir = createTempDir(packageName + "_cache-dir");
 
         // Services provided by a typical shipping device
         registerService(ClipboardManager.class,
@@ -131,34 +133,35 @@
     @Override
     public Looper getMainLooper() {
         Objects.requireNonNull(mMainThread,
-                "Test must request setProvideMainThread() via RavenwoodRule");
+                "Test must request setProvideMainThread() via RavenwoodConfig");
         return mMainThread.getLooper();
     }
 
     @Override
     public Handler getMainThreadHandler() {
         Objects.requireNonNull(mMainThread,
-                "Test must request setProvideMainThread() via RavenwoodRule");
+                "Test must request setProvideMainThread() via RavenwoodConfig");
         return mMainThread.getThreadHandler();
     }
 
     @Override
     public Executor getMainExecutor() {
         Objects.requireNonNull(mMainThread,
-                "Test must request setProvideMainThread() via RavenwoodRule");
+                "Test must request setProvideMainThread() via RavenwoodConfig");
         return mMainThread.getThreadExecutor();
     }
 
     @Override
     public String getPackageName() {
         return Objects.requireNonNull(mPackageName,
-                "Test must request setPackageName() via RavenwoodRule");
+                "Test must request setPackageName() (or setTargetPackageName())"
+                + " via RavenwoodConfig");
     }
 
     @Override
     public String getOpPackageName() {
         return Objects.requireNonNull(mPackageName,
-                "Test must request setPackageName() via RavenwoodRule");
+                "Test must request setPackageName() via RavenwoodConfig");
     }
 
     @Override
@@ -227,6 +230,15 @@
         return new File(RAVENWOOD_RESOURCE_APK).getAbsolutePath();
     }
 
+    public void setApplicationContext(RavenwoodContext appContext) {
+        mAppContext = appContext;
+    }
+
+    @Override
+    public Context getApplicationContext() {
+        return mAppContext;
+    }
+
     /**
      * Wrap the given {@link Supplier} to become memoized.
      *
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java
index d73afd4..04b67c4 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java
@@ -19,7 +19,6 @@
 
 import static org.junit.Assert.fail;
 
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 
 import com.android.internal.annotations.GuardedBy;
@@ -35,12 +34,11 @@
 import java.util.WeakHashMap;
 
 /**
- * Used to store various states associated with the current test runner.
+ * Used to store various states associated with the current test runner that's inly needed
+ * in junit-impl.
  *
- * This class could be added to {@link RavenwoodAwareTestRunner} as a field, but we don't
- * want to put it in junit-src/ (for one, that'll cause all the downstream dependencies to be
- * rebuilt when this file is updated), so we manage it separately using a Map from each
- * {@link RavenwoodAwareTestRunner} instance to a {@link RavenwoodRunnerState}.
+ * We don't want to put it in junit-src to avoid having to recompile all the downstream
+ * dependencies after changing this class.
  *
  * All members must be called from the runner's main thread.
  */
@@ -51,23 +49,12 @@
     private static final WeakHashMap<RavenwoodAwareTestRunner, RavenwoodRunnerState> sStates =
             new WeakHashMap<>();
 
-    /**
-     * Get the instance for a given runner.
-     */
-    public static RavenwoodRunnerState forRunner(@NonNull RavenwoodAwareTestRunner runner) {
-        synchronized (sStates) {
-            var ret = sStates.get(runner);
-            if (ret == null) {
-                ret = new RavenwoodRunnerState(runner);
-                sStates.put(runner, ret);
-            }
-            return ret;
-        }
-    }
-
     private final RavenwoodAwareTestRunner mRunner;
 
-    private RavenwoodRunnerState(RavenwoodAwareTestRunner runner) {
+    /**
+     * Ctor.
+     */
+    public RavenwoodRunnerState(RavenwoodAwareTestRunner runner) {
         mRunner = runner;
     }
 
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
index 03c9001..f4756c5 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
@@ -16,12 +16,10 @@
 
 package android.platform.test.ravenwood;
 
-import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_EMPTY_RESOURCES_APK;
 import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_RESOURCE_APK;
 import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING;
 
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import android.app.ActivityManager;
 import android.app.Instrumentation;
@@ -34,7 +32,6 @@
 import android.os.Looper;
 import android.os.ServiceManager;
 import android.util.Log;
-import android.view.DisplayAdjustments;
 
 import androidx.test.platform.app.InstrumentationRegistry;
 
@@ -163,31 +160,52 @@
             main = null;
         }
 
-        // TODO This should be integrated into LoadedApk
-        final Supplier<Resources> resourcesSupplier = () -> {
-            var resApkFile = new File(RAVENWOOD_RESOURCE_APK);
-            if (!resApkFile.isFile()) {
-                resApkFile = new File(RAVENWOOD_EMPTY_RESOURCES_APK);
-            }
-            assertTrue(resApkFile.isFile());
-            final String res = resApkFile.getAbsolutePath();
-            final var emptyPaths = new String[0];
+        final boolean isSelfInstrumenting =
+                Objects.equals(config.mTestPackageName, config.mTargetPackageName);
 
-            ResourcesManager.getInstance().initializeApplicationPaths(res, emptyPaths);
-
-            final var ret = ResourcesManager.getInstance().getResources(null, res,
-                    emptyPaths, emptyPaths, emptyPaths,
-                    emptyPaths, null, null,
-                    new DisplayAdjustments().getCompatibilityInfo(),
-                    RavenwoodRuntimeEnvironmentController.class.getClassLoader(), null);
-
-            assertNotNull(ret);
-            return ret;
+        // This will load the resources from the apk set to `resource_apk` in the build file.
+        // This is supposed to be the "target app"'s resources.
+        final Supplier<Resources> targetResourcesLoader = () -> {
+            var file = new File(RAVENWOOD_RESOURCE_APK);
+            return config.mState.loadResources(file.exists() ? file : null);
         };
+        // Set up test context's resources.
+        // If the target package name == test package name, then we use the main resources.
+        // Otherwise, we don't simulate loading resources from the test APK yet.
+        // (we need to add `test_resource_apk` to `android_ravenwood_test`)
+        final Supplier<Resources> testResourcesLoader;
+        if (isSelfInstrumenting) {
+            testResourcesLoader = targetResourcesLoader;
+        } else {
+            testResourcesLoader = () -> {
+                fail("Cannot load resources from the test context (yet)."
+                        + " Use target context's resources instead.");
+                return null; // unreachable.
+            };
+        }
 
-        config.mContext = new RavenwoodContext(config.mPackageName, main, resourcesSupplier);
+        var testContext = new RavenwoodContext(
+                config.mTestPackageName, main, testResourcesLoader);
+        var targetContext = new RavenwoodContext(
+                config.mTargetPackageName, main, targetResourcesLoader);
+
+        // Set up app context.
+        var appContext = new RavenwoodContext(
+                config.mTargetPackageName, main, targetResourcesLoader);
+        appContext.setApplicationContext(appContext);
+        if (isSelfInstrumenting) {
+            testContext.setApplicationContext(appContext);
+            targetContext.setApplicationContext(appContext);
+        } else {
+            // When instrumenting into another APK, the test context doesn't have an app context.
+            targetContext.setApplicationContext(appContext);
+        }
+        config.mTestContext = testContext;
+        config.mTargetContext = targetContext;
+
+        // Prepare other fields.
         config.mInstrumentation = new Instrumentation();
-        config.mInstrumentation.basicInit(config.mContext);
+        config.mInstrumentation.basicInit(config.mTestContext, config.mTargetContext);
         InstrumentationRegistry.registerInstance(config.mInstrumentation, Bundle.EMPTY);
 
         RavenwoodSystemServer.init(config);
@@ -224,10 +242,14 @@
 
         InstrumentationRegistry.registerInstance(null, Bundle.EMPTY);
         config.mInstrumentation = null;
-        if (config.mContext != null) {
-            ((RavenwoodContext) config.mContext).cleanUp();
+        if (config.mTestContext != null) {
+            ((RavenwoodContext) config.mTestContext).cleanUp();
         }
-        config.mContext = null;
+        if (config.mTargetContext != null) {
+            ((RavenwoodContext) config.mTargetContext).cleanUp();
+        }
+        config.mTestContext = null;
+        config.mTargetContext = null;
 
         if (config.mProvideMainThread) {
             Looper.getMainLooper().quit();
@@ -240,7 +262,9 @@
         ServiceManager.reset$ravenwood();
 
         setSystemProperties(RavenwoodSystemProperties.DEFAULT_VALUES);
-        Binder.restoreCallingIdentity(sOriginalIdentityToken);
+        if (sOriginalIdentityToken != -1) {
+            Binder.restoreCallingIdentity(sOriginalIdentityToken);
+        }
         android.os.Process.reset$ravenwood();
 
         ResourcesManager.setInstance(null); // Better structure needed.
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java
index f3a93c1..d4090e2 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java
@@ -67,7 +67,7 @@
 
         sStartedServices = new ArraySet<>();
         sTimings = new TimingsTraceAndSlog();
-        sServiceManager = new SystemServiceManager(config.mContext);
+        sServiceManager = new SystemServiceManager(config.mTestContext);
         sServiceManager.setStartInfo(false,
                 SystemClock.elapsedRealtime(),
                 SystemClock.uptimeMillis());
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
index bc944d7..cb8af0c 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
@@ -171,6 +171,12 @@
     private Description mDescription = null;
     private Throwable mExceptionInConstructor = null;
 
+    /**
+     * Stores internal states / methods associated with this runner that's only needed in
+     * junit-impl.
+     */
+    final RavenwoodRunnerState mState = new RavenwoodRunnerState(this);
+
     private Error logAndFail(String message, Throwable exception) {
         Log.e(TAG, message, exception);
         throw new AssertionError(message, exception);
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
index 04e0bed..ea33aa6 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
@@ -63,7 +63,10 @@
     int mUid = NOBODY_UID;
     int mPid = sNextPid.getAndIncrement();
 
-    String mPackageName;
+    String mTestPackageName;
+    String mTargetPackageName;
+
+    int mMinSdkLevel;
 
     boolean mProvideMainThread = false;
 
@@ -71,9 +74,15 @@
 
     final List<Class<?>> mServicesRequired = new ArrayList<>();
 
-    volatile Context mContext;
+    volatile Context mTestContext;
+    volatile Context mTargetContext;
     volatile Instrumentation mInstrumentation;
 
+    /**
+     * Stores internal states / methods associated with this config that's only needed in
+     * junit-impl.
+     */
+    final RavenwoodConfigState mState = new RavenwoodConfigState(this);
     private RavenwoodConfig() {
     }
 
@@ -84,6 +93,12 @@
         return RavenwoodRule.isOnRavenwood();
     }
 
+    private void setDefaults() {
+        if (mTargetPackageName == null) {
+            mTargetPackageName = mTestPackageName;
+        }
+    }
+
     public static class Builder {
         private final RavenwoodConfig mConfig = new RavenwoodConfig();
 
@@ -109,11 +124,28 @@
         }
 
         /**
-         * Configure the identity of this process to be the given package name for the duration
-         * of the test. Has no effect on non-Ravenwood environments.
+         * Configure the package name of the test, which corresponds to
+         * {@link Instrumentation#getContext()}.
          */
         public Builder setPackageName(@NonNull String packageName) {
-            mConfig.mPackageName = Objects.requireNonNull(packageName);
+            mConfig.mTestPackageName = Objects.requireNonNull(packageName);
+            return this;
+        }
+
+        /**
+         * Configure the package name of the target app, which corresponds to
+         * {@link Instrumentation#getTargetContext()}. Defaults to {@link #setPackageName}.
+         */
+        public Builder setTargetPackageName(@NonNull String packageName) {
+            mConfig.mTargetPackageName = Objects.requireNonNull(packageName);
+            return this;
+        }
+
+        /**
+         * Configure the min SDK level of the test.
+         */
+        public Builder setMinSdkLevel(int sdkLevel) {
+            mConfig.mMinSdkLevel = sdkLevel;
             return this;
         }
 
@@ -178,6 +210,7 @@
         }
 
         public RavenwoodConfig build() {
+            mConfig.setDefaults();
             return mConfig;
         }
     }
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index 7847e7c..984106b 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -216,7 +216,7 @@
      */
     @Deprecated
     public Context getContext() {
-        return Objects.requireNonNull(mConfiguration.mContext,
+        return Objects.requireNonNull(mConfiguration.mTestContext,
                 "Context is only available during @Test execution");
     }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToNotificationsShadeTransition.kt b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodConfigState.java
similarity index 69%
rename from packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToNotificationsShadeTransition.kt
rename to ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodConfigState.java
index 48ec198..43a28ba 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToNotificationsShadeTransition.kt
+++ b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodConfigState.java
@@ -13,13 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package android.platform.test.ravenwood;
 
-package com.android.systemui.scene.ui.composable.transitions
-
-import com.android.compose.animation.scene.TransitionBuilder
-
-fun TransitionBuilder.goneToNotificationsShadeTransition(
-    durationScale: Double = 1.0,
-) {
-    toNotificationsShadeTransition(durationScale)
+/** Stub class. The actual implementaetion is in junit-impl-src. */
+public class RavenwoodConfigState {
+    public RavenwoodConfigState(RavenwoodConfig config) {
+    }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToNotificationsShadeTransition.kt b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRunnerState.java
similarity index 69%
copy from packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToNotificationsShadeTransition.kt
copy to ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRunnerState.java
index 48ec198..83cbc52 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToNotificationsShadeTransition.kt
+++ b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRunnerState.java
@@ -13,13 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package android.platform.test.ravenwood;
 
-package com.android.systemui.scene.ui.composable.transitions
-
-import com.android.compose.animation.scene.TransitionBuilder
-
-fun TransitionBuilder.goneToNotificationsShadeTransition(
-    durationScale: Double = 1.0,
-) {
-    toNotificationsShadeTransition(durationScale)
+/** Stub class. The actual implementaetion is in junit-impl-src. */
+public class RavenwoodRunnerState {
+    public RavenwoodRunnerState(RavenwoodAwareTestRunner runner) {
+    }
 }
diff --git a/ravenwood/tests/bivalentinst/Android.bp b/ravenwood/tests/bivalentinst/Android.bp
new file mode 100644
index 0000000..38d1b299
--- /dev/null
+++ b/ravenwood/tests/bivalentinst/Android.bp
@@ -0,0 +1,149 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_ravenwood_test {
+    name: "RavenwoodBivalentInstTest_self_inst",
+
+    srcs: [
+        "test/**/*.java",
+    ],
+    exclude_srcs: [
+        "test/**/*_nonself.java",
+    ],
+
+    static_libs: [
+        "RavenwoodBivalentInstTest_self_inst_device_R",
+
+        "androidx.annotation_annotation",
+        "androidx.test.ext.junit",
+        "androidx.test.rules",
+
+        "junit",
+        "truth",
+    ],
+    // TODO(b/366246777) uncomment it and the test.
+    // resource_apk: "RavenwoodBivalentInstTest_self_inst_device",
+    auto_gen_config: true,
+}
+
+android_ravenwood_test {
+    name: "RavenwoodBivalentInstTest_nonself_inst",
+
+    srcs: [
+        "test/**/*.java",
+    ],
+    exclude_srcs: [
+        "test/**/*_self.java",
+    ],
+
+    static_libs: [
+        "RavenwoodBivalentInstTestTarget_R",
+        "RavenwoodBivalentInstTest_nonself_inst_device_R",
+
+        "androidx.annotation_annotation",
+        "androidx.test.ext.junit",
+        "androidx.test.rules",
+
+        "junit",
+        "truth",
+    ],
+    // TODO(b/366246777) uncomment it and the test.
+    // resource_apk: "RavenwoodBivalentInstTestTarget",
+    auto_gen_config: true,
+}
+
+// We have 3 R.javas from the 3 packages (2 test apks below, and 1 target APK)
+// RavenwoodBivalentInstTest needs to use all of them, but we can't add all the
+// {.aapt.srcjar}'s together because that'd cause
+// "duplicate declaration of androidx.test.core.R$string."
+// So we build them as separate libraries, and include them as static_libs.
+java_library {
+    name: "RavenwoodBivalentInstTestTarget_R",
+    srcs: [
+        ":RavenwoodBivalentInstTestTarget{.aapt.srcjar}",
+    ],
+}
+
+java_library {
+    name: "RavenwoodBivalentInstTest_self_inst_device_R",
+    srcs: [
+        ":RavenwoodBivalentInstTest_self_inst_device{.aapt.srcjar}",
+    ],
+}
+
+java_library {
+    name: "RavenwoodBivalentInstTest_nonself_inst_device_R",
+    srcs: [
+        ":RavenwoodBivalentInstTest_nonself_inst_device{.aapt.srcjar}",
+    ],
+}
+
+android_test {
+    name: "RavenwoodBivalentInstTest_self_inst_device",
+
+    srcs: [
+        "test/**/*.java",
+    ],
+    exclude_srcs: [
+        "test/**/*_nonself.java",
+    ],
+    static_libs: [
+        "junit",
+        "truth",
+
+        "androidx.annotation_annotation",
+        "androidx.test.ext.junit",
+        "androidx.test.rules",
+
+        "ravenwood-junit",
+    ],
+    test_suites: [
+        "device-tests",
+    ],
+    use_resource_processor: false,
+    manifest: "AndroidManifest-self-inst.xml",
+    test_config: "AndroidTest-self-inst.xml",
+    optimize: {
+        enabled: false,
+    },
+}
+
+android_test {
+    name: "RavenwoodBivalentInstTest_nonself_inst_device",
+
+    srcs: [
+        "test/**/*.java",
+    ],
+    exclude_srcs: [
+        "test/**/*_self.java",
+    ],
+    static_libs: [
+        "junit",
+        "truth",
+
+        "androidx.annotation_annotation",
+        "androidx.test.ext.junit",
+        "androidx.test.rules",
+
+        "ravenwood-junit",
+    ],
+    data: [
+        ":RavenwoodBivalentInstTestTarget",
+    ],
+    test_suites: [
+        "device-tests",
+    ],
+    use_resource_processor: false,
+    manifest: "AndroidManifest-nonself-inst.xml",
+    test_config: "AndroidTest-nonself-inst.xml",
+    instrumentation_for: "RavenwoodBivalentInstTestTarget",
+    optimize: {
+        enabled: false,
+    },
+}
diff --git a/ravenwood/tests/bivalentinst/AndroidManifest-nonself-inst.xml b/ravenwood/tests/bivalentinst/AndroidManifest-nonself-inst.xml
new file mode 100644
index 0000000..a5a1f17
--- /dev/null
+++ b/ravenwood/tests/bivalentinst/AndroidManifest-nonself-inst.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.ravenwood.bivalentinsttest_nonself_inst">
+
+    <application android:debuggable="true" >
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.ravenwood.bivalentinst_target_app"
+        />
+</manifest>
diff --git a/ravenwood/tests/bivalentinst/AndroidManifest-self-inst.xml b/ravenwood/tests/bivalentinst/AndroidManifest-self-inst.xml
new file mode 100644
index 0000000..3dc4c56
--- /dev/null
+++ b/ravenwood/tests/bivalentinst/AndroidManifest-self-inst.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.ravenwood.bivalentinsttest_self_inst">
+
+    <application android:debuggable="true" >
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.ravenwood.bivalentinsttest_self_inst"
+        />
+</manifest>
diff --git a/ravenwood/tests/bivalentinst/AndroidTest-nonself-inst.xml b/ravenwood/tests/bivalentinst/AndroidTest-nonself-inst.xml
new file mode 100644
index 0000000..9491c53
--- /dev/null
+++ b/ravenwood/tests/bivalentinst/AndroidTest-nonself-inst.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration>
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-instrumentation" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="RavenwoodBivalentInstTestTarget.apk" />
+        <option name="test-file-name" value="RavenwoodBivalentInstTest_nonself_inst_device.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.ravenwood.bivalentinsttest_nonself_inst" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+    </test>
+</configuration>
diff --git a/ravenwood/tests/bivalentinst/AndroidTest-self-inst.xml b/ravenwood/tests/bivalentinst/AndroidTest-self-inst.xml
new file mode 100644
index 0000000..3079c06
--- /dev/null
+++ b/ravenwood/tests/bivalentinst/AndroidTest-self-inst.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration>
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-instrumentation" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="RavenwoodBivalentInstTest_self_inst_device.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.ravenwood.bivalentinsttest_self_inst" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+    </test>
+</configuration>
diff --git a/ravenwood/tests/bivalentinst/res/values/strings.xml b/ravenwood/tests/bivalentinst/res/values/strings.xml
new file mode 100644
index 0000000..73ef650
--- /dev/null
+++ b/ravenwood/tests/bivalentinst/res/values/strings.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+           xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string translatable="false" name="test_string_in_test">String in test APK</string>
+</resources>
diff --git a/ravenwood/tests/bivalentinst/targetapp/Android.bp b/ravenwood/tests/bivalentinst/targetapp/Android.bp
new file mode 100644
index 0000000..7528a62
--- /dev/null
+++ b/ravenwood/tests/bivalentinst/targetapp/Android.bp
@@ -0,0 +1,20 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_app {
+    name: "RavenwoodBivalentInstTestTarget",
+    srcs: [
+        "src/**/*.java",
+    ],
+    sdk_version: "current",
+    optimize: {
+        enabled: false,
+    },
+    use_resource_processor: false,
+}
diff --git a/ravenwood/tests/bivalentinst/targetapp/AndroidManifest.xml b/ravenwood/tests/bivalentinst/targetapp/AndroidManifest.xml
new file mode 100644
index 0000000..0715f5d
--- /dev/null
+++ b/ravenwood/tests/bivalentinst/targetapp/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.ravenwood.bivalentinst_target_app">
+    <application>
+    </application>
+</manifest>
diff --git a/ravenwood/tests/bivalentinst/targetapp/res/values/strings.xml b/ravenwood/tests/bivalentinst/targetapp/res/values/strings.xml
new file mode 100644
index 0000000..395bc2a
--- /dev/null
+++ b/ravenwood/tests/bivalentinst/targetapp/res/values/strings.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+           xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string translatable="false" name="test_string_in_target">Test string in target APK</string>
+</resources>
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToNotificationsShadeTransition.kt b/ravenwood/tests/bivalentinst/targetapp/src/com/android/ravenwoodtest/bivalentinst/Empty.java
similarity index 69%
copy from packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToNotificationsShadeTransition.kt
copy to ravenwood/tests/bivalentinst/targetapp/src/com/android/ravenwoodtest/bivalentinst/Empty.java
index 48ec198..15e50ec 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToNotificationsShadeTransition.kt
+++ b/ravenwood/tests/bivalentinst/targetapp/src/com/android/ravenwoodtest/bivalentinst/Empty.java
@@ -13,13 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package com.android.ravenwoodtest.bivalentinst;
 
-package com.android.systemui.scene.ui.composable.transitions
-
-import com.android.compose.animation.scene.TransitionBuilder
-
-fun TransitionBuilder.goneToNotificationsShadeTransition(
-    durationScale: Double = 1.0,
-) {
-    toNotificationsShadeTransition(durationScale)
+/**
+ * Empty class. We need it because an instrumentation target APK must have code.
+ */
+public class Empty {
 }
diff --git a/ravenwood/tests/bivalentinst/test/com/android/ravenwoodtest/bivalentinst/RavenwoodInstrumentationTest_nonself.java b/ravenwood/tests/bivalentinst/test/com/android/ravenwoodtest/bivalentinst/RavenwoodInstrumentationTest_nonself.java
new file mode 100644
index 0000000..9f3ca6f
--- /dev/null
+++ b/ravenwood/tests/bivalentinst/test/com/android/ravenwoodtest/bivalentinst/RavenwoodInstrumentationTest_nonself.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ravenwoodtest.bivalentinst;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.platform.test.annotations.DisabledOnRavenwood;
+import android.platform.test.ravenwood.RavenwoodConfig;
+import android.platform.test.ravenwood.RavenwoodConfig.Config;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for the case where the instrumentation target is _not_ the test APK itself.
+ */
+@RunWith(AndroidJUnit4.class)
+public class RavenwoodInstrumentationTest_nonself {
+    private static final String TARGET_PACKAGE_NAME =
+            "com.android.ravenwood.bivalentinst_target_app";
+    private static final String TEST_PACKAGE_NAME =
+            "com.android.ravenwood.bivalentinsttest_nonself_inst";
+
+    @Config
+    public static final RavenwoodConfig sConfig = new RavenwoodConfig.Builder()
+            .setPackageName(TEST_PACKAGE_NAME)
+            .setTargetPackageName(TARGET_PACKAGE_NAME)
+            .build();
+
+    private static Instrumentation sInstrumentation;
+    private static Context sTestContext;
+    private static Context sTargetContext;
+
+    @BeforeClass
+    public static void beforeClass() {
+        sInstrumentation = InstrumentationRegistry.getInstrumentation();
+        sTestContext = sInstrumentation.getContext();
+        sTargetContext = sInstrumentation.getTargetContext();
+    }
+
+    @Test
+    public void testTestContextPackageName() {
+        assertThat(sTestContext.getPackageName()).isEqualTo(TEST_PACKAGE_NAME);
+    }
+
+    @Test
+    public void testTargetContextPackageName() {
+        assertThat(sTargetContext.getPackageName()).isEqualTo(TARGET_PACKAGE_NAME);
+    }
+
+    @Test
+    public void testTestAppContext() {
+        // Test context doesn't have an app context.
+        assertThat(sTestContext.getApplicationContext()).isNull();
+    }
+
+    @Test
+    public void testTargetAppContextPackageName() {
+        assertThat(sTargetContext.getApplicationContext().getPackageName())
+                .isEqualTo(TARGET_PACKAGE_NAME);
+    }
+
+    @Test
+    public void testTargetAppAppContextPackageName() {
+        assertThat(sTargetContext.getApplicationContext()
+                .getApplicationContext().getPackageName())
+                .isEqualTo(TARGET_PACKAGE_NAME);
+    }
+
+    @Test
+    public void testContextSameness() {
+        assertThat(sTargetContext).isNotSameInstanceAs(sTestContext);
+
+        assertThat(sTargetContext).isNotSameInstanceAs(sTargetContext.getApplicationContext());
+
+        assertThat(sTargetContext.getApplicationContext()).isSameInstanceAs(
+                sTargetContext.getApplicationContext().getApplicationContext());
+    }
+
+    @Test
+    @DisabledOnRavenwood(reason = "b/366246777")
+    public void testTargetAppResource() {
+        assertThat(sTargetContext.getString(
+                com.android.ravenwood.bivalentinst_target_app.R.string.test_string_in_target))
+                .isEqualTo("Test string in target APK");
+    }
+
+    @Test
+    @DisabledOnRavenwood(
+            reason = "Loading resources from non-self-instrumenting test APK isn't supported yet")
+    public void testTestAppResource() {
+        assertThat(sTestContext.getString(
+                com.android.ravenwood.bivalentinsttest_nonself_inst.R.string.test_string_in_test))
+                .isEqualTo("String in test APK");
+    }
+}
diff --git a/ravenwood/tests/bivalentinst/test/com/android/ravenwoodtest/bivalentinst/RavenwoodInstrumentationTest_self.java b/ravenwood/tests/bivalentinst/test/com/android/ravenwoodtest/bivalentinst/RavenwoodInstrumentationTest_self.java
new file mode 100644
index 0000000..fdff222
--- /dev/null
+++ b/ravenwood/tests/bivalentinst/test/com/android/ravenwoodtest/bivalentinst/RavenwoodInstrumentationTest_self.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ravenwoodtest.bivalentinst;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.platform.test.annotations.DisabledOnRavenwood;
+import android.platform.test.ravenwood.RavenwoodConfig;
+import android.platform.test.ravenwood.RavenwoodConfig.Config;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for the case where the instrumentation target is the test APK itself.
+ */
+@RunWith(AndroidJUnit4.class)
+public class RavenwoodInstrumentationTest_self {
+
+    private static final String TARGET_PACKAGE_NAME =
+            "com.android.ravenwood.bivalentinsttest_self_inst";
+    private static final String TEST_PACKAGE_NAME =
+            "com.android.ravenwood.bivalentinsttest_self_inst";
+
+    @Config
+    public static final RavenwoodConfig sConfig = new RavenwoodConfig.Builder()
+            .setPackageName(TEST_PACKAGE_NAME)
+            .setTargetPackageName(TARGET_PACKAGE_NAME)
+            .build();
+
+
+    private static Instrumentation sInstrumentation;
+    private static Context sTestContext;
+    private static Context sTargetContext;
+
+    @BeforeClass
+    public static void beforeClass() {
+        sInstrumentation = InstrumentationRegistry.getInstrumentation();
+        sTestContext = sInstrumentation.getContext();
+        sTargetContext = sInstrumentation.getTargetContext();
+    }
+
+    @Test
+    public void testTestContextPackageName() {
+        assertThat(sTestContext.getPackageName()).isEqualTo(TEST_PACKAGE_NAME);
+    }
+
+    @Test
+    public void testTargetContextPackageName() {
+        assertThat(sTargetContext.getPackageName()).isEqualTo(TARGET_PACKAGE_NAME);
+    }
+
+    @Test
+    public void testTestAppContextPackageName() {
+        assertThat(sTestContext.getApplicationContext().getPackageName())
+                .isEqualTo(TEST_PACKAGE_NAME);
+    }
+
+    @Test
+    public void testTestAppAppContextPackageName() {
+        assertThat(sTestContext.getApplicationContext().getPackageName())
+                .isEqualTo(TEST_PACKAGE_NAME);
+    }
+
+    @Test
+    public void testTargetAppContextPackageName() {
+        assertThat(sTargetContext.getApplicationContext()
+                .getApplicationContext().getPackageName())
+                .isEqualTo(TARGET_PACKAGE_NAME);
+    }
+
+    @Test
+    public void testTargetAppAppContextPackageName() {
+        assertThat(sTargetContext.getApplicationContext()
+                .getApplicationContext().getPackageName())
+                .isEqualTo(TARGET_PACKAGE_NAME);
+    }
+
+    @Test
+    public void testContextSameness() {
+        assertThat(sTargetContext).isNotSameInstanceAs(sTestContext);
+
+        assertThat(sTestContext).isNotSameInstanceAs(sTestContext.getApplicationContext());
+        assertThat(sTargetContext).isNotSameInstanceAs(sTargetContext.getApplicationContext());
+
+        assertThat(sTestContext.getApplicationContext()).isSameInstanceAs(
+                sTestContext.getApplicationContext().getApplicationContext());
+        assertThat(sTargetContext.getApplicationContext()).isSameInstanceAs(
+                sTargetContext.getApplicationContext().getApplicationContext());
+    }
+
+    @Test
+    @DisabledOnRavenwood(reason = "b/366246777")
+    public void testTargetAppResource() {
+        assertThat(sTargetContext.getString(
+                com.android.ravenwood.bivalentinsttest_self_inst.R.string.test_string_in_test))
+                .isEqualTo("String in test APK");
+    }
+
+    @Test
+    @DisabledOnRavenwood(reason = "b/366246777")
+    public void testTestAppResource() {
+        assertThat(sTestContext.getString(
+                com.android.ravenwood.bivalentinsttest_self_inst.R.string.test_string_in_test))
+                .isEqualTo("String in test APK");
+    }
+}
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt
index a38512e..f7f9a85 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt
@@ -127,7 +127,7 @@
             }
         }
 
-        stats.totalProcessTime = log.iTime("$executableName processing $inJar") {
+        stats.totalProcessTime = log.vTime("$executableName processing $inJar") {
             ZipFile(inJar).use { inZip ->
                 val inEntries = inZip.entries()
 
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt
index e8341e5..10fe0a3 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt
@@ -20,6 +20,20 @@
 import com.android.hoststubgen.SetOnce
 import com.android.hoststubgen.ensureFileExists
 import com.android.hoststubgen.log
+import java.nio.file.Paths
+import kotlin.io.path.exists
+
+/**
+ * If this file exits, we also read options from it. This is "unsafe" because it could break
+ * incremental builds, if it sets any flag that affects the output file.
+ * (however, for now, there's no such options.)
+ *
+ * For example, to enable verbose logging, do `echo '-v' > ~/.raveniezr-unsafe`
+ *
+ * (but even the content of this file changes, soong won't rerun the command, so you need to
+ * remove the output first and then do a build again.)
+ */
+private val RAVENIZER_DOTFILE = System.getenv("HOME") + "/.raveniezr-unsafe"
 
 class RavenizerOptions(
     /** Input jar file*/
@@ -35,9 +49,16 @@
     var fatalValidation: SetOnce<Boolean> = SetOnce(false),
 ) {
     companion object {
-        fun parseArgs(args: Array<String>): RavenizerOptions {
+
+        fun parseArgs(origArgs: Array<String>): RavenizerOptions {
+            val args = origArgs.toMutableList()
+            if (Paths.get(RAVENIZER_DOTFILE).exists()) {
+                log.i("Reading options from $RAVENIZER_DOTFILE")
+                args.add(0, "@$RAVENIZER_DOTFILE")
+            }
+
             val ret = RavenizerOptions()
-            val ai = ArgIterator.withAtFiles(args)
+            val ai = ArgIterator.withAtFiles(args.toTypedArray())
 
             while (true) {
                 val arg = ai.nextArgOptional()
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/RunnerRewritingAdapter.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/RunnerRewritingAdapter.kt
index bd9d96d..cf6d6f6 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/RunnerRewritingAdapter.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/RunnerRewritingAdapter.kt
@@ -184,7 +184,7 @@
             av.visit("value", ravenwoodTestRunnerType.type)
             av.visitEnd()
         }
-        log.i("Update the @RunWith: ${classInternalName.toHumanReadableClassName()}")
+        log.v("Update the @RunWith: ${classInternalName.toHumanReadableClassName()}")
     }
 
     /*
@@ -442,7 +442,7 @@
             // Don't process a class if it has a @NoRavenizer annotation.
             classes.findClass(className)?.let { cn ->
                 if (cn.findAnyAnnotation(noRavenizerAnotType.descAsSet) != null) {
-                    log.w("Class ${className.toHumanReadableClassName()} has" +
+                    log.i("Class ${className.toHumanReadableClassName()} has" +
                         " @${noRavenizerAnotType.humanReadableName}. Skipping."
                     )
                     return false
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
index f83269f..2362b91 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
@@ -31,12 +31,14 @@
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Slog;
+import android.app.appsearch.AppSearchResult;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.appfunctions.RemoteServiceCaller.RunServiceCallCallback;
 import com.android.server.appfunctions.RemoteServiceCaller.ServiceUsageCompleteListener;
 
 import java.util.Objects;
+import java.util.concurrent.CompletionException;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
@@ -81,6 +83,17 @@
         final SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback =
                 new SafeOneTimeExecuteAppFunctionCallback(executeAppFunctionCallback);
 
+        try {
+            executeAppFunctionInternal(requestInternal, safeExecuteAppFunctionCallback);
+        } catch (Exception e) {
+            safeExecuteAppFunctionCallback.onResult(mapExceptionToExecuteAppFunctionResponse(e));
+        }
+    }
+
+    private void executeAppFunctionInternal(
+            ExecuteAppFunctionAidlRequest requestInternal,
+            SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback) {
+
         String validatedCallingPackage;
         UserHandle targetUser;
         try {
@@ -119,7 +132,7 @@
             return;
         }
 
-        mCallerValidator
+        var unused = mCallerValidator
                 .verifyCallerCanExecuteAppFunction(
                         validatedCallingPackage,
                         targetPackageName,
@@ -159,6 +172,12 @@
                             } finally {
                                 Binder.restoreCallingIdentity(token);
                             }
+                        })
+                .exceptionally(
+                        ex -> {
+                            safeExecuteAppFunctionCallback.onResult(
+                                    mapExceptionToExecuteAppFunctionResponse(ex));
+                            return null;
                         });
     }
 
@@ -235,4 +254,41 @@
                             /* extras= */ null));
         }
     }
+
+    private ExecuteAppFunctionResponse mapExceptionToExecuteAppFunctionResponse(Throwable e) {
+        if(e instanceof CompletionException) {
+            e = e.getCause();
+        }
+
+        if (e instanceof AppSearchException) {
+            AppSearchException appSearchException = (AppSearchException) e;
+            return ExecuteAppFunctionResponse.newFailure(
+                    mapAppSearchResultFailureCodeToExecuteAppFunctionResponse(
+                            appSearchException.getResultCode()),
+                    appSearchException.getMessage(),
+                    /* extras= */ null);
+        }
+
+        return ExecuteAppFunctionResponse.newFailure(
+                ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR,
+                e.getMessage(),
+                /* extras= */ null);
+    }
+
+    private int mapAppSearchResultFailureCodeToExecuteAppFunctionResponse(int resultCode) {
+        if (resultCode == AppSearchResult.RESULT_OK) {
+            throw new IllegalArgumentException(
+                    "This method can only be used to convert failure result codes.");
+        }
+
+        switch (resultCode) {
+            case AppSearchResult.RESULT_NOT_FOUND:
+                return ExecuteAppFunctionResponse.RESULT_INVALID_ARGUMENT;
+            case AppSearchResult.RESULT_INVALID_ARGUMENT:
+            case AppSearchResult.RESULT_INTERNAL_ERROR:
+            case AppSearchResult.RESULT_SECURITY_ERROR:
+                // fall-through
+        }
+        return ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR;
+    }
 }
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppSearchException.java b/services/appfunctions/java/com/android/server/appfunctions/AppSearchException.java
new file mode 100644
index 0000000..c23470a
--- /dev/null
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppSearchException.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appfunctions;
+
+import android.app.appsearch.AppSearchResult;
+
+/** Exception to wrap failure result codes returned by AppSearch. */
+public class AppSearchException extends RuntimeException {
+    private final int resultCode;
+
+    public AppSearchException(int resultCode, String message) {
+        super(message);
+        this.resultCode = resultCode;
+    }
+
+    /**
+     * Returns the result code used to create this exception, typically one of the {@link
+     * AppSearchResult} result codes.
+     */
+    public int getResultCode() {
+        return resultCode;
+    }
+}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java b/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java
index 9998438..94a63b4 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java
@@ -30,6 +30,7 @@
 import android.app.appsearch.AppSearchBatchResult;
 import android.app.appsearch.AppSearchManager;
 import android.app.appsearch.AppSearchManager.SearchContext;
+import android.app.appsearch.AppSearchResult;
 import android.app.appsearch.GenericDocument;
 import android.app.appsearch.GetByDocumentIdRequest;
 import android.content.Context;
@@ -38,8 +39,9 @@
 import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
-import java.util.Objects;
+
 import com.android.internal.infra.AndroidFuture;
+import java.util.Objects;
 
 /* Validates that caller has the correct privilege to call an AppFunctionManager Api. */
 class CallerValidatorImpl implements CallerValidator {
@@ -110,6 +112,10 @@
                 mContext.checkPermission(Manifest.permission.EXECUTE_APP_FUNCTIONS, pid, uid)
                         == PackageManager.PERMISSION_GRANTED;
 
+        if (!hasExecutionPermission) {
+            return AndroidFuture.completedFuture(false);
+        }
+
         final long token = Binder.clearCallingIdentity();
         try {
             FutureAppSearchSession futureAppSearchSession =
@@ -144,7 +150,14 @@
         if (result.isSuccess()) {
             return result.getSuccesses().get(documentId);
         }
-        throw new IllegalArgumentException("No document in the result for id: " + documentId);
+
+        AppSearchResult<GenericDocument> failedResult = result.getFailures().get(documentId);
+        throw new AppSearchException(
+                failedResult.getResultCode(),
+                "Unable to retrieve document with id: "
+                        + documentId
+                        + " due to "
+                        + failedResult.getErrorMessage());
     }
 
     private static boolean getRestrictCallersWithExecuteAppFunctionsProperty(
diff --git a/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java b/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java
index b1c25c4..0044b4b 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java
@@ -26,7 +26,6 @@
 import android.app.appsearch.PutDocumentsRequest;
 import android.app.appsearch.RemoveByDocumentIdRequest;
 import android.app.appsearch.SearchResult;
-import android.app.appsearch.SearchResults;
 import android.app.appsearch.SearchSpec;
 import android.app.appsearch.SetSchemaRequest;
 import android.app.appsearch.SetSchemaResponse;
@@ -36,8 +35,6 @@
 import java.io.Closeable;
 import java.io.IOException;
 import java.util.List;
-import java.util.Objects;
-import java.util.concurrent.Executor;
 
 /** A future API wrapper of {@link AppSearchSession} APIs. */
 public interface FutureAppSearchSession extends Closeable {
@@ -78,7 +75,7 @@
      * AppSearchSession} database.
      */
     AndroidFuture<AppSearchBatchResult<String, GenericDocument>> getByDocumentId(
-            GetByDocumentIdRequest getRequest);
+            @NonNull GetByDocumentIdRequest getRequest);
 
     /**
      * Retrieves documents from the open {@link AppSearchSession} that match a given query string
@@ -88,29 +85,15 @@
             @NonNull String queryExpression, @NonNull SearchSpec searchSpec);
 
     /** A future API wrapper of {@link android.app.appsearch.SearchResults}. */
-    class FutureSearchResults {
-        private final SearchResults mSearchResults;
-        private final Executor mExecutor;
+    interface FutureSearchResults {
 
-        public FutureSearchResults(
-                @NonNull SearchResults searchResults, @NonNull Executor executor) {
-            mSearchResults = Objects.requireNonNull(searchResults);
-            mExecutor = Objects.requireNonNull(executor);
-        }
-
-        public AndroidFuture<List<SearchResult>> getNextPage() {
-            AndroidFuture<AppSearchResult<List<SearchResult>>> nextPageFuture =
-                    new AndroidFuture<>();
-
-            mSearchResults.getNextPage(mExecutor, nextPageFuture::complete);
-            return nextPageFuture.thenApply(
-                    result -> {
-                        if (result.isSuccess()) {
-                            return result.getResultValue();
-                        } else {
-                            throw new RuntimeException(failedResultToException(result));
-                        }
-                    });
-        }
+        /**
+         * Retrieves the next page of {@link SearchResult} objects from the {@link AppSearchSession}
+         * database.
+         *
+         * <p>Continue calling this method to access results until it returns an empty list,
+         * signifying there are no more results.
+         */
+        AndroidFuture<List<SearchResult>> getNextPage();
     }
 }
diff --git a/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSessionImpl.java b/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSessionImpl.java
index e78f390..3079d9f 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSessionImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSessionImpl.java
@@ -30,6 +30,8 @@
 import android.app.appsearch.GetSchemaResponse;
 import android.app.appsearch.PutDocumentsRequest;
 import android.app.appsearch.RemoveByDocumentIdRequest;
+import android.app.appsearch.SearchResult;
+import android.app.appsearch.SearchResults;
 import android.app.appsearch.SearchSpec;
 import android.app.appsearch.SetSchemaRequest;
 import android.app.appsearch.SetSchemaResponse;
@@ -37,6 +39,7 @@
 import com.android.internal.infra.AndroidFuture;
 
 import java.io.IOException;
+import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 
@@ -176,12 +179,39 @@
             @NonNull String queryExpression, @NonNull SearchSpec searchSpec) {
         return getSessionAsync()
                 .thenApply(session -> session.search(queryExpression, searchSpec))
-                .thenApply(result -> new FutureSearchResults(result, mExecutor));
+                .thenApply(result -> new FutureSearchResultsImpl(result, mExecutor));
     }
 
     @Override
     public void close() throws IOException {}
 
+    private static final class FutureSearchResultsImpl implements FutureSearchResults {
+        private final SearchResults mSearchResults;
+        private final Executor mExecutor;
+
+        private FutureSearchResultsImpl(
+                @NonNull SearchResults searchResults, @NonNull Executor executor) {
+            this.mSearchResults = searchResults;
+            this.mExecutor = executor;
+        }
+
+        @Override
+        public AndroidFuture<List<SearchResult>> getNextPage() {
+            AndroidFuture<AppSearchResult<List<SearchResult>>> nextPageFuture =
+                    new AndroidFuture<>();
+
+            mSearchResults.getNextPage(mExecutor, nextPageFuture::complete);
+            return nextPageFuture.thenApply(
+                    result -> {
+                        if (result.isSuccess()) {
+                            return result.getResultValue();
+                        } else {
+                            throw new RuntimeException(failedResultToException(result));
+                        }
+                    });
+        }
+    }
+
     private static final class BatchResultCallbackAdapter<K, V>
             implements BatchResultCallback<K, V> {
         private final AndroidFuture<AppSearchBatchResult<K, V>> mFuture;
diff --git a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
index 01e1008..e2573590 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
@@ -16,35 +16,239 @@
 
 package com.android.server.appfunctions;
 
+import static android.app.appfunctions.AppFunctionRuntimeMetadata.RUNTIME_SCHEMA_TYPE;
+
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.WorkerThread;
+import android.app.appfunctions.AppFunctionRuntimeMetadata;
+import android.app.appfunctions.AppFunctionStaticMetadataHelper;
+import android.app.appsearch.AppSearchBatchResult;
+import android.app.appsearch.AppSearchResult;
+import android.app.appsearch.AppSearchSchema;
+import android.app.appsearch.PackageIdentifier;
 import android.app.appsearch.PropertyPath;
+import android.app.appsearch.PutDocumentsRequest;
+import android.app.appsearch.RemoveByDocumentIdRequest;
 import android.app.appsearch.SearchResult;
 import android.app.appsearch.SearchSpec;
+import android.app.appsearch.SetSchemaRequest;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.Signature;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.infra.AndroidFuture;
 import com.android.server.appfunctions.FutureAppSearchSession.FutureSearchResults;
 
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Collection;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
 
 /**
  * This class implements helper methods for synchronously interacting with AppSearch while
  * synchronizing AppFunction runtime and static metadata.
+ *
+ * <p>This class is not thread safe.
  */
 public class MetadataSyncAdapter {
-    private final FutureAppSearchSession mFutureAppSearchSession;
+    private static final String TAG = MetadataSyncAdapter.class.getSimpleName();
+    private final FutureAppSearchSession mRuntimeMetadataSearchSession;
+    private final FutureAppSearchSession mStaticMetadataSearchSession;
     private final Executor mSyncExecutor;
+    private final PackageManager mPackageManager;
+
+    // Hidden constants in {@link SetSchemaRequest} that restricts runtime metadata visibility
+    // by permissions.
+    public static final int EXECUTE_APP_FUNCTIONS = 9;
+    public static final int EXECUTE_APP_FUNCTIONS_TRUSTED = 10;
 
     public MetadataSyncAdapter(
             @NonNull Executor syncExecutor,
-            @NonNull FutureAppSearchSession futureAppSearchSession) {
+            @NonNull FutureAppSearchSession runtimeMetadataSearchSession,
+            @NonNull FutureAppSearchSession staticMetadataSearchSession,
+            @NonNull PackageManager packageManager) {
         mSyncExecutor = Objects.requireNonNull(syncExecutor);
-        mFutureAppSearchSession = Objects.requireNonNull(futureAppSearchSession);
+        mRuntimeMetadataSearchSession = Objects.requireNonNull(runtimeMetadataSearchSession);
+        mStaticMetadataSearchSession = Objects.requireNonNull(staticMetadataSearchSession);
+        mPackageManager = Objects.requireNonNull(packageManager);
+    }
+
+    /**
+     * This method submits a request to synchronize the AppFunction runtime and static metadata.
+     *
+     * @return A {@link AndroidFuture} that completes with a boolean value indicating whether the
+     *     synchronization was successful.
+     */
+    public AndroidFuture<Boolean> submitSyncRequest() {
+        AndroidFuture<Boolean> settableSyncStatus = new AndroidFuture<>();
+        mSyncExecutor.execute(
+                () -> {
+                    try {
+                        trySyncAppFunctionMetadataBlocking();
+                        settableSyncStatus.complete(true);
+                    } catch (Exception e) {
+                        settableSyncStatus.completeExceptionally(e);
+                    }
+                });
+        return settableSyncStatus;
+    }
+
+    @WorkerThread
+    private void trySyncAppFunctionMetadataBlocking()
+            throws ExecutionException, InterruptedException {
+        ArrayMap<String, ArraySet<String>> staticPackageToFunctionMap =
+                getPackageToFunctionIdMap(
+                        mStaticMetadataSearchSession,
+                        AppFunctionStaticMetadataHelper.STATIC_SCHEMA_TYPE,
+                        AppFunctionStaticMetadataHelper.PROPERTY_FUNCTION_ID,
+                        AppFunctionStaticMetadataHelper.PROPERTY_PACKAGE_NAME);
+        ArrayMap<String, ArraySet<String>> runtimePackageToFunctionMap =
+                getPackageToFunctionIdMap(
+                        mRuntimeMetadataSearchSession,
+                        RUNTIME_SCHEMA_TYPE,
+                        AppFunctionRuntimeMetadata.PROPERTY_FUNCTION_ID,
+                        AppFunctionRuntimeMetadata.PROPERTY_PACKAGE_NAME);
+
+        ArrayMap<String, ArraySet<String>> addedFunctionsDiffMap =
+                getAddedFunctionsDiffMap(staticPackageToFunctionMap, runtimePackageToFunctionMap);
+        ArrayMap<String, ArraySet<String>> removedFunctionsDiffMap =
+                getRemovedFunctionsDiffMap(staticPackageToFunctionMap, runtimePackageToFunctionMap);
+
+        Set<AppSearchSchema> appRuntimeMetadataSchemas =
+                getAllRuntimeMetadataSchemas(staticPackageToFunctionMap.keySet());
+        appRuntimeMetadataSchemas.add(
+                AppFunctionRuntimeMetadata.createParentAppFunctionRuntimeSchema());
+
+        // Operation order matters here. i.e. remove -> setSchema -> add. Otherwise we would
+        // encounter an error trying to delete a document with no existing schema.
+        if (!removedFunctionsDiffMap.isEmpty()) {
+            RemoveByDocumentIdRequest removeByDocumentIdRequest =
+                    buildRemoveRuntimeMetadataRequest(removedFunctionsDiffMap);
+            AppSearchBatchResult<String, Void> removeDocumentBatchResult =
+                    mRuntimeMetadataSearchSession.remove(removeByDocumentIdRequest).get();
+            if (!removeDocumentBatchResult.isSuccess()) {
+                throw convertFailedAppSearchResultToException(
+                        removeDocumentBatchResult.getFailures().values());
+            }
+        }
+
+        if (!addedFunctionsDiffMap.isEmpty()) {
+            // TODO(b/357551503): only set schema on package diff
+            SetSchemaRequest addSetSchemaRequest =
+                    buildSetSchemaRequestForRuntimeMetadataSchemas(appRuntimeMetadataSchemas);
+            Objects.requireNonNull(
+                    mRuntimeMetadataSearchSession.setSchema(addSetSchemaRequest).get());
+            PutDocumentsRequest putDocumentsRequest =
+                    buildPutRuntimeMetadataRequest(addedFunctionsDiffMap);
+            AppSearchBatchResult<String, Void> putDocumentBatchResult =
+                    mRuntimeMetadataSearchSession.put(putDocumentsRequest).get();
+            if (!putDocumentBatchResult.isSuccess()) {
+                throw convertFailedAppSearchResultToException(
+                        putDocumentBatchResult.getFailures().values());
+            }
+        }
+    }
+
+    @NonNull
+    private static IllegalStateException convertFailedAppSearchResultToException(
+            @NonNull Collection<AppSearchResult<Void>> appSearchResult) {
+        Objects.requireNonNull(appSearchResult);
+        StringBuilder errorMessages = new StringBuilder();
+        for (AppSearchResult<Void> result : appSearchResult) {
+            errorMessages.append(result.getErrorMessage());
+        }
+        return new IllegalStateException(errorMessages.toString());
+    }
+
+    @NonNull
+    private PutDocumentsRequest buildPutRuntimeMetadataRequest(
+            @NonNull ArrayMap<String, ArraySet<String>> addedFunctionsDiffMap) {
+        Objects.requireNonNull(addedFunctionsDiffMap);
+        PutDocumentsRequest.Builder putDocumentRequestBuilder = new PutDocumentsRequest.Builder();
+
+        for (int i = 0; i < addedFunctionsDiffMap.size(); i++) {
+            String packageName = addedFunctionsDiffMap.keyAt(i);
+            ArraySet<String> addedFunctionIds = addedFunctionsDiffMap.valueAt(i);
+            for (String addedFunctionId : addedFunctionIds) {
+                putDocumentRequestBuilder.addGenericDocuments(
+                        new AppFunctionRuntimeMetadata.Builder(packageName, addedFunctionId)
+                                .build());
+            }
+        }
+        return putDocumentRequestBuilder.build();
+    }
+
+    @NonNull
+    private RemoveByDocumentIdRequest buildRemoveRuntimeMetadataRequest(
+            @NonNull ArrayMap<String, ArraySet<String>> removedFunctionsDiffMap) {
+        Objects.requireNonNull(AppFunctionRuntimeMetadata.APP_FUNCTION_RUNTIME_NAMESPACE);
+        Objects.requireNonNull(removedFunctionsDiffMap);
+        RemoveByDocumentIdRequest.Builder removeDocumentRequestBuilder =
+                new RemoveByDocumentIdRequest.Builder(
+                        AppFunctionRuntimeMetadata.APP_FUNCTION_RUNTIME_NAMESPACE);
+
+        for (int i = 0; i < removedFunctionsDiffMap.size(); i++) {
+            String packageName = removedFunctionsDiffMap.keyAt(i);
+            ArraySet<String> removedFunctionIds = removedFunctionsDiffMap.valueAt(i);
+            for (String functionId : removedFunctionIds) {
+                String documentId =
+                        AppFunctionRuntimeMetadata.getDocumentIdForAppFunction(
+                                packageName, functionId);
+                removeDocumentRequestBuilder.addIds(documentId);
+            }
+        }
+        return removeDocumentRequestBuilder.build();
+    }
+
+    @NonNull
+    private SetSchemaRequest buildSetSchemaRequestForRuntimeMetadataSchemas(
+            @NonNull Set<AppSearchSchema> metadataSchemaSet) {
+        Objects.requireNonNull(metadataSchemaSet);
+        SetSchemaRequest.Builder setSchemaRequestBuilder =
+                new SetSchemaRequest.Builder().setForceOverride(true).addSchemas(metadataSchemaSet);
+
+        for (AppSearchSchema runtimeMetadataSchema : metadataSchemaSet) {
+            String packageName =
+                    AppFunctionRuntimeMetadata.getPackageNameFromSchema(
+                            runtimeMetadataSchema.getSchemaType());
+            byte[] packageCert = getCertificate(packageName);
+            if (packageCert == null) {
+                continue;
+            }
+            setSchemaRequestBuilder.setSchemaTypeVisibilityForPackage(
+                    runtimeMetadataSchema.getSchemaType(),
+                    true,
+                    new PackageIdentifier(packageName, packageCert));
+            setSchemaRequestBuilder.addRequiredPermissionsForSchemaTypeVisibility(
+                    runtimeMetadataSchema.getSchemaType(), Set.of(EXECUTE_APP_FUNCTIONS));
+            setSchemaRequestBuilder.addRequiredPermissionsForSchemaTypeVisibility(
+                    runtimeMetadataSchema.getSchemaType(), Set.of(EXECUTE_APP_FUNCTIONS_TRUSTED));
+        }
+        return setSchemaRequestBuilder.build();
+    }
+
+    @NonNull
+    @WorkerThread
+    private Set<AppSearchSchema> getAllRuntimeMetadataSchemas(
+            @NonNull Set<String> staticMetadataPackages) {
+        Objects.requireNonNull(staticMetadataPackages);
+
+        Set<AppSearchSchema> appRuntimeMetadataSchemas = new ArraySet<>();
+        for (String packageName : staticMetadataPackages) {
+            appRuntimeMetadataSchemas.add(
+                    AppFunctionRuntimeMetadata.createAppFunctionRuntimeSchema(packageName));
+        }
+
+        return appRuntimeMetadataSchemas;
     }
 
     /**
@@ -123,13 +327,17 @@
      * This method returns a map of package names to a set of function ids from the AppFunction
      * metadata.
      *
-     * @param schemaType The name space of the AppFunction metadata.
+     * @param searchSession The {@link FutureAppSearchSession} to search the AppFunction metadata.
+     * @param schemaType The schema type of the AppFunction metadata.
+     * @param propertyFunctionId The property name of the function id in the AppFunction metadata.
+     * @param propertyPackageName The property name of the package name in the AppFunction metadata.
      * @return A map of package names to a set of function ids from the AppFunction metadata.
      */
     @NonNull
     @VisibleForTesting
     @WorkerThread
-    ArrayMap<String, ArraySet<String>> getPackageToFunctionIdMap(
+    static ArrayMap<String, ArraySet<String>> getPackageToFunctionIdMap(
+            @NonNull FutureAppSearchSession searchSession,
             @NonNull String schemaType,
             @NonNull String propertyFunctionId,
             @NonNull String propertyPackageName)
@@ -140,7 +348,7 @@
         ArrayMap<String, ArraySet<String>> packageToFunctionIds = new ArrayMap<>();
 
         FutureSearchResults futureSearchResults =
-                mFutureAppSearchSession
+                searchSession
                         .search(
                                 "",
                                 buildMetadataSearchSpec(
@@ -188,4 +396,39 @@
                                 new PropertyPath(propertyPackageName)))
                 .build();
     }
+
+    /** Gets the SHA-256 certificate from a {@link PackageManager}, or null if it is not found. */
+    @Nullable
+    private byte[] getCertificate(@NonNull String packageName) {
+        Objects.requireNonNull(packageName);
+        PackageInfo packageInfo;
+        try {
+            packageInfo =
+                    Objects.requireNonNull(
+                            mPackageManager.getPackageInfo(
+                                    packageName,
+                                    PackageManager.GET_META_DATA
+                                            | PackageManager.GET_SIGNING_CERTIFICATES));
+        } catch (Exception e) {
+            Slog.d(TAG, "Package name info not found for package: " + packageName);
+            return null;
+        }
+        if (packageInfo.signingInfo == null) {
+            Slog.d(TAG, "Signing info not found for package: " + packageInfo.packageName);
+            return null;
+        }
+
+        MessageDigest md;
+        try {
+            md = MessageDigest.getInstance("SHA256");
+        } catch (NoSuchAlgorithmException e) {
+            return null;
+        }
+        Signature[] signatures = packageInfo.signingInfo.getSigningCertificateHistory();
+        if (signatures == null || signatures.length == 0) {
+            return null;
+        }
+        md.update(signatures[0].toByteArray());
+        return md.digest();
+    }
 }
diff --git a/services/autofill/java/com/android/server/autofill/TEST_MAPPING b/services/autofill/java/com/android/server/autofill/TEST_MAPPING
index d8a6917..1dbeebe 100644
--- a/services/autofill/java/com/android/server/autofill/TEST_MAPPING
+++ b/services/autofill/java/com/android/server/autofill/TEST_MAPPING
@@ -1,15 +1,7 @@
 {
   "presubmit-large": [
     {
-      "name": "CtsAutoFillServiceTestCases",
-      "options": [
-        {
-          "include-annotation": "android.platform.test.annotations.Presubmit"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsAutoFillServiceTestCases_android_server_autofill_Presubmit"
     }
   ]
 }
diff --git a/services/backup/TEST_MAPPING b/services/backup/TEST_MAPPING
index e153230..0c14e56 100644
--- a/services/backup/TEST_MAPPING
+++ b/services/backup/TEST_MAPPING
@@ -1,12 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "FrameworksMockingServicesTests",
-      "options": [
-        {
-          "include-filter": "com.android.server.backup"
-        }
-      ]
+      "name": "FrameworksMockingServicesTests_backup"
     },
     {
       "name": "CtsBackupTestCases",
diff --git a/services/companion/java/com/android/server/companion/securechannel/AttestationVerifier.java b/services/companion/java/com/android/server/companion/securechannel/AttestationVerifier.java
index 1559a3f..df3071e 100644
--- a/services/companion/java/com/android/server/companion/securechannel/AttestationVerifier.java
+++ b/services/companion/java/com/android/server/companion/securechannel/AttestationVerifier.java
@@ -53,9 +53,8 @@
      *
      * @param remoteAttestation the full certificate chain containing attestation extension.
      * @param attestationChallenge attestation challenge for authentication.
-     * @return true if attestation is successfully verified; false otherwise.
+     * @return 1 if attestation is successfully verified; 0 otherwise.
      */
-    @NonNull
     public int verifyAttestation(
             @NonNull byte[] remoteAttestation,
             @NonNull byte[] attestationChallenge
diff --git a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
index caa877c..14579c6 100644
--- a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
+++ b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
@@ -1,81 +1,32 @@
 {
   "presubmit": [
     {
-      "name": "CtsVirtualDevicesTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsVirtualDevicesTestCases"
     },
     {
-      "name": "CtsVirtualDevicesAudioTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsVirtualDevicesAudioTestCases"
     },
     {
-      "name": "CtsVirtualDevicesSensorTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsVirtualDevicesSensorTestCases"
     },
     {
-      "name": "CtsVirtualDevicesAppLaunchTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsVirtualDevicesAppLaunchTestCases"
     },
     {
       "name": "CtsVirtualDevicesCameraTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ],
       "keywords": ["primary-device"]
     },
     {
-      "name": "CtsHardwareTestCases",
-      "options": [
-        {
-          "include-filter": "android.hardware.input.cts.tests"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ],
+      "name": "CtsHardwareTestCases_cts_tests",
       "file_patterns": ["Virtual[^/]*\\.java"]
     },
     {
-      "name": "CtsAccessibilityServiceTestCases",
-      "options": [
-        {
-          "include-filter": "android.accessibilityservice.cts.AccessibilityDisplayProxyTest"
-        },
-        {
-          "exclude-annotation": "android.support.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsAccessibilityServiceTestCases_cts_accessibilitydisplayproxytest"
     }
   ],
   "postsubmit": [
     {
-      "name": "CtsMediaAudioTestCases",
-      "options": [
-        {
-          "include-filter": "android.media.audio.cts.AudioFocusWithVdmTest"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsMediaAudioTestCases_cts_audiofocuswithvdmtest"
     },
     {
       "name": "CtsPermissionTestCases",
@@ -92,12 +43,7 @@
       ]
     },
     {
-      "name": "CtsPermissionMultiDeviceTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsPermissionMultiDeviceTestCases"
     }
   ]
 }
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 1b5b7e8..4e36e3f 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -237,6 +237,7 @@
         "dreams_flags_lib",
         "aconfig_new_storage_flags_lib",
         "powerstats_flags_lib",
+        "locksettings_flags_lib",
     ],
     javac_shard_size: 50,
     javacflags: [
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index 30f3fd2..68d0ad2 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -1,13 +1,7 @@
 {
     "presubmit": [
         {
-            "name": "CtsLocationFineTestCases",
-            "options": [
-                {
-                    // TODO: Wait for test to deflake - b/293934372
-                    "exclude-filter":"android.location.cts.fine.ScanningSettingsTest"
-                }
-             ]
+            "name": "CtsLocationFineTestCases_android_server_location"
         },
         {
             "name": "CtsLocationCoarseTestCases"
@@ -20,12 +14,7 @@
             "file_patterns": ["NotificationManagerService\\.java"]
         },
         {
-            "name": "CtsWindowManagerDeviceWindow",
-            "options": [
-                {
-                    "include-filter": "android.server.wm.window.ToastWindowTest"
-                }
-            ],
+            "name": "CtsWindowManagerDeviceWindow_window_toastwindowtest",
             "file_patterns": ["NotificationManagerService\\.java"]
         },
         {
@@ -103,12 +92,7 @@
             "file_patterns": ["VcnManagementService\\.java"]
         },
         {
-            "name": "FrameworksVpnTests",
-            "options": [
-                {
-                    "exclude-annotation": "com.android.testutils.SkipPresubmit"
-                }
-            ],
+            "name": "FrameworksVpnTests_android_server_connectivity",
             "file_patterns": ["VpnManagerService\\.java"]
         },
         {
@@ -123,6 +107,15 @@
                 "Background.*\\.java",
                 "Activity.*\\.java"
             ]
+        },
+        {
+            "name": "CtsOsTestCases",
+            "file_patterns": ["StorageManagerService\\.java"],
+            "options": [
+                {
+                    "include-filter": "android.os.storage.cts.StorageManagerTest"
+                }
+            ]
         }
     ],
     "presubmit-large": [
@@ -180,15 +173,6 @@
                     "include-filter": "com.android.server.wm.BackgroundActivityStart*"
                 }
             ]
-        },
-        {
-            "name": "CtsOsTestCases",
-            "file_patterns": ["StorageManagerService\\.java"],
-            "options": [
-                {
-                    "include-filter": "android.os.storage.cts.StorageManagerTest"
-                }
-            ]
         }
    ]
 }
diff --git a/services/core/java/com/android/server/adaptiveauth/AdaptiveAuthService.java b/services/core/java/com/android/server/adaptiveauth/AdaptiveAuthService.java
index 4da5cfc..9398c7a 100644
--- a/services/core/java/com/android/server/adaptiveauth/AdaptiveAuthService.java
+++ b/services/core/java/com/android/server/adaptiveauth/AdaptiveAuthService.java
@@ -34,7 +34,6 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
-import android.os.PowerManager;
 import android.os.SystemClock;
 import android.util.Log;
 import android.util.Slog;
@@ -73,7 +72,6 @@
     private final LockSettingsInternal mLockSettings;
     private final BiometricManager mBiometricManager;
     private final KeyguardManager mKeyguardManager;
-    private final PowerManager mPowerManager;
     private final WindowManagerInternal mWindowManager;
     private final UserManagerInternal mUserManager;
     @VisibleForTesting
@@ -93,7 +91,6 @@
         mBiometricManager = Objects.requireNonNull(
                 context.getSystemService(BiometricManager.class));
         mKeyguardManager = Objects.requireNonNull(context.getSystemService(KeyguardManager.class));
-        mPowerManager = Objects.requireNonNull(context.getSystemService(PowerManager.class));
         mWindowManager = Objects.requireNonNull(
                 LocalServices.getService(WindowManagerInternal.class));
         mUserManager = Objects.requireNonNull(LocalServices.getService(UserManagerInternal.class));
@@ -290,9 +287,6 @@
                     parentUserId);
         }
 
-        // Power off the display
-        mPowerManager.goToSleep(SystemClock.uptimeMillis());
-
         // Lock the device
         mWindowManager.lockNow();
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index f5a297b..6fd281e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -685,6 +685,11 @@
     // default. Controlled by Settings.Global.FORCE_ENABLE_PSS_PROFILING
     volatile boolean mForceEnablePssProfiling = false;
 
+    // Indicates whether to use ApplicationInfo to determine launched state instead of PM user state
+    // This is a temporary workaround until the trunk-stable flag is pushed to nextfood.
+    // TODO: b/365979852 - remove this workaround when redundant
+    volatile boolean mFlagUseAppInfoNotLaunched = false;
+
     /**
      * Indicates whether the foreground service background start restriction is enabled for
      * caller app that is targeting S+.
@@ -1017,6 +1022,9 @@
     private static final Uri FORCE_ENABLE_PSS_PROFILING_URI =
             Settings.Global.getUriFor(Settings.Global.FORCE_ENABLE_PSS_PROFILING);
 
+    private static final Uri ENABLE_USE_APP_INFO_NOT_LAUNCHED_URI =
+            Settings.Global.getUriFor(Settings.Global.ENABLE_USE_APP_INFO_NOT_LAUNCHED);
+
     /**
      * The threshold to decide if a given association should be dumped into metrics.
      */
@@ -1479,6 +1487,7 @@
                     false, this);
         }
         mResolver.registerContentObserver(FORCE_ENABLE_PSS_PROFILING_URI, false, this);
+        mResolver.registerContentObserver(ENABLE_USE_APP_INFO_NOT_LAUNCHED_URI, false, this);
         updateConstants();
         if (mSystemServerAutomaticHeapDumpEnabled) {
             updateEnableAutomaticSystemServerHeapDumps();
@@ -1495,6 +1504,7 @@
         updateActivityStartsLoggingEnabled();
         updateForegroundServiceStartsLoggingEnabled();
         updateForceEnablePssProfiling();
+        updateEnableUseAppInfoNotLaunched();
         // Read DropboxRateLimiter params from flags.
         mService.initDropboxRateLimiter();
     }
@@ -1540,6 +1550,8 @@
             updateEnableAutomaticSystemServerHeapDumps();
         } else if (FORCE_ENABLE_PSS_PROFILING_URI.equals(uri)) {
             updateForceEnablePssProfiling();
+        } else if (ENABLE_USE_APP_INFO_NOT_LAUNCHED_URI.equals(uri)) {
+            updateEnableUseAppInfoNotLaunched();
         }
     }
 
@@ -1659,6 +1671,11 @@
                 Settings.Global.FORCE_ENABLE_PSS_PROFILING, 0) == 1;
     }
 
+    private void updateEnableUseAppInfoNotLaunched() {
+        mFlagUseAppInfoNotLaunched = Settings.Global.getInt(mResolver,
+                Settings.Global.ENABLE_USE_APP_INFO_NOT_LAUNCHED, 0) == 1;
+    }
+
     private void updateBackgroundActivityStarts() {
         mFlagBackgroundActivityStartsEnabled = DeviceConfig.getBoolean(
                 DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -2538,6 +2555,8 @@
         pw.print("  OOMADJ_UPDATE_QUICK="); pw.println(OOMADJ_UPDATE_QUICK);
         pw.print("  ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION=");
         pw.println(mEnableWaitForFinishAttachApplication);
+        pw.print("  FLAG_USE_APP_INFO_NOT_LAUNCHED=");
+        pw.println(mFlagUseAppInfoNotLaunched);
 
         pw.print("  "); pw.print(KEY_FOLLOW_UP_OOMADJ_UPDATE_WAIT_DURATION);
         pw.print("="); pw.println(FOLLOW_UP_OOMADJ_UPDATE_WAIT_DURATION);
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 3f4902d..75e9fad 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -1018,7 +1018,9 @@
         if (Flags.addBatteryUsageStatsSliceAtom()) {
             statsManager.setPullAtomCallback(
                     FrameworkStatsLog.BATTERY_USAGE_STATS_PER_UID,
-                    null, // use default PullAtomMetadata values
+                    new StatsManager.PullAtomMetadata.Builder()
+                            .setTimeoutMillis(3_000L)
+                            .build(),
                     DIRECT_EXECUTOR,
                     pullAtomCallback);
         }
@@ -1098,14 +1100,11 @@
                                     DEVICE_CONFIG_NAMESPACE,
                                     MIN_CONSUMED_POWER_THRESHOLD_KEY,
                                     0);
-                    final long sessionStart = 0;
-                    final long sessionEnd = System.currentTimeMillis();
                     final BatteryUsageStatsQuery query =
                             new BatteryUsageStatsQuery.Builder()
                                     .setMaxStatsAgeMs(0)
                                     .includeProcessStateData()
                                     .includeVirtualUids()
-                                    .aggregateSnapshots(sessionStart, sessionEnd)
                                     .setMinConsumedPowerThreshold(minConsumedPowerThreshold)
                                     .build();
                     bus = getBatteryUsageStats(List.of(query)).get(0);
@@ -1834,7 +1833,7 @@
 
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
-    public void noteScreenState(final int state) {
+    public void noteScreenState(final int displayId, final int state, final int reason) {
         super.noteScreenState_enforcePermission();
 
         synchronized (mLock) {
@@ -1844,7 +1843,8 @@
             mHandler.post(() -> {
                 if (DBG) Slog.d(TAG, "begin noteScreenState");
                 synchronized (mStats) {
-                    mStats.noteScreenStateLocked(0, state, elapsedRealtime, uptime, currentTime);
+                    mStats.noteScreenStateLocked(
+                            displayId, state, reason, elapsedRealtime, uptime, currentTime);
                 }
                 if (DBG) Slog.d(TAG, "end noteScreenState");
             });
@@ -1854,7 +1854,7 @@
 
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
-    public void noteScreenBrightness(final int brightness) {
+    public void noteScreenBrightness(final int displayId, final int brightness) {
         super.noteScreenBrightness_enforcePermission();
 
         synchronized (mLock) {
@@ -1862,7 +1862,8 @@
             final long uptime = SystemClock.uptimeMillis();
             mHandler.post(() -> {
                 synchronized (mStats) {
-                    mStats.noteScreenBrightnessLocked(0, brightness, elapsedRealtime, uptime);
+                    mStats.noteScreenBrightnessLocked(
+                            displayId, brightness, elapsedRealtime, uptime);
                 }
             });
         }
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 4898f10..cb918a0 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -3398,7 +3398,8 @@
         // Check if we should mark the processrecord for first launch after force-stopping
         if (wasStopped) {
             boolean wasEverLaunched = false;
-            if (android.app.Flags.useAppInfoNotLaunched()) {
+            if (android.app.Flags.useAppInfoNotLaunched()
+                    || mService.mConstants.mFlagUseAppInfoNotLaunched) {
                 wasEverLaunched = !info.isNotLaunched();
             } else {
                 try {
@@ -3419,7 +3420,8 @@
                         : STOPPED_STATE_FIRST_LAUNCH;
                 r.getWindowProcessController().setStoppedState(stoppedState);
             } else {
-                if (android.app.Flags.useAppInfoNotLaunched()) {
+                if (android.app.Flags.useAppInfoNotLaunched()
+                        || mService.mConstants.mFlagUseAppInfoNotLaunched) {
                     // If it was launched before, then it must be a force-stop
                     r.setWasForceStopped(wasEverLaunched);
                 } else {
@@ -3769,7 +3771,7 @@
                     boolean hasActivity = false;
                     int connUid = 0;
                     int connGroup = 0;
-                    while (i >= bottomI) {
+                    while (subProc.info.uid != uid) {
                         mLruProcesses.remove(i);
                         mLruProcesses.add(endIndex, subProc);
                         if (DEBUG_LRU) Slog.d(TAG_LRU,
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 439bca0..a13ce65 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -370,6 +370,13 @@
                   String propertyName = "next_boot." + makeAconfigFlagPropertyName(
                       actualNamespace, actualFlagName);
 
+                  if (Flags.supportLocalOverridesSysprops()) {
+                    // Don't propagate if there is a local override.
+                    String overrideName = actualNamespace + ":" + actualFlagName;
+                    if (DeviceConfig.getProperty(NAMESPACE_LOCAL_OVERRIDES, overrideName) != null) {
+                      continue;
+                    }
+                  }
                   setProperty(propertyName, flagValue);
               }
 
@@ -388,6 +395,42 @@
                 if (enableAconfigStorageDaemon()) {
                     setLocalOverridesInNewStorage(properties);
                 }
+
+                if (Flags.supportLocalOverridesSysprops()) {
+                  String overridesNamespace = properties.getNamespace();
+                  for (String key : properties.getKeyset()) {
+                    String realNamespace = key.split(":")[0];
+                    String realFlagName = key.split(":")[1];
+                    String aconfigPropertyName =
+                        makeAconfigFlagPropertyName(realNamespace, realFlagName);
+                    if (aconfigPropertyName == null) {
+                      logErr("unable to construct system property for " + realNamespace + "/"
+                        + key);
+                      return;
+                    }
+
+                    if (properties.getString(key, null) == null) {
+                      String deviceConfigValue =
+                              DeviceConfig.getProperty(realNamespace, realFlagName);
+                      String stagedDeviceConfigValue =
+                              DeviceConfig.getProperty(NAMESPACE_REBOOT_STAGING,
+                                              realNamespace + "*" + realFlagName);
+
+                      setProperty(aconfigPropertyName, deviceConfigValue);
+                      if (stagedDeviceConfigValue == null) {
+                        setProperty("next_boot." + aconfigPropertyName, deviceConfigValue);
+                      } else {
+                        setProperty("next_boot." + aconfigPropertyName, stagedDeviceConfigValue);
+                      }
+                    } else {
+                      // Otherwise, propagate the override to sysprops.
+                      setProperty(aconfigPropertyName, properties.getString(key, null));
+                      // If there's a staged value, make sure it's the override value.
+                      setProperty("next_boot." + aconfigPropertyName,
+                                properties.getString(key, null));
+                    }
+                  }
+                }
         });
     }
 
diff --git a/services/core/java/com/android/server/am/TEST_MAPPING b/services/core/java/com/android/server/am/TEST_MAPPING
index ab5e2d0..6383dcb 100644
--- a/services/core/java/com/android/server/am/TEST_MAPPING
+++ b/services/core/java/com/android/server/am/TEST_MAPPING
@@ -22,32 +22,10 @@
       ]
     },
     {
-      "name": "CtsAppFgsTestCases",
-      "options": [
-        {
-          "include-annotation": "android.platform.test.annotations.Presubmit"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.LargeTest"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsAppFgsTestCases_pm_Presubmit"
     },
     {
-      "name": "CtsShortFgsTestCases",
-      "options": [
-        {
-          "include-annotation": "android.platform.test.annotations.Presubmit"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.LargeTest"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsShortFgsTestCases_pm_Presubmit"
     },
     {
       "name": "FrameworksServicesTests_android_server_am_Presubmit"
@@ -73,23 +51,14 @@
     },
     {
       "file_patterns": ["Broadcast.*"],
-      "name": "CtsBroadcastTestCases",
-      "options": [
-        { "exclude-annotation": "androidx.test.filters.LargeTest" },
-        { "exclude-annotation": "androidx.test.filters.FlakyTest" },
-        { "exclude-annotation": "org.junit.Ignore" }
-      ]
+      "name": "CtsBroadcastTestCases_android_server_am"
     },
     {
-      "name": "CtsBRSTestCases",
       "file_patterns": [
         "ActivityManagerService\\.java",
         "BroadcastQueue\\.java"
       ],
-      "options": [
-        { "exclude-annotation": "androidx.test.filters.FlakyTest" },
-        { "exclude-annotation": "org.junit.Ignore" }
-      ]
+      "name": "CtsBRSTestCases"
     }
   ],
   "postsubmit": [
@@ -110,13 +79,7 @@
       ]
     },
     {
-      "name": "CtsStatsdAtomHostTestCases",
-      "options": [
-        { "include-filter": "android.cts.statsdatom.appexit.AppExitHostTest" },
-        { "exclude-annotation": "androidx.test.filters.LargeTest" },
-        { "exclude-annotation": "androidx.test.filters.FlakyTest" },
-        { "exclude-annotation": "org.junit.Ignore" }
-      ]
+      "name": "CtsStatsdAtomHostTestCases_appexit_appexithosttest"
     },
     {
       "name": "CtsContentTestCases",
diff --git a/services/core/java/com/android/server/app/TEST_MAPPING b/services/core/java/com/android/server/app/TEST_MAPPING
index b718ce6..9e76175 100644
--- a/services/core/java/com/android/server/app/TEST_MAPPING
+++ b/services/core/java/com/android/server/app/TEST_MAPPING
@@ -1,26 +1,10 @@
 {
   "presubmit": [
     {
-      "name": "CtsGameManagerTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsGameManagerTestCases"
     },
     {
-      "name": "CtsStatsdAtomHostTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        },
-        {
-          "include-filter": "android.cts.statsdatom.gamemanager"
-        }
-      ],
+      "name": "CtsStatsdAtomHostTestCases_statsdatom_gamemanager",
       "file_patterns": [
         "(/|^)GameManagerService.java"
       ]
@@ -29,18 +13,7 @@
       "name": "FrameworksMockingServicesTests_android_server_app"
     },
     {
-      "name": "FrameworksCoreGameManagerTests",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        },
-        {
-          "include-filter": "android.app"
-        }
-      ],
+      "name": "FrameworksCoreGameManagerTests_android_app",
       "file_patterns": [
         "(/|^)GameManagerService.java", "(/|^)GameManagerSettings.java"
       ]
diff --git a/services/core/java/com/android/server/appop/AttributedOp.java b/services/core/java/com/android/server/appop/AttributedOp.java
index 430be03..314664b 100644
--- a/services/core/java/com/android/server/appop/AttributedOp.java
+++ b/services/core/java/com/android/server/appop/AttributedOp.java
@@ -110,7 +110,8 @@
 
         mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
                 parent.packageName, persistentDeviceId, tag, uidState, flags, accessTime,
-                AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);
+                AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE,
+                DiscreteRegistry.ACCESS_TYPE_NOTE_OP);
     }
 
     /**
@@ -254,7 +255,7 @@
         if (isStarted) {
             mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
                     parent.packageName, persistentDeviceId, tag, uidState, flags, startTime,
-                    attributionFlags, attributionChainId);
+                    attributionFlags, attributionChainId, DiscreteRegistry.ACCESS_TYPE_START_OP);
         }
     }
 
@@ -290,12 +291,17 @@
      * stopping in the HistoricalRegistry, but does not delete it.
      *
      * @param triggeredByUidStateChange If {@code true}, then this method operates as usual, except
-     * that {@link AppOpsService#mActiveWatchers} will not be notified. This is currently only
-     * used in {@link #onUidStateChanged(int)}, for the purpose of restarting (i.e.,
-     * finishing then immediately starting again in the new uid state) the AttributedOp. In this
-     * case, the caller is responsible for guaranteeing that either the AttributedOp is started
-     * again or all {@link AppOpsService#mActiveWatchers} are notified that the AttributedOp is
-     * finished.
+     *                                  that {@link AppOpsService#mActiveWatchers} will not be
+     *                                  notified. This is currently only
+     *                                  used in {@link #onUidStateChanged(int)}, for the purpose of
+     *                                  restarting (i.e.,
+     *                                  finishing then immediately starting again in the new uid
+     *                                  state) the AttributedOp. In this
+     *                                  case, the caller is responsible for guaranteeing that either
+     *                                  the AttributedOp is started
+     *                                  again or all {@link AppOpsService#mActiveWatchers} are
+     *                                  notified that the AttributedOp is
+     *                                  finished.
      */
     @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService
     private void finishOrPause(@NonNull IBinder clientId, boolean triggeredByUidStateChange,
@@ -335,7 +341,9 @@
             mAppOpsService.mHistoricalRegistry.increaseOpAccessDuration(parent.op, parent.uid,
                     parent.packageName, persistentDeviceId, tag, event.getUidState(),
                     event.getFlags(), finishedEvent.getNoteTime(), finishedEvent.getDuration(),
-                    event.getAttributionFlags(), event.getAttributionChainId());
+                    event.getAttributionFlags(), event.getAttributionChainId(),
+                    isPausing ? DiscreteRegistry.ACCESS_TYPE_PAUSE_OP
+                            : DiscreteRegistry.ACCESS_TYPE_FINISH_OP);
 
             if (!isPausing) {
                 mAppOpsService.mInProgressStartOpEventPool.release(event);
@@ -443,7 +451,7 @@
             mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
                     parent.packageName, persistentDeviceId, tag, event.getUidState(),
                     event.getFlags(), startTime, event.getAttributionFlags(),
-                    event.getAttributionChainId());
+                    event.getAttributionChainId(), DiscreteRegistry.ACCESS_TYPE_RESUME_OP);
             if (shouldSendActive) {
                 mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
                         parent.packageName, tag, event.getVirtualDeviceId(), true,
@@ -864,12 +872,12 @@
         }
 
         InProgressStartOpEvent acquire(long startTime, long elapsedTime, @NonNull IBinder clientId,
-                @Nullable String attributionTag, int virtualDeviceId,  @NonNull Runnable onDeath,
+                @Nullable String attributionTag, int virtualDeviceId, @NonNull Runnable onDeath,
                 int proxyUid, @Nullable String proxyPackageName,
                 @Nullable String proxyAttributionTag, @Nullable String proxyDeviceId,
-                @AppOpsManager.UidState int uidState,
-                @AppOpsManager.OpFlags int flags, @AppOpsManager.AttributionFlags
-                int attributionFlags, int attributionChainId) throws RemoteException {
+                @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags,
+                @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId)
+                throws RemoteException {
 
             InProgressStartOpEvent recycled = acquire();
 
diff --git a/services/core/java/com/android/server/appop/DiscreteRegistry.java b/services/core/java/com/android/server/appop/DiscreteRegistry.java
index 2ce4623..7f161f6 100644
--- a/services/core/java/com/android/server/appop/DiscreteRegistry.java
+++ b/services/core/java/com/android/server/appop/DiscreteRegistry.java
@@ -32,13 +32,23 @@
 import static android.app.AppOpsManager.OP_FLAG_SELF;
 import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
 import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXY;
+import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION;
+import static android.app.AppOpsManager.OP_MONITOR_LOCATION;
 import static android.app.AppOpsManager.OP_NONE;
 import static android.app.AppOpsManager.OP_PHONE_CALL_CAMERA;
 import static android.app.AppOpsManager.OP_PHONE_CALL_MICROPHONE;
+import static android.app.AppOpsManager.OP_PROCESS_OUTGOING_CALLS;
+import static android.app.AppOpsManager.OP_READ_ICC_SMS;
+import static android.app.AppOpsManager.OP_READ_SMS;
 import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
 import static android.app.AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO;
 import static android.app.AppOpsManager.OP_RECORD_AUDIO;
 import static android.app.AppOpsManager.OP_RESERVED_FOR_TESTING;
+import static android.app.AppOpsManager.OP_SEND_SMS;
+import static android.app.AppOpsManager.OP_SMS_FINANCIAL_TRANSACTIONS;
+import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
+import static android.app.AppOpsManager.OP_WRITE_ICC_SMS;
+import static android.app.AppOpsManager.OP_WRITE_SMS;
 import static android.app.AppOpsManager.flagsToString;
 import static android.app.AppOpsManager.getUidStateName;
 import static android.companion.virtual.VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT;
@@ -46,6 +56,7 @@
 import static java.lang.Long.min;
 import static java.lang.Math.max;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
@@ -62,6 +73,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.XmlUtils;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
@@ -72,6 +84,8 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.text.SimpleDateFormat;
 import java.time.Duration;
 import java.time.Instant;
@@ -125,7 +139,6 @@
  * relies on {@link HistoricalRegistry} for controlling that no calls are allowed until then. All
  * outside calls are going through {@link HistoricalRegistry}, where
  * {@link HistoricalRegistry#isPersistenceInitializedMLocked()} check is done.
- *
  */
 
 final class DiscreteRegistry {
@@ -142,11 +155,40 @@
             + OP_PHONE_CALL_MICROPHONE + "," + OP_PHONE_CALL_CAMERA + ","
             + OP_RECEIVE_AMBIENT_TRIGGER_AUDIO + "," + OP_RECEIVE_SANDBOX_TRIGGER_AUDIO
             + "," + OP_RESERVED_FOR_TESTING;
+    private static final int[] sDiscreteOpsToLog =
+            new int[]{OP_FINE_LOCATION, OP_COARSE_LOCATION, OP_EMERGENCY_LOCATION, OP_CAMERA,
+                    OP_RECORD_AUDIO, OP_PHONE_CALL_MICROPHONE, OP_PHONE_CALL_CAMERA,
+                    OP_RECEIVE_AMBIENT_TRIGGER_AUDIO, OP_RECEIVE_SANDBOX_TRIGGER_AUDIO, OP_READ_SMS,
+                    OP_WRITE_SMS, OP_SEND_SMS, OP_READ_ICC_SMS, OP_WRITE_ICC_SMS,
+                    OP_SMS_FINANCIAL_TRANSACTIONS, OP_SYSTEM_ALERT_WINDOW, OP_MONITOR_LOCATION,
+                    OP_MONITOR_HIGH_POWER_LOCATION, OP_PROCESS_OUTGOING_CALLS,
+            };
     private static final long DEFAULT_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(7).toMillis();
     private static final long MAXIMUM_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(30).toMillis();
     private static final long DEFAULT_DISCRETE_HISTORY_QUANTIZATION =
             Duration.ofMinutes(1).toMillis();
 
+    static final int ACCESS_TYPE_NOTE_OP =
+            FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__NOTE_OP;
+    static final int ACCESS_TYPE_START_OP =
+            FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__START_OP;
+    static final int ACCESS_TYPE_FINISH_OP =
+            FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__FINISH_OP;
+    static final int ACCESS_TYPE_PAUSE_OP =
+            FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__PAUSE_OP;
+    static final int ACCESS_TYPE_RESUME_OP =
+            FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__RESUME_OP;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"ACCESS_TYPE_"}, value = {
+            ACCESS_TYPE_NOTE_OP,
+            ACCESS_TYPE_START_OP,
+            ACCESS_TYPE_FINISH_OP,
+            ACCESS_TYPE_PAUSE_OP,
+            ACCESS_TYPE_RESUME_OP
+    })
+    public @interface AccessType {}
+
     private static long sDiscreteHistoryCutoff;
     private static long sDiscreteHistoryQuantization;
     private static int[] sDiscreteOps;
@@ -255,7 +297,23 @@
     void recordDiscreteAccess(int uid, String packageName, @NonNull String deviceId, int op,
             @Nullable String attributionTag, @AppOpsManager.OpFlags int flags,
             @AppOpsManager.UidState int uidState, long accessTime, long accessDuration,
-            @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) {
+            @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId,
+            @AccessType int accessType) {
+        if (shouldLogAccess(op)) {
+            int firstChar = 0;
+            if (attributionTag != null && attributionTag.startsWith(packageName)) {
+                firstChar = packageName.length();
+                if (firstChar < attributionTag.length() && attributionTag.charAt(firstChar)
+                        == '.') {
+                    firstChar++;
+                }
+            }
+            FrameworkStatsLog.write(FrameworkStatsLog.APP_OP_ACCESS_TRACKED, uid, op, accessType,
+                    uidState, flags, attributionFlags,
+                    attributionTag == null ? null : attributionTag.substring(firstChar),
+                    attributionChainId);
+        }
+
         if (!isDiscreteOp(op, flags)) {
             return;
         }
@@ -388,7 +446,7 @@
                                 if (event == null
                                         || event.mAttributionChainId == ATTRIBUTION_CHAIN_ID_NONE
                                         || (event.mAttributionFlags & ATTRIBUTION_FLAG_TRUSTED)
-                                                == 0) {
+                                        == 0) {
                                     continue;
                                 }
 
@@ -1523,6 +1581,11 @@
         return true;
     }
 
+    private static boolean shouldLogAccess(int op) {
+        return Flags.appopAccessTrackingLoggingEnabled()
+                && ArrayUtils.contains(sDiscreteOpsToLog, op);
+    }
+
     private static long discretizeTimeStamp(long timeStamp) {
         return timeStamp / sDiscreteHistoryQuantization * sDiscreteHistoryQuantization;
 
@@ -1530,7 +1593,7 @@
 
     private static long discretizeDuration(long duration) {
         return duration == -1 ? -1 : (duration + sDiscreteHistoryQuantization - 1)
-                        / sDiscreteHistoryQuantization * sDiscreteHistoryQuantization;
+                / sDiscreteHistoryQuantization * sDiscreteHistoryQuantization;
     }
 
     void setDebugMode(boolean debugMode) {
diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java
index fffb108..6b02538 100644
--- a/services/core/java/com/android/server/appop/HistoricalRegistry.java
+++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java
@@ -474,7 +474,8 @@
     void incrementOpAccessedCount(int op, int uid, @NonNull String packageName,
             @NonNull String deviceId, @Nullable String attributionTag, @UidState int uidState,
             @OpFlags int flags, long accessTime,
-            @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) {
+            @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId,
+            @DiscreteRegistry.AccessType int accessType) {
         synchronized (mInMemoryLock) {
             if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
                 if (!isPersistenceInitializedMLocked()) {
@@ -487,7 +488,7 @@
 
                 mDiscreteRegistry.recordDiscreteAccess(uid, packageName, deviceId, op,
                         attributionTag, flags, uidState, accessTime, -1, attributionFlags,
-                        attributionChainId);
+                        attributionChainId, accessType);
             }
         }
     }
@@ -510,7 +511,8 @@
     void increaseOpAccessDuration(int op, int uid, @NonNull String packageName,
             @NonNull String deviceId, @Nullable String attributionTag, @UidState int uidState,
             @OpFlags int flags, long eventStartTime, long increment,
-            @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) {
+            @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId,
+            @DiscreteRegistry.AccessType int accessType) {
         synchronized (mInMemoryLock) {
             if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
                 if (!isPersistenceInitializedMLocked()) {
@@ -522,7 +524,7 @@
                         attributionTag, uidState, flags, increment);
                 mDiscreteRegistry.recordDiscreteAccess(uid, packageName, deviceId, op,
                         attributionTag, flags, uidState, eventStartTime, increment,
-                        attributionFlags, attributionChainId);
+                        attributionFlags, attributionChainId, accessType);
             }
         }
     }
diff --git a/services/core/java/com/android/server/appop/TEST_MAPPING b/services/core/java/com/android/server/appop/TEST_MAPPING
index 9317c1e..25dd30b 100644
--- a/services/core/java/com/android/server/appop/TEST_MAPPING
+++ b/services/core/java/com/android/server/appop/TEST_MAPPING
@@ -1,12 +1,7 @@
 {
     "presubmit": [
         {
-            "name": "CtsAppOpsTestCases",
-            "options": [
-                {
-                  "exclude-annotation": "androidx.test.filters.FlakyTest"
-                }
-            ]
+            "name": "CtsAppOpsTestCases"
         },
         {
             "name": "CtsAppOps2TestCases"
@@ -21,12 +16,7 @@
             "name": "CtsPermissionTestCases_Platform"
         },
         {
-            "name": "CtsAppTestCases",
-            "options": [
-                {
-                    "include-filter": "android.app.cts.ActivityManagerApi29Test"
-                }
-            ]
+            "name": "CtsAppTestCases_cts_activitymanagerapi29test"
         },
         {
             "name": "CtsStatsdAtomHostTestCases",
diff --git a/services/core/java/com/android/server/attention/TEST_MAPPING b/services/core/java/com/android/server/attention/TEST_MAPPING
index e5b0344..519ed07 100644
--- a/services/core/java/com/android/server/attention/TEST_MAPPING
+++ b/services/core/java/com/android/server/attention/TEST_MAPPING
@@ -1,24 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "CtsVoiceInteractionTestCases",
-      "options": [
-        {
-          "include-filter": "android.voiceinteraction.cts.AlwaysOnHotwordDetectorTest"
-        },
-        {
-          "include-filter": "android.voiceinteraction.cts.unittests.HotwordDetectedResultTest"
-        },
-        {
-          "include-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest"
-        },
-        {
-          "include-filter": "android.voiceinteraction.cts.HotwordDetectionServiceProximityTest"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsVoiceInteractionTestCases_android_server_attention"
     }
   ]
 }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 6daf0d0..c3d09bb 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -9211,7 +9211,7 @@
                 index = 1;
             }
 
-            if (replaceStreamBtSco()) {
+            if (replaceStreamBtSco() && index != 0) {
                 index = (int) (mIndexMin + (index * 10 - mIndexMin) / getIndexStepFactor() + 5)
                         / 10;
             }
diff --git a/services/core/java/com/android/server/biometrics/biometrics.aconfig b/services/core/java/com/android/server/biometrics/biometrics.aconfig
index b2e95aa..d3da8dd 100644
--- a/services/core/java/com/android/server/biometrics/biometrics.aconfig
+++ b/services/core/java/com/android/server/biometrics/biometrics.aconfig
@@ -24,3 +24,13 @@
       purpose: PURPOSE_BUGFIX
   }
 }
+
+flag {
+  name: "set_ignore_speed_up"
+  namespace: "biometrics_framework"
+  description: "This flag controls whether setIgnoreDisplayTouches is called directly on session from FingerprintProvider"
+  bug: "359289274"
+  metadata {
+      purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 8195efe..456591c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -791,7 +791,17 @@
 
     @Override
     public void setIgnoreDisplayTouches(long requestId, int sensorId, boolean ignoreTouches) {
-        mFingerprintSensors.get(sensorId).getScheduler().getCurrentClientIfMatches(
+        if (Flags.setIgnoreSpeedUp()) {
+            try {
+                mFingerprintSensors.get(
+                        sensorId).getLazySession().get().getSession().setIgnoreDisplayTouches(
+                        ignoreTouches);
+                Slog.d(getTag(), "setIgnoreDisplayTouches set to " + ignoreTouches);
+            } catch (Exception e) {
+                Slog.w(getTag(), "setIgnore failed", e);
+            }
+        } else {
+            mFingerprintSensors.get(sensorId).getScheduler().getCurrentClientIfMatches(
                 requestId, (client) -> {
                     if (!(client instanceof Udfps)) {
                         Slog.e(getTag(),
@@ -800,6 +810,7 @@
                     }
                     ((Udfps) client).setIgnoreDisplayTouches(ignoreTouches);
                 });
+        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/cpu/CpuInfoReader.java b/services/core/java/com/android/server/cpu/CpuInfoReader.java
index 984ad1d..a68451a 100644
--- a/services/core/java/com/android/server/cpu/CpuInfoReader.java
+++ b/services/core/java/com/android/server/cpu/CpuInfoReader.java
@@ -40,6 +40,7 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -80,13 +81,14 @@
     /** package **/ @interface CpusetCategory{}
 
     // TODO(b/242722241): Protect updatable variables with a local lock.
-    private final File mCpusetDir;
     private final long mMinReadIntervalMillis;
     private final SparseIntArray mCpusetCategoriesByCpus = new SparseIntArray();
     private final SparseArray<File> mCpuFreqPolicyDirsById = new SparseArray<>();
     private final SparseArray<StaticPolicyInfo> mStaticPolicyInfoById = new SparseArray<>();
     private final SparseArray<LongSparseLongArray> mTimeInStateByPolicyId = new SparseArray<>();
+    private final AtomicBoolean mShouldReadCpusetCategories;
 
+    private File mCpusetDir;
     private File mCpuFreqDir;
     private File mProcStatFile;
     private SparseArray<CpuUsageStats> mCumulativeCpuUsageStats = new SparseArray<>();
@@ -106,10 +108,13 @@
         mCpuFreqDir = cpuFreqDir;
         mProcStatFile = procStatFile;
         mMinReadIntervalMillis = minReadIntervalMillis;
+        mShouldReadCpusetCategories = new AtomicBoolean(true);
     }
 
     /**
      * Initializes CpuInfoReader and returns a boolean to indicate whether the reader is enabled.
+     *
+     * <p>Returns {@code true} on success. Otherwise, returns {@code false}.
      */
     public boolean init() {
         if (mCpuFreqPolicyDirsById.size() > 0) {
@@ -139,8 +144,7 @@
             Slogf.e(TAG, "Missing proc stat file at %s", mProcStatFile.getAbsolutePath());
             return false;
         }
-        readCpusetCategories();
-        if (mCpusetCategoriesByCpus.size() == 0) {
+        if (!readCpusetCategories()) {
             Slogf.e(TAG, "Failed to read cpuset information from %s", mCpusetDir.getAbsolutePath());
             return false;
         }
@@ -163,10 +167,19 @@
         return true;
     }
 
+  public void stopPeriodicCpusetReading() {
+        mShouldReadCpusetCategories.set(false);
+        if (!readCpusetCategories()) {
+            Slogf.e(TAG, "Failed to read cpuset information from %s",
+                    mCpusetDir.getAbsolutePath());
+            mIsEnabled = false;
+        }
+    }
+
     /**
      * Reads CPU information from proc and sys fs files exposed by the Kernel.
      *
-     * @return SparseArray keyed by CPU core ID; {@code null} on error or when disabled.
+     * <p>Returns SparseArray keyed by CPU core ID; {@code null} on error or when disabled.
      */
     @Nullable
     public SparseArray<CpuInfo> readCpuInfos() {
@@ -183,6 +196,12 @@
         }
         mLastReadUptimeMillis = uptimeMillis;
         mLastReadCpuInfos = null;
+        if (mShouldReadCpusetCategories.get() && !readCpusetCategories()) {
+            Slogf.e(TAG, "Failed to read cpuset information from %s",
+                    mCpusetDir.getAbsolutePath());
+            mIsEnabled = false;
+            return null;
+        }
         SparseArray<CpuUsageStats> cpuUsageStatsByCpus = readLatestCpuUsageStats();
         if (cpuUsageStatsByCpus == null || cpuUsageStatsByCpus.size() == 0) {
             Slogf.e(TAG, "Failed to read latest CPU usage stats");
@@ -324,7 +343,7 @@
     /**
      * Sets the CPU frequency for testing.
      *
-     * <p>Return {@code true} on success. Otherwise, returns {@code false}.
+     * <p>Returns {@code true} on success. Otherwise, returns {@code false}.
      */
     @VisibleForTesting
     boolean setCpuFreqDir(File cpuFreqDir) {
@@ -354,7 +373,7 @@
     /**
      * Sets the proc stat file for testing.
      *
-     * <p>Return true on success. Otherwise, returns false.
+     * <p>Returns {@code true} on success. Otherwise, returns {@code false}.
      */
     @VisibleForTesting
     boolean setProcStatFile(File procStatFile) {
@@ -366,6 +385,21 @@
         return true;
     }
 
+    /**
+     * Set the cpuset directory for testing.
+     *
+     * <p>Returns {@code true} on success. Otherwise, returns {@code false}.
+     */
+    @VisibleForTesting
+    boolean setCpusetDir(File cpusetDir) {
+        if (!cpusetDir.exists() && !cpusetDir.isDirectory()) {
+            Slogf.e(TAG, "Missing or invalid cpuset directory at %s", cpusetDir.getAbsolutePath());
+            return false;
+        }
+        mCpusetDir = cpusetDir;
+        return true;
+    }
+
     private void populateCpuFreqPolicyDirsById(File[] policyDirs) {
         mCpuFreqPolicyDirsById.clear();
         for (int i = 0; i < policyDirs.length; i++) {
@@ -381,12 +415,27 @@
         }
     }
 
-    private void readCpusetCategories() {
+    /**
+     * Reads cpuset categories by CPU.
+     *
+     * <p>The cpusets are read from the cpuset category specific directories
+     * under the /dev/cpuset directory. The cpuset categories are subject to change at any point
+     * during system bootup, as determined by the init rules specified within the init.rc files.
+     * Therefore, it's necessary to read the cpuset categories each time before accessing CPU usage
+     * statistics until the system boot completes. Once the boot is complete, the latest changes to
+     * the cpuset categories will take a few seconds to propagate. Thus, on boot complete,
+     * the periodic reading is stopped with a delay of
+     * {@link CpuMonitorService#STOP_PERIODIC_CPUSET_READING_DELAY_MILLISECONDS}.
+     *
+     * <p>Returns {@code true} on success. Otherwise, returns {@code false}.
+     */
+    private boolean readCpusetCategories() {
         File[] cpusetDirs = mCpusetDir.listFiles(File::isDirectory);
         if (cpusetDirs == null) {
             Slogf.e(TAG, "Missing cpuset directories at %s", mCpusetDir.getAbsolutePath());
-            return;
+            return false;
         }
+        mCpusetCategoriesByCpus.clear();
         for (int i = 0; i < cpusetDirs.length; i++) {
             File dir = cpusetDirs[i];
             @CpusetCategory int cpusetCategory;
@@ -418,6 +467,7 @@
                 }
             }
         }
+        return mCpusetCategoriesByCpus.size() > 0;
     }
 
     private void readStaticPolicyInfo() {
diff --git a/services/core/java/com/android/server/cpu/CpuMonitorService.java b/services/core/java/com/android/server/cpu/CpuMonitorService.java
index 7ea2c1b..88ff7e4 100644
--- a/services/core/java/com/android/server/cpu/CpuMonitorService.java
+++ b/services/core/java/com/android/server/cpu/CpuMonitorService.java
@@ -22,6 +22,7 @@
 import static com.android.server.cpu.CpuAvailabilityMonitoringConfig.CPUSET_BACKGROUND;
 import static com.android.server.cpu.CpuInfoReader.FLAG_CPUSET_CATEGORY_BACKGROUND;
 import static com.android.server.cpu.CpuInfoReader.FLAG_CPUSET_CATEGORY_TOP_APP;
+import static com.android.server.SystemService.PHASE_BOOT_COMPLETED;
 
 import android.annotation.Nullable;
 import android.content.Context;
@@ -82,6 +83,15 @@
     //  frequently. Should this duration be increased as well when this happens?
     private static final long LATEST_AVAILABILITY_DURATION_MILLISECONDS =
             TimeUnit.SECONDS.toMillis(30);
+    /**
+     * Delay to stop the periodic cpuset reading after boot complete.
+     *
+     * Device specific implementations can update cpuset on boot complete. This may take
+     * a few seconds to propagate. So, wait for a few minutes before stopping the periodic cpuset
+     * reading.
+     */
+    private static final long STOP_PERIODIC_CPUSET_READING_DELAY_MILLISECONDS =
+            TimeUnit.MINUTES.toMillis(2);
 
     private final Context mContext;
     private final HandlerThread mHandlerThread;
@@ -90,6 +100,7 @@
     private final long mNormalMonitoringIntervalMillis;
     private final long mDebugMonitoringIntervalMillis;
     private final long mLatestAvailabilityDurationMillis;
+    private final long mStopPeriodicCpusetReadingDelayMillis;
     private final Object mLock = new Object();
     @GuardedBy("mLock")
     private final SparseArrayMap<CpuMonitorInternal.CpuAvailabilityCallback,
@@ -153,13 +164,15 @@
         this(context, new CpuInfoReader(), new ServiceThread(TAG,
                         Process.THREAD_PRIORITY_BACKGROUND, /* allowIo= */ true),
                 Build.IS_USERDEBUG || Build.IS_ENG, NORMAL_MONITORING_INTERVAL_MILLISECONDS,
-                DEBUG_MONITORING_INTERVAL_MILLISECONDS, LATEST_AVAILABILITY_DURATION_MILLISECONDS);
+                DEBUG_MONITORING_INTERVAL_MILLISECONDS, LATEST_AVAILABILITY_DURATION_MILLISECONDS,
+                STOP_PERIODIC_CPUSET_READING_DELAY_MILLISECONDS);
     }
 
     @VisibleForTesting
     CpuMonitorService(Context context, CpuInfoReader cpuInfoReader, HandlerThread handlerThread,
             boolean shouldDebugMonitor, long normalMonitoringIntervalMillis,
-            long debugMonitoringIntervalMillis, long latestAvailabilityDurationMillis) {
+            long debugMonitoringIntervalMillis, long latestAvailabilityDurationMillis,
+            long stopPeriodicCpusetReadingDelayMillis) {
         super(context);
         mContext = context;
         mHandlerThread = handlerThread;
@@ -167,6 +180,7 @@
         mNormalMonitoringIntervalMillis = normalMonitoringIntervalMillis;
         mDebugMonitoringIntervalMillis = debugMonitoringIntervalMillis;
         mLatestAvailabilityDurationMillis = latestAvailabilityDurationMillis;
+        mStopPeriodicCpusetReadingDelayMillis = stopPeriodicCpusetReadingDelayMillis;
         mCpuInfoReader = cpuInfoReader;
         mCpusetInfosByCpuset = new SparseArray<>(2);
         mCpusetInfosByCpuset.append(CPUSET_ALL, new CpusetInfo(CPUSET_ALL));
@@ -200,6 +214,16 @@
         }
     }
 
+    @Override
+    public void onBootPhase(int phase) {
+        if (phase != PHASE_BOOT_COMPLETED) {
+            return;
+        }
+        Slogf.i(TAG, "Stopping periodic cpuset reading on boot complete");
+        mHandler.postDelayed(() -> mCpuInfoReader.stopPeriodicCpusetReading(),
+                mStopPeriodicCpusetReadingDelayMillis);
+    }
+
     @VisibleForTesting
     long getCurrentMonitoringIntervalMillis() {
         synchronized (mLock) {
diff --git a/services/core/java/com/android/server/crashrecovery/TEST_MAPPING b/services/core/java/com/android/server/crashrecovery/TEST_MAPPING
index 615db34..537fb325 100644
--- a/services/core/java/com/android/server/crashrecovery/TEST_MAPPING
+++ b/services/core/java/com/android/server/crashrecovery/TEST_MAPPING
@@ -1,4 +1,9 @@
 {
+  "presubmit": [
+    {
+      "name": "CrashRecoveryModuleTests"
+    }
+  ],
   "postsubmit": [
     {
       "name": "FrameworksMockingServicesTests",
@@ -7,9 +12,6 @@
           "include-filter": "com.android.server.RescuePartyTest"
         }
       ]
-    },
-    {
-      "name": "CrashRecoveryModuleTests"
     }
   ]
 }
\ No newline at end of file
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index c3faec0..04573f4 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -536,7 +536,9 @@
         mLastBrightnessEvent = new BrightnessEvent(mDisplayId);
         mTempBrightnessEvent = new BrightnessEvent(mDisplayId);
 
-        if (mDisplayId == Display.DEFAULT_DISPLAY) {
+        if (flags.isBatteryStatsEnabledForAllDisplays()) {
+            mBatteryStats = BatteryStatsService.getService();
+        } else if (mDisplayId == Display.DEFAULT_DISPLAY) {
             mBatteryStats = BatteryStatsService.getService();
         } else {
             mBatteryStats = null;
@@ -2791,8 +2793,7 @@
                 screenState, mDisplayStatsId, reason);
         if (mBatteryStats != null) {
             try {
-                // TODO(multi-display): make this multi-display
-                mBatteryStats.noteScreenState(screenState);
+                mBatteryStats.noteScreenState(mDisplayId, screenState, reason);
             } catch (RemoteException e) {
                 // same process
             }
@@ -2807,7 +2808,7 @@
                 int brightnessInt = mFlags.isBrightnessIntRangeUserPerceptionEnabled()
                         ? BrightnessSynchronizer.brightnessFloatToIntSetting(mContext, brightness)
                         : BrightnessSynchronizer.brightnessFloatToInt(brightness);
-                mBatteryStats.noteScreenBrightness(brightnessInt);
+                mBatteryStats.noteScreenBrightness(mDisplayId, brightnessInt);
             } catch (RemoteException e) {
                 // same process
             }
diff --git a/services/core/java/com/android/server/display/TEST_MAPPING b/services/core/java/com/android/server/display/TEST_MAPPING
index 049b2fd..4d7962f 100644
--- a/services/core/java/com/android/server/display/TEST_MAPPING
+++ b/services/core/java/com/android/server/display/TEST_MAPPING
@@ -1,21 +1,12 @@
 {
     "presubmit": [
         {
-            "name": "DisplayServiceTests",
-            "options": [
-                {"include-filter": "com.android.server.display"},
-                {"exclude-annotation": "androidx.test.filters.FlakyTest"},
-                {"exclude-annotation": "org.junit.Ignore"}
-            ]
+            "name": "DisplayServiceTests_server_display"
         }
     ],
     "postsubmit": [
         {
-            "name": "DisplayServiceTests",
-            "options": [
-                {"include-filter": "com.android.server.display"},
-                {"exclude-annotation": "org.junit.Ignore"}
-            ]
+            "name": "DisplayServiceTests_server_display"
         }
     ]
 }
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index 69b67c8..f600e7f 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -200,6 +200,11 @@
             Flags::normalBrightnessForDozeParameter
     );
 
+    private final FlagState mEnableBatteryStatsForAllDisplays = new FlagState(
+            Flags.FLAG_ENABLE_BATTERY_STATS_FOR_ALL_DISPLAYS,
+            Flags::enableBatteryStatsForAllDisplays
+    );
+
     /**
      * @return {@code true} if 'port' is allowed in display layout configuration file.
      */
@@ -415,6 +420,14 @@
     }
 
     /**
+      * @return {@code true} if battery stats is enabled for all displays, not just the primary
+      * display.
+      */
+    public boolean isBatteryStatsEnabledForAllDisplays() {
+        return mEnableBatteryStatsForAllDisplays.isEnabled();
+    }
+
+    /**
      * dumps all flagstates
      * @param pw printWriter
      */
@@ -456,6 +469,7 @@
         pw.println(" " + mNewHdrBrightnessModifier);
         pw.println(" " + mNormalBrightnessForDozeParameter);
         pw.println(" " + mIdleScreenConfigInSubscribingLightSensor);
+        pw.println(" " + mEnableBatteryStatsForAllDisplays);
     }
 
     private static class FlagState {
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 70230b4..9968ba5 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -349,3 +349,11 @@
       purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "enable_battery_stats_for_all_displays"
+    namespace: "display_manager"
+    description: "Flag to enable battery stats for all displays."
+    bug: "366112793"
+    is_fixed_read_only: true
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/hdmi/TEST_MAPPING b/services/core/java/com/android/server/hdmi/TEST_MAPPING
index d116087d..bacacaf 100644
--- a/services/core/java/com/android/server/hdmi/TEST_MAPPING
+++ b/services/core/java/com/android/server/hdmi/TEST_MAPPING
@@ -12,18 +12,7 @@
   // Postsubmit tests for TV devices
   "tv-postsubmit": [
     {
-      "name": "HdmiCecTests",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        },
-        {
-          "include-filter": "android.hardware.hdmi"
-        }
-      ],
+      "name": "HdmiCecTests_hardware_hdmi",
       "file_patterns": [
         "(/|^)DeviceFeature[^/]*", "(/|^)Hdmi[^/]*"
       ]
diff --git a/services/core/java/com/android/server/input/debug/TouchpadDebugView.java b/services/core/java/com/android/server/input/debug/TouchpadDebugView.java
index 486d4af..cc13e8e 100644
--- a/services/core/java/com/android/server/input/debug/TouchpadDebugView.java
+++ b/services/core/java/com/android/server/input/debug/TouchpadDebugView.java
@@ -16,6 +16,8 @@
 
 package com.android.server.input.debug;
 
+import static android.util.TypedValue.COMPLEX_UNIT_DIP;
+
 import android.annotation.NonNull;
 import android.content.Context;
 import android.content.res.Configuration;
@@ -24,6 +26,7 @@
 import android.graphics.Rect;
 import android.hardware.input.InputManager;
 import android.util.Slog;
+import android.util.TypedValue;
 import android.view.Gravity;
 import android.view.MotionEvent;
 import android.view.ViewConfiguration;
@@ -38,6 +41,13 @@
 import java.util.Objects;
 
 public class TouchpadDebugView extends LinearLayout {
+    private static final float MAX_SCREEN_WIDTH_PROPORTION = 0.4f;
+    private static final float MAX_SCREEN_HEIGHT_PROPORTION = 0.4f;
+    private static final float MIN_SCALE_FACTOR = 10f;
+    private static final float TEXT_SIZE_SP = 16.0f;
+    private static final float DEFAULT_RES_X = 47f;
+    private static final float DEFAULT_RES_Y = 45f;
+
     /**
      * Input device ID for the touchpad that this debug view is displaying.
      */
@@ -62,6 +72,7 @@
             new TouchpadHardwareState(0, 0 /* buttonsDown */, 0, 0,
                     new TouchpadFingerState[0]);
     private TouchpadVisualizationView mTouchpadVisualizationView;
+    private final TouchpadHardwareProperties mTouchpadHardwareProperties;
 
     public TouchpadDebugView(Context context, int touchpadId,
             TouchpadHardwareProperties touchpadHardwareProperties) {
@@ -69,10 +80,10 @@
         mTouchpadId = touchpadId;
         mWindowManager =
                 Objects.requireNonNull(getContext().getSystemService(WindowManager.class));
-        init(context, touchpadHardwareProperties, touchpadId);
+        mTouchpadHardwareProperties = touchpadHardwareProperties;
+        init(context, touchpadId);
         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
 
-        // TODO(b/360137366): Use the hardware properties to initialise layout parameters.
         mWindowLayoutParams = new WindowManager.LayoutParams();
         mWindowLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
         mWindowLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
@@ -92,8 +103,8 @@
         mWindowLayoutParams.gravity = Gravity.TOP | Gravity.LEFT;
     }
 
-    private void init(Context context, TouchpadHardwareProperties touchpadHardwareProperties,
-            int touchpadId) {
+    private void init(Context context, int touchpadId) {
+        updateScreenDimensions();
         setOrientation(VERTICAL);
         setLayoutParams(new LayoutParams(
                 LayoutParams.WRAP_CONTENT,
@@ -102,35 +113,34 @@
 
         TextView nameView = new TextView(context);
         nameView.setBackgroundColor(Color.RED);
-        nameView.setTextSize(20);
+        nameView.setTextSize(TEXT_SIZE_SP);
         nameView.setText(Objects.requireNonNull(Objects.requireNonNull(
                         mContext.getSystemService(InputManager.class))
                 .getInputDevice(touchpadId)).getName());
         nameView.setGravity(Gravity.CENTER);
         nameView.setTextColor(Color.WHITE);
-        nameView.setLayoutParams(new LayoutParams(1000, 200));
+        nameView.setLayoutParams(
+                new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
 
         mTouchpadVisualizationView = new TouchpadVisualizationView(context,
-                touchpadHardwareProperties);
+                mTouchpadHardwareProperties);
         mTouchpadVisualizationView.setBackgroundColor(Color.WHITE);
-        //TODO(b/365568238): set the view size according to the touchpad size from the
-        // TouchpadHardwareProperties
-        mTouchpadVisualizationView.setLayoutParams(new LayoutParams(778, 500));
 
         //TODO(b/365562952): Add a display for recognized gesture info here
         TextView gestureInfoView = new TextView(context);
         gestureInfoView.setBackgroundColor(Color.GRAY);
-        gestureInfoView.setTextSize(20);
+        gestureInfoView.setTextSize(TEXT_SIZE_SP);
         gestureInfoView.setText("Touchpad Debug View 3");
         gestureInfoView.setGravity(Gravity.CENTER);
         gestureInfoView.setTextColor(Color.BLACK);
-        gestureInfoView.setLayoutParams(new LayoutParams(1000, 200));
+        gestureInfoView.setLayoutParams(
+                new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
 
         addView(nameView);
         addView(mTouchpadVisualizationView);
         addView(gestureInfoView);
 
-        updateScreenDimensions();
+        updateViewsDimensions();
     }
 
     @Override
@@ -191,6 +201,7 @@
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
         updateScreenDimensions();
+        updateViewsDimensions();
 
         // Adjust view position to stay within screen bounds after rotation
         mWindowLayoutParams.x =
@@ -204,6 +215,41 @@
         return deltaX * deltaX + deltaY * deltaY >= mTouchSlop * mTouchSlop;
     }
 
+    private void updateViewsDimensions() {
+        float resX = mTouchpadHardwareProperties.getResX() == 0f ? DEFAULT_RES_X
+                : mTouchpadHardwareProperties.getResX();
+        float resY = mTouchpadHardwareProperties.getResY() == 0f ? DEFAULT_RES_Y
+                : mTouchpadHardwareProperties.getResY();
+
+        float touchpadHeightMm = Math.abs(
+                mTouchpadHardwareProperties.getBottom() - mTouchpadHardwareProperties.getTop())
+                / resY;
+        float touchpadWidthMm = Math.abs(
+                mTouchpadHardwareProperties.getLeft() - mTouchpadHardwareProperties.getRight())
+                / resX;
+
+        float maxViewWidthPx = mScreenWidth * MAX_SCREEN_WIDTH_PROPORTION;
+        float maxViewHeightPx = mScreenHeight * MAX_SCREEN_HEIGHT_PROPORTION;
+
+        float minScaleFactorPx = TypedValue.applyDimension(COMPLEX_UNIT_DIP, MIN_SCALE_FACTOR,
+                getResources().getDisplayMetrics());
+
+        float scaleFactorBasedOnWidth =
+                touchpadWidthMm * minScaleFactorPx > maxViewWidthPx ? maxViewWidthPx
+                        / touchpadWidthMm : minScaleFactorPx;
+        float scaleFactorBasedOnHeight =
+                touchpadHeightMm * minScaleFactorPx > maxViewHeightPx ? maxViewHeightPx
+                        / touchpadHeightMm : minScaleFactorPx;
+        float scaleFactorUsed = Math.min(scaleFactorBasedOnHeight, scaleFactorBasedOnWidth);
+
+        mTouchpadVisualizationView.setLayoutParams(
+                new LayoutParams((int) (touchpadWidthMm * scaleFactorUsed),
+                        (int) (touchpadHeightMm * scaleFactorUsed)));
+
+        mTouchpadVisualizationView.updateScaleFactor(scaleFactorUsed);
+        mTouchpadVisualizationView.invalidate();
+    }
+
     private void updateScreenDimensions() {
         Rect windowBounds =
                 mWindowManager.getCurrentWindowMetrics().getBounds();
diff --git a/services/core/java/com/android/server/input/debug/TouchpadVisualizationView.java b/services/core/java/com/android/server/input/debug/TouchpadVisualizationView.java
index 9ba7d0a..67c3621 100644
--- a/services/core/java/com/android/server/input/debug/TouchpadVisualizationView.java
+++ b/services/core/java/com/android/server/input/debug/TouchpadVisualizationView.java
@@ -30,40 +30,52 @@
 public class TouchpadVisualizationView extends View {
     private static final String TAG = "TouchpadVizMain";
     private static final boolean DEBUG = true;
+    private static final float DEFAULT_RES_X = 47f;
+    private static final float DEFAULT_RES_Y = 45f;
 
     private final TouchpadHardwareProperties mTouchpadHardwareProperties;
+    private float mScaleFactor;
 
     TouchpadHardwareState mLatestHardwareState = new TouchpadHardwareState(0, 0, 0, 0,
             new TouchpadFingerState[]{});
 
-    private final Paint mOvalPaint;
+    private final Paint mOvalStrokePaint;
+    private final Paint mOvalFillPaint;
+    private final RectF mTempOvalRect = new RectF();
 
     public TouchpadVisualizationView(Context context,
             TouchpadHardwareProperties touchpadHardwareProperties) {
         super(context);
         mTouchpadHardwareProperties = touchpadHardwareProperties;
-        mOvalPaint = new Paint();
-        mOvalPaint.setAntiAlias(true);
-        mOvalPaint.setARGB(255, 0, 0, 0);
-        mOvalPaint.setStyle(Paint.Style.STROKE);
+        mScaleFactor = 1;
+        mOvalStrokePaint = new Paint();
+        mOvalStrokePaint.setAntiAlias(true);
+        mOvalStrokePaint.setARGB(255, 0, 0, 0);
+        mOvalStrokePaint.setStyle(Paint.Style.STROKE);
+        mOvalFillPaint = new Paint();
+        mOvalFillPaint.setAntiAlias(true);
+        mOvalFillPaint.setARGB(255, 0, 0, 0);
     }
 
-    private final RectF mOvalRect = new RectF();
-
-    private void drawOval(Canvas canvas, float x, float y, float major, float minor, float angle,
-            Paint paint) {
+    private void drawOval(Canvas canvas, float x, float y, float major, float minor, float angle) {
         canvas.save(Canvas.MATRIX_SAVE_FLAG);
         canvas.rotate(angle, x, y);
-        mOvalRect.left = x - minor / 2;
-        mOvalRect.right = x + minor / 2;
-        mOvalRect.top = y - major / 2;
-        mOvalRect.bottom = y + major / 2;
-        canvas.drawOval(mOvalRect, paint);
+        mTempOvalRect.left = x - minor / 2;
+        mTempOvalRect.right = x + minor / 2;
+        mTempOvalRect.top = y - major / 2;
+        mTempOvalRect.bottom = y + major / 2;
+        canvas.drawOval(mTempOvalRect, mOvalStrokePaint);
+        canvas.drawOval(mTempOvalRect, mOvalFillPaint);
         canvas.restore();
     }
 
     @Override
     protected void onDraw(Canvas canvas) {
+        float maximumPressure = 0;
+        for (TouchpadFingerState touchpadFingerState : mLatestHardwareState.getFingerStates()) {
+            maximumPressure = Math.max(maximumPressure, touchpadFingerState.getPressure());
+        }
+
         for (TouchpadFingerState touchpadFingerState : mLatestHardwareState.getFingerStates()) {
             float newX = translateRange(mTouchpadHardwareProperties.getLeft(),
                     mTouchpadHardwareProperties.getRight(), 0, getWidth(),
@@ -73,16 +85,22 @@
                     mTouchpadHardwareProperties.getBottom(), 0, getHeight(),
                     touchpadFingerState.getPositionY());
 
-            float newAngle = -translateRange(mTouchpadHardwareProperties.getOrientationMinimum(),
-                    mTouchpadHardwareProperties.getOrientationMaximum(), 0, 360,
-                    touchpadFingerState.getOrientation());
+            float newAngle = translateRange(0, mTouchpadHardwareProperties.getOrientationMaximum(),
+                    0, 90, touchpadFingerState.getOrientation());
 
-            float newTouchMajor =
-                    touchpadFingerState.getTouchMajor() / mTouchpadHardwareProperties.getResX();
-            float newTouchMinor =
-                    touchpadFingerState.getTouchMinor() / mTouchpadHardwareProperties.getResY();
+            float resX = mTouchpadHardwareProperties.getResX() == 0f ? DEFAULT_RES_X
+                    : mTouchpadHardwareProperties.getResX();
+            float resY = mTouchpadHardwareProperties.getResY() == 0f ? DEFAULT_RES_Y
+                    : mTouchpadHardwareProperties.getResY();
 
-            drawOval(canvas, newX, newY, newTouchMajor, newTouchMinor, newAngle, mOvalPaint);
+            float newTouchMajor = touchpadFingerState.getTouchMajor() * mScaleFactor / resY;
+            float newTouchMinor = touchpadFingerState.getTouchMinor() * mScaleFactor / resX;
+
+            float pressureToOpacity = translateRange(0, maximumPressure, 0, 255,
+                    touchpadFingerState.getPressure());
+            mOvalFillPaint.setAlpha((int) pressureToOpacity);
+
+            drawOval(canvas, newX, newY, newTouchMajor, newTouchMinor, newAngle);
         }
     }
 
@@ -101,6 +119,15 @@
         invalidate();
     }
 
+    /**
+     * Update the scale factor of the drawings in the view.
+     *
+     * @param scaleFactor the new scale factor
+     */
+    public void updateScaleFactor(float scaleFactor) {
+        mScaleFactor = scaleFactor;
+    }
+
     private float translateRange(float rangeBeforeMin, float rangeBeforeMax,
             float rangeAfterMin, float rangeAfterMax, float value) {
         return rangeAfterMin + (value - rangeBeforeMin) / (rangeBeforeMax - rangeBeforeMin) * (
diff --git a/services/core/java/com/android/server/lights/TEST_MAPPING b/services/core/java/com/android/server/lights/TEST_MAPPING
index 1d2cd3c..8abdf00 100644
--- a/services/core/java/com/android/server/lights/TEST_MAPPING
+++ b/services/core/java/com/android/server/lights/TEST_MAPPING
@@ -1,12 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "CtsHardwareTestCases",
-      "options": [
-        {"include-filter": "com.android.hardware.lights"},
-        {"exclude-annotation": "androidx.test.filters.FlakyTest"},
-        {"exclude-annotation": "androidx.test.filters.LargeTest"}
-      ]
+      "name": "CtsHardwareTestCases_hardware_lights"
     },
     {
       "name": "FrameworksServicesTests_android_server_lights"
diff --git a/services/core/java/com/android/server/location/TEST_MAPPING b/services/core/java/com/android/server/location/TEST_MAPPING
index 64b1ed2..b2ac7d1 100644
--- a/services/core/java/com/android/server/location/TEST_MAPPING
+++ b/services/core/java/com/android/server/location/TEST_MAPPING
@@ -1,13 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "CtsLocationFineTestCases",
-      "options": [
-          {
-             // TODO: Wait for test to deflake - b/293934372
-             "exclude-filter":"android.location.cts.fine.ScanningSettingsTest"
-          }
-      ]
+      "name": "CtsLocationFineTestCases_android_server_location"
     },
     {
       "name": "CtsLocationCoarseTestCases"
diff --git a/services/core/java/com/android/server/locksettings/Android.bp b/services/core/java/com/android/server/locksettings/Android.bp
new file mode 100644
index 0000000..53f1ac6
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/Android.bp
@@ -0,0 +1,11 @@
+aconfig_declarations {
+    name: "locksettings_flags",
+    package: "com.android.server.locksettings",
+    container: "system",
+    srcs: ["*.aconfig"],
+}
+
+java_aconfig_library {
+    name: "locksettings_flags_lib",
+    aconfig_declarations: "locksettings_flags",
+}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 7d44ba1..3780fbd 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -16,7 +16,6 @@
 
 package com.android.server.locksettings;
 
-import static android.security.Flags.reportPrimaryAuthAttempts;
 import static android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE;
 import static android.Manifest.permission.CONFIGURE_FACTORY_RESET_PROTECTION;
 import static android.Manifest.permission.MANAGE_BIOMETRIC;
@@ -32,6 +31,7 @@
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.UserHandle.USER_ALL;
 import static android.os.UserHandle.USER_SYSTEM;
+import static android.security.Flags.reportPrimaryAuthAttempts;
 
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN;
@@ -1836,6 +1836,13 @@
     }
 
     /**
+     * Set a new LSKF for the given user/profile. Only succeeds if the synthetic password for the
+     * user is protected by the given {@param savedCredential}.
+     * <p>
+     * When {@link android.security.Flags#clearStrongAuthOnAddPrimaryCredential()} is enabled and
+     * setting a new credential where there was none, updates the strong auth state for
+     * {@param userId} to <tt>STRONG_AUTH_NOT_REQUIRED</tt>.
+     *
      * @param savedCredential if the user is a profile with
      * {@link UserManager#isCredentialSharableWithParent()} with unified challenge and
      *   savedCredential is empty, LSS will try to re-derive the profile password internally.
@@ -1884,6 +1891,12 @@
 
             onSyntheticPasswordUnlocked(userId, sp);
             setLockCredentialWithSpLocked(credential, sp, userId);
+            if (android.security.Flags.clearStrongAuthOnAddPrimaryCredential()
+                    && savedCredential.isNone() && !credential.isNone()) {
+                // Clear the strong auth value, since the LSKF has just been entered and set,
+                // but only when the previous credential was None.
+                mStrongAuth.reportUnlock(userId);
+            }
             sendCredentialsOnChangeIfRequired(credential, userId, isLockTiedToParent);
             return true;
         }
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
index f44b852..820c0ef 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
@@ -273,11 +273,6 @@
                     "server_based_ror_enabled", false);
         }
 
-        public boolean waitForInternet() {
-            return DeviceConfig.getBoolean(
-                    DeviceConfig.NAMESPACE_OTA, "wait_for_internet_ror", false);
-        }
-
         public boolean isNetworkConnected() {
             final ConnectivityManager connectivityManager =
                     mContext.getSystemService(ConnectivityManager.class);
@@ -433,7 +428,7 @@
 
     /** Wrapper function to set error code serialized through handler, */
     private void setLoadEscrowDataErrorCode(@RebootEscrowErrorCode int value, Handler handler) {
-        if (mInjector.waitForInternet()) {
+        if (Flags.waitForInternetRor()) {
             mInjector.post(
                     handler,
                     () -> {
@@ -516,7 +511,7 @@
             mWakeLock.acquire(mInjector.getWakeLockTimeoutMillis());
         }
 
-        if (mInjector.waitForInternet()) {
+        if (Flags.waitForInternetRor()) {
             // Timeout to stop retrying same as the wake lock timeout.
             mInjector.postDelayed(
                     retryHandler,
@@ -553,7 +548,7 @@
             return;
         }
 
-        if (mInjector.waitForInternet()) {
+        if (Flags.waitForInternetRor()) {
             if (mRebootEscrowTimedOut) {
                 Slog.w(TAG, "Failed to load reboot escrow data within timeout");
                 compareAndSetLoadEscrowDataErrorCode(
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index cc58f38..3a429b0 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -1701,7 +1701,7 @@
                     .setGatekeeperHAT(response.getPayload()).build();
             if (response.getShouldReEnroll()) {
                 try {
-                    response = gatekeeper.enroll(userId, spHandle, spHandle,
+                    response = gatekeeper.enroll(userId, spHandle, gatekeeperPassword,
                             gatekeeperPassword);
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Failed to invoke gatekeeper.enroll", e);
diff --git a/services/core/java/com/android/server/locksettings/TEST_MAPPING b/services/core/java/com/android/server/locksettings/TEST_MAPPING
index ffbdf7f..d338c50 100644
--- a/services/core/java/com/android/server/locksettings/TEST_MAPPING
+++ b/services/core/java/com/android/server/locksettings/TEST_MAPPING
@@ -1,15 +1,7 @@
 {
     "presubmit-large": [
         {
-            "name": "CtsDevicePolicyManagerTestCases",
-            "options": [
-                {
-                    "include-annotation": "com.android.cts.devicepolicy.annotations.LockSettingsTest"
-                },
-                {
-                    "exclude-annotation": "androidx.test.filters.FlakyTest"
-                }
-            ]
+            "name": "CtsDevicePolicyManagerTestCases_LockSettingsTest"
         }
     ],
     "presubmit": [
diff --git a/services/core/java/com/android/server/locksettings/flags.aconfig b/services/core/java/com/android/server/locksettings/flags.aconfig
new file mode 100644
index 0000000..6818de9
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/flags.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.server.locksettings"
+container: "system"
+
+flag {
+     name: "wait_for_internet_ror"
+     namespace: "sudo"
+     description: "Feature flag to wait for internet connectivity before calling resume on reboot server."
+     bug: "231660348"
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/media/projection/TEST_MAPPING b/services/core/java/com/android/server/media/projection/TEST_MAPPING
index 7aa9118..b33097c 100644
--- a/services/core/java/com/android/server/media/projection/TEST_MAPPING
+++ b/services/core/java/com/android/server/media/projection/TEST_MAPPING
@@ -1,15 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "MediaProjectionTests",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        }
-      ]
+      "name": "MediaProjectionTests"
     }
   ]
 }
diff --git a/services/core/java/com/android/server/net/TEST_MAPPING b/services/core/java/com/android/server/net/TEST_MAPPING
index ad6b0ca..d95849e 100644
--- a/services/core/java/com/android/server/net/TEST_MAPPING
+++ b/services/core/java/com/android/server/net/TEST_MAPPING
@@ -1,40 +1,16 @@
 {
   "presubmit-large": [
     {
-      "name": "CtsHostsideNetworkPolicyTests",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "android.platform.test.annotations.FlakyTest"
-        }
-      ]
+      "name": "CtsHostsideNetworkPolicyTests"
     }
   ],
   "presubmit": [
     {
-      "name": "FrameworksServicesTests",
-      "file_patterns": ["(/|^)Network(Policy|Management)[^/]*\\.java"],
-      "options": [
-        {
-          "include-filter": "com.android.server.net."
-        },
-        {
-          "include-annotation": "android.platform.test.annotations.Presubmit"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "FrameworksServicesTests_android_server_net_Presubmit",
+      "file_patterns": ["(/|^)Network(Policy|Management)[^/]*\\.java"]
     },
     {
-      "name": "FrameworksVpnTests",
-      "options": [
-        {
-          "exclude-annotation": "com.android.testutils.SkipPresubmit"
-        }
-      ],
+      "name": "FrameworksVpnTests_android_server_connectivity",
       "file_patterns": ["VpnManagerService\\.java"]
     }
   ]
diff --git a/services/core/java/com/android/server/notification/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java
index 4fa7112..82e00d9 100644
--- a/services/core/java/com/android/server/notification/GroupHelper.java
+++ b/services/core/java/com/android/server/notification/GroupHelper.java
@@ -757,8 +757,12 @@
 
             // scenario 3: sparse/singleton groups
             if (Flags.notificationForceGroupSingletons()) {
-                groupSparseGroups(record, notificationList, summaryByGroupKey, sectioner,
-                    fullAggregateGroupKey);
+                try {
+                    groupSparseGroups(record, notificationList, summaryByGroupKey, sectioner,
+                        fullAggregateGroupKey);
+                } catch (Throwable e) {
+                    Slog.wtf(TAG, "Failed to group sparse groups", e);
+                }
             }
         }
     }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index e2ec006..ba7d4d2 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -51,6 +51,7 @@
 import static android.app.NotificationChannel.PROMOTIONS_ID;
 import static android.app.NotificationChannel.RECS_ID;
 import static android.app.NotificationChannel.SOCIAL_MEDIA_ID;
+import static android.app.NotificationChannel.SYSTEM_RESERVED_IDS;
 import static android.app.NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED;
 import static android.app.NotificationManager.ACTION_AUTOMATIC_ZEN_RULE_STATUS_CHANGED;
 import static android.app.NotificationManager.ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED;
@@ -109,6 +110,7 @@
 import static android.service.notification.Adjustment.TYPE_PROMOTION;
 import static android.service.notification.Adjustment.TYPE_SOCIAL_MEDIA;
 import static android.service.notification.Flags.callstyleCallbackApi;
+import static android.service.notification.Flags.notificationClassification;
 import static android.service.notification.Flags.notificationForceGrouping;
 import static android.service.notification.Flags.redactSensitiveNotificationsBigTextStyle;
 import static android.service.notification.Flags.redactSensitiveNotificationsFromUntrustedListeners;
@@ -4405,6 +4407,15 @@
             if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channelId)) {
                 throw new IllegalArgumentException("Cannot delete default channel");
             }
+            if (notificationClassification()) {
+                // Check for all reserved channels, but do not throw because it's a common
+                // preexisting pattern for apps to (try to) delete all channels that don't match
+                //  their current desired channel structure
+                if (SYSTEM_RESERVED_IDS.contains(channelId)) {
+                    Log.v(TAG, "Package " + pkg + " cannot delete a reserved channel");
+                    return;
+                }
+            }
             enforceDeletingChannelHasNoFgService(pkg, callingUser, channelId);
             enforceDeletingChannelHasNoUserInitiatedJob(pkg, callingUser, channelId);
             cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0,
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 821722b..a4fdb75 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -22,6 +22,7 @@
 import static android.app.NotificationChannel.PROMOTIONS_ID;
 import static android.app.NotificationChannel.RECS_ID;
 import static android.app.NotificationChannel.SOCIAL_MEDIA_ID;
+import static android.app.NotificationChannel.SYSTEM_RESERVED_IDS;
 import static android.app.NotificationChannel.USER_LOCKED_IMPORTANCE;
 import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
 import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
@@ -440,6 +441,12 @@
                 channel.setImportanceLockedByCriticalDeviceFunction(
                         r.defaultAppLockedImportance || r.fixedImportance);
 
+                if (notificationClassification()) {
+                    if (SYSTEM_RESERVED_IDS.contains(id) && channel.isDeleted() ) {
+                        channel.setDeleted(false);
+                    }
+                }
+
                 if (isShortcutOk(channel) && isDeletionOk(channel)) {
                     r.channels.put(id, channel);
                 }
@@ -1023,6 +1030,11 @@
             if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channel.getId())) {
                 throw new IllegalArgumentException("Reserved id");
             }
+            // Only the user can update bundle channel settings
+            if (notificationClassification() && !fromSystemOrSystemUi
+                    && SYSTEM_RESERVED_IDS.contains(channel.getId())) {
+                return false;
+            }
             NotificationChannel existing = r.channels.get(channel.getId());
             if (existing != null && fromTargetApp) {
                 // Actually modifying an existing channel - keep most of the existing settings
diff --git a/services/core/java/com/android/server/notification/TEST_MAPPING b/services/core/java/com/android/server/notification/TEST_MAPPING
index 468c451..dc7129cd 100644
--- a/services/core/java/com/android/server/notification/TEST_MAPPING
+++ b/services/core/java/com/android/server/notification/TEST_MAPPING
@@ -1,32 +1,10 @@
 {
   "presubmit": [
     {
-      "name": "CtsNotificationTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.LargeTest"
-        }
-      ]
+      "name": "CtsNotificationTestCases_notification"
     },
     {
-      "name": "FrameworksUiServicesTests",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.LargeTest"
-        }
-      ]
+      "name": "FrameworksUiServicesTests_notification"
     }
   ],
   "postsubmit": [
diff --git a/services/core/java/com/android/server/notification/ZenModeEventLogger.java b/services/core/java/com/android/server/notification/ZenModeEventLogger.java
index b03a54e..fcc5e97 100644
--- a/services/core/java/com/android/server/notification/ZenModeEventLogger.java
+++ b/services/core/java/com/android/server/notification/ZenModeEventLogger.java
@@ -419,7 +419,7 @@
 
             if (config.automaticRules != null) {
                 for (ZenModeConfig.ZenRule rule : config.automaticRules.values()) {
-                    if (rule != null && rule.isAutomaticActive()) {
+                    if (rule != null && rule.isActive()) {
                         rules.add(rule);
                     }
                 }
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 2ada9ae4..e9db1b5 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -887,7 +887,7 @@
                 return Condition.STATE_UNKNOWN;
             }
             if (Flags.modesApi() && Flags.modesUi()) {
-                return rule.isAutomaticActive() ? STATE_TRUE : STATE_FALSE;
+                return rule.isActive() ? STATE_TRUE : STATE_FALSE;
             } else {
                 // Buggy, does not consider snoozing!
                 return rule.condition != null ? rule.condition.state : STATE_FALSE;
@@ -967,12 +967,12 @@
                 // snoozing-unsnoozing or activating-stopping.
                 if (condition.state == STATE_TRUE) {
                     rule.resetConditionOverride();
-                    if (!rule.isAutomaticActive()) {
+                    if (!rule.isActive()) {
                         rule.setConditionOverride(OVERRIDE_ACTIVATE);
                     }
                 } else if (condition.state == STATE_FALSE) {
                     rule.resetConditionOverride();
-                    if (rule.isAutomaticActive()) {
+                    if (rule.isActive()) {
                         rule.setConditionOverride(OVERRIDE_DEACTIVATE);
                     }
                 }
@@ -1609,7 +1609,7 @@
                     // 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()) {
+                        if (automaticRule.isActive()) {
                             automaticRule.setConditionOverride(OVERRIDE_DEACTIVATE);
                         }
                     }
@@ -1618,7 +1618,7 @@
                 if (zenMode == Global.ZEN_MODE_OFF) {
                     newConfig.manualRule = null;
                     for (ZenRule automaticRule : newConfig.automaticRules.values()) {
-                        if (automaticRule.isAutomaticActive()) {
+                        if (automaticRule.isActive()) {
                             automaticRule.setConditionOverride(OVERRIDE_DEACTIVATE);
                         }
                     }
@@ -1665,7 +1665,7 @@
                 mConfig.manualRule.dumpDebug(proto, ZenModeProto.ENABLED_ACTIVE_CONDITIONS);
             }
             for (ZenRule rule : mConfig.automaticRules.values()) {
-                if (rule.isAutomaticActive()) {
+                if (rule.isActive()) {
                     rule.dumpDebug(proto, ZenModeProto.ENABLED_ACTIVE_CONDITIONS);
                 }
             }
@@ -2020,9 +2020,9 @@
                             scheduleEnabledBroadcast(
                                     rule.getPkg(), config.user, rule.id, rule.enabled);
                         }
-                        if (original.isAutomaticActive() != rule.isAutomaticActive()) {
+                        if (original.isActive() != rule.isActive()) {
                             scheduleActivationBroadcast(
-                                    rule.getPkg(), config.user, rule.id, rule.isAutomaticActive());
+                                    rule.getPkg(), config.user, rule.id, rule.isActive());
                         }
                     }
                 }
@@ -2106,7 +2106,7 @@
             if (mConfig.isManualActive()) return mConfig.manualRule.zenMode;
             int zen = Global.ZEN_MODE_OFF;
             for (ZenRule automaticRule : mConfig.automaticRules.values()) {
-                if (automaticRule.isAutomaticActive()) {
+                if (automaticRule.isActive()) {
                     if (zenSeverity(automaticRule.zenMode) > zenSeverity(zen)) {
                         // automatic rule triggered dnd and user hasn't seen update dnd dialog
                         if (Settings.Secure.getInt(mContext.getContentResolver(),
@@ -2182,7 +2182,7 @@
             }
 
             for (ZenRule automaticRule : mConfig.automaticRules.values()) {
-                if (automaticRule.isAutomaticActive()) {
+                if (automaticRule.isActive()) {
                     // Active rules with INTERRUPTION_FILTER_ALL are not included in consolidated
                     // policy. This is relevant in case some other active rule has a more
                     // restrictive INTERRUPTION_FILTER but a more lenient ZenPolicy!
diff --git a/services/core/java/com/android/server/os/TEST_MAPPING b/services/core/java/com/android/server/os/TEST_MAPPING
index 50c8964..3ffcd18 100644
--- a/services/core/java/com/android/server/os/TEST_MAPPING
+++ b/services/core/java/com/android/server/os/TEST_MAPPING
@@ -2,36 +2,18 @@
   "presubmit": [
     {
       "file_patterns": ["Bugreport[^/]*\\.java"],
-      "name": "BugreportManagerTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.LargeTest"
-        }
-      ]
+      "name": "BugreportManagerTestCases_android_server_os"
     },
     {
       "file_patterns": ["Bugreport[^/]*\\.java"],
-      "name": "CtsBugreportTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.LargeTest"
-        }
-      ]
+      "name": "CtsBugreportTestCases_android_server_os"
     },
     {
       "name": "CtsUsbTests"
     },
     {
       "file_patterns": ["Bugreport[^/]*\\.java"],
-      "name": "ShellTests",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.LargeTest"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "ShellTests_android_server_os"
     }
   ],
   "postsubmit": [
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 023f765..ee15bec 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -92,6 +92,7 @@
 import android.multiuser.Flags;
 import android.net.Uri;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IInterface;
@@ -214,7 +215,7 @@
 
     @VisibleForTesting
     static class LauncherAppsImpl extends ILauncherApps.Stub {
-        private static final boolean DEBUG = false;
+        private static final boolean DEBUG = Build.IS_DEBUGGABLE;
         private static final String TAG = "LauncherAppsService";
         private static final String NAMESPACE_MULTIUSER = "multiuser";
         private static final String FLAG_NON_SYSTEM_ACCESS_TO_HIDDEN_PROFILES =
@@ -495,8 +496,28 @@
 
         private boolean canAccessProfile(int callingUid, int callingUserId, int callingPid,
                 int targetUserId, String message) {
-            if (targetUserId == callingUserId) return true;
+            if (DEBUG) {
+                final AndroidPackage callingPackage =
+                        mPackageManagerInternal.getPackage(callingUid);
+                final String callingPackageName = callingPackage == null
+                        ? null : callingPackage.getPackageName();
+                Slog.v(TAG, "canAccessProfile called by " + callingPackageName
+                        + " for user " + callingUserId
+                        + " requesting to access user "
+                        + targetUserId + " when invoking " + message);
+            }
+            if (targetUserId == callingUserId) {
+                if (DEBUG) {
+                    Slog.v(TAG, message + " passed canAccessProfile for targetuser"
+                        + targetUserId + " because it is the same as the calling user");
+                }
+                return true;
+            }
             if (injectHasInteractAcrossUsersFullPermission(callingPid, callingUid)) {
+              if (DEBUG) {
+                    Slog.v(TAG, message + " passed because calling process"
+                        + "has permission to interact across users");
+                }
                 return true;
             }
 
@@ -514,11 +535,25 @@
 
             if (isHiddenProfile(UserHandle.of(targetUserId))
                     && !canAccessHiddenProfile(callingUid, callingPid)) {
+                Slog.w(TAG, message + " for hidden profile user " + targetUserId
+                        + " from " + callingUserId + " not allowed");
+
                 return false;
             }
 
-            return mUserManagerInternal.isProfileAccessible(callingUserId, targetUserId,
-                    message, true);
+            final boolean ret = mUserManagerInternal.isProfileAccessible(
+                    callingUserId, targetUserId, message, true);
+            if (DEBUG) {
+                final AndroidPackage callingPackage =
+                        mPackageManagerInternal.getPackage(callingUid);
+                final String callingPackageName = callingPackage == null
+                        ? null : callingPackage.getPackageName();
+                Slog.v(TAG, "canAccessProfile returned " + ret + " for " + callingPackageName
+                        + " for user " + callingUserId
+                        + " requesting to access user "
+                        + targetUserId + " when invoking " + message);
+            }
+            return ret;
         }
 
         private boolean isHiddenProfile(UserHandle targetUser) {
@@ -1341,6 +1376,10 @@
         @Override
         public void pinShortcuts(String callingPackage, String packageName, List<String> ids,
                 UserHandle targetUser) {
+            if (DEBUG) {
+                Slog.v(TAG, "pinShortcuts: " + callingPackage + " is pinning shortcuts from "
+                        + packageName + " for user " + targetUser);
+            }
             if (!mShortcutServiceInternal
                     .areShortcutsSupportedOnHomeScreen(targetUser.getIdentifier())) {
                 // Requires strict ACCESS_SHORTCUTS permission for user-profiles with items
@@ -1351,6 +1390,11 @@
             }
             ensureShortcutPermission(callingPackage);
             if (!canAccessProfile(targetUser.getIdentifier(), "Cannot pin shortcuts")) {
+                if (DEBUG) {
+                    Slog.v(TAG, "pinShortcuts: " + callingPackage
+                            + " is pinning shortcuts from " + packageName
+                            + " for user " + targetUser + " but cannot access profile");
+                }
                 return;
             }
 
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index be6fa14..1316df1 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -856,8 +856,9 @@
                     params.appPackageName, SYSTEM_UID);
             if (ps != null
                     && PackageArchiver.isArchived(ps.getUserStateOrDefault(userId))
-                    && PackageArchiver.getResponsibleInstallerPackage(ps)
-                            .equals(requestedInstallerPackageName)) {
+                    && TextUtils.equals(
+                        PackageArchiver.getResponsibleInstallerPackage(ps),
+                        requestedInstallerPackageName)) {
                 params.installFlags |= PackageManager.INSTALL_UNARCHIVE;
             }
         }
diff --git a/services/core/java/com/android/server/pm/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java
index 045d4db..d65e30b 100644
--- a/services/core/java/com/android/server/pm/ShortcutLauncher.java
+++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java
@@ -42,6 +42,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * Launcher information used by {@link ShortcutService}.
@@ -128,9 +129,15 @@
      */
     public void pinShortcuts(@UserIdInt int packageUserId,
             @NonNull String packageName, @NonNull List<String> ids, boolean forPinRequest) {
+        if (ShortcutService.DEBUG) {
+            Slog.v(TAG, "ShortcutLauncher#pinShortcuts: pin shortcuts from " + packageName
+                    + " with userId=" + packageUserId + " shortcutIds="
+                    + ids.stream().collect(Collectors.joining(", ", "[", "]")));
+        }
         final ShortcutPackage packageShortcuts =
                 mShortcutUser.getPackageShortcutsIfExists(packageName);
         if (packageShortcuts == null) {
+            Slog.w(TAG, "ShortcutLauncher#pinShortcuts packageShortcuts is null");
             return; // No need to instantiate.
         }
 
@@ -155,6 +162,10 @@
                 final String id = ids.get(i);
                 final ShortcutInfo si = packageShortcuts.findShortcutById(id);
                 if (si == null) {
+                    if (ShortcutService.DEBUG) {
+                        Slog.w(TAG, "ShortcutLauncher#pinShortcuts: cannot pin "
+                                + id + " because it does not exist");
+                    }
                     continue;
                 }
                 if (si.isDynamic() || si.isLongLived()
@@ -174,6 +185,13 @@
                         }
                     }
                 }
+                if (ShortcutService.DEBUG) {
+                    Slog.v(TAG, "ShortcutLauncher#pinShortcuts: "
+                            + " newSet: " + newSet.stream().collect(
+                                    Collectors.joining(", ", "[", "]"))
+                            + " floatingSet: " + floatingSet.stream().collect(
+                                    Collectors.joining(", ", "[", "]")));
+                }
                 mPinnedShortcuts.put(up, newSet);
             }
         }
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 60056eb..c9ad498 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -729,6 +729,11 @@
             }
             pinnedShortcuts.addAll(pinned);
         });
+        if (ShortcutService.DEBUG) {
+            Slog.v(TAG, "ShortcutPackage#refreshPinnedFlags: "
+                    + " pinnedShortcuts: " + pinnedShortcuts.stream().collect(
+                            Collectors.joining(", ", "[", "]")));
+        }
         // Secondly, update the pinned state if necessary.
         final List<ShortcutInfo> pinned = findAll(pinnedShortcuts);
         if (pinned != null) {
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index a3ff195..ea495c9 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -169,7 +169,7 @@
 public class ShortcutService extends IShortcutService.Stub {
     static final String TAG = "ShortcutService";
 
-    static final boolean DEBUG = false; // STOPSHIP if true
+    static final boolean DEBUG = Build.IS_DEBUGGABLE; // STOPSHIP if true
     static final boolean DEBUG_LOAD = false; // STOPSHIP if true
     static final boolean DEBUG_PROCSTATE = false; // STOPSHIP if true
     static final boolean DEBUG_REBOOT = Build.IS_DEBUGGABLE;
@@ -3206,6 +3206,11 @@
         public void pinShortcuts(int launcherUserId,
                 @NonNull String callingPackage, @NonNull String packageName,
                 @NonNull List<String> shortcutIds, int userId) {
+            if (DEBUG) {
+                Slog.v(TAG, "pinShortcuts: " + callingPackage + ", with userId=" + launcherUserId
+                        + ", is trying to pin shortcuts from " + packageName
+                        + " with userId=" + userId);
+            }
             // Calling permission must be checked by LauncherAppsImpl.
             Preconditions.checkStringNotEmpty(packageName, "packageName");
             Objects.requireNonNull(shortcutIds, "shortcutIds");
@@ -3230,6 +3235,11 @@
                                     && !si.isDeclaredInManifest(),
                             ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO,
                             callingPackage, launcherUserId, false);
+                } else {
+                    if (DEBUG) {
+                        Slog.w(TAG, "specified package " + packageName + ", with userId=" + userId
+                        + ", doesn't exist.");
+                    }
                 }
                 // Get list of shortcuts that will get unpinned.
                 ArraySet<String> oldPinnedIds = launcher.getPinnedShortcutIds(packageName, userId);
@@ -5448,6 +5458,17 @@
      */
     private List<ShortcutInfo> prepareChangedShortcuts(ArraySet<String> changedIds,
             ArraySet<String> newIds, List<ShortcutInfo> deletedList, final ShortcutPackage ps) {
+        if (DEBUG) {
+            Slog.v(TAG, "prepareChangedShortcuts: "
+                + " changedIds=" + (changedIds == null
+                        ? "n/a" : changedIds.stream().collect(Collectors.joining(", ", "[", "]")))
+                + " newIds=" + (newIds == null
+                        ? "n/a" : newIds.stream().collect(Collectors.joining(", ", "[", "]")))
+                + " deletedList=" + (deletedList == null
+                        ? "n/a" : deletedList.stream().map(ShortcutInfo::getId).collect(
+                                Collectors.joining(", ", "[", "]")))
+                + " ps=" + (ps == null ? "n/a" : ps.getPackageName()));
+        }
         if (ps == null) {
             // This can happen when package restore is not finished yet.
             return null;
diff --git a/services/core/java/com/android/server/pm/verify/domain/TEST_MAPPING b/services/core/java/com/android/server/pm/verify/domain/TEST_MAPPING
index 8a1982a..db98c40 100644
--- a/services/core/java/com/android/server/pm/verify/domain/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/verify/domain/TEST_MAPPING
@@ -1,12 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "PackageManagerServiceUnitTests",
-      "options": [
-        {
-          "include-filter": "com.android.server.pm.test.verify.domain"
-        }
-      ]
+      "name": "PackageManagerServiceUnitTests_verify_domain"
     },
     {
       "name": "CtsDomainVerificationDeviceStandaloneTestCases"
diff --git a/services/core/java/com/android/server/policy/TEST_MAPPING b/services/core/java/com/android/server/policy/TEST_MAPPING
index bdb174d..76a0503 100644
--- a/services/core/java/com/android/server/policy/TEST_MAPPING
+++ b/services/core/java/com/android/server/policy/TEST_MAPPING
@@ -1,32 +1,10 @@
 {
   "presubmit": [
     {
-      "name": "FrameworksServicesTests",
-      "options": [
-        {
-          "include-filter": "com.android.server.policy."
-        },
-        {
-          "include-annotation": "android.platform.test.annotations.Presubmit"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "FrameworksServicesTests_android_server_policy_Presubmit"
     },
     {
-      "name": "WmTests",
-      "options": [
-        {
-          "include-filter": "com.android.server.policy."
-        },
-        {
-          "include-annotation": "android.platform.test.annotations.Presubmit"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "WmTests_server_policy_Presubmit"
     },
     {
       "name": "CtsPermissionPolicyTestCases",
@@ -49,30 +27,15 @@
       "name": "CtsPermissionTestCases_Platform"
     },
     {
-      "name": "CtsBackupTestCases",
-      "options": [
-        {
-          "include-filter": "android.backup.cts.PermissionTest"
-        }
-      ]
+      "name": "CtsBackupTestCases_cts_permissiontest"
     }
   ],
   "postsubmit": [
     {
-      "name": "FrameworksServicesTests",
-      "options": [
-        {
-          "include-filter": "com.android.server.policy."
-        }
-      ]
+      "name": "FrameworksServicesTests_android_server_policy"
     },
     {
-      "name": "WmTests",
-      "options": [
-        {
-          "include-filter": "com.android.server.policy."
-        }
-      ]
+      "name": "WmTests_server_policy"
     },
     {
       "name": "CtsPermissionPolicyTestCases",
diff --git a/services/core/java/com/android/server/power/TEST_MAPPING b/services/core/java/com/android/server/power/TEST_MAPPING
index 935a238..f67f56d 100644
--- a/services/core/java/com/android/server/power/TEST_MAPPING
+++ b/services/core/java/com/android/server/power/TEST_MAPPING
@@ -1,22 +1,13 @@
 {
   "presubmit": [
     {
-      "name": "CtsBatterySavingTestCases",
-      "options": [
-        {"exclude-annotation": "androidx.test.filters.FlakyTest"},
-        {"exclude-annotation": "androidx.test.filters.LargeTest"}
-      ]
+      "name": "CtsBatterySavingTestCases_android_server_power"
     },
     {
       "name": "FrameworksMockingServicesTests_android_server_power_Presubmit"
     },
     {
-      "name": "PowerServiceTests",
-      "options": [
-        {"include-filter": "com.android.server.power"},
-        {"exclude-annotation": "androidx.test.filters.FlakyTest"},
-        {"exclude-annotation": "org.junit.Ignore"}
-      ]
+      "name": "PowerServiceTests_server_power"
     }
   ],
   "postsubmit": [
@@ -24,28 +15,16 @@
       "name": "CtsBatterySavingTestCases"
     },
     {
-      "name": "FrameworksMockingServicesTests",
-      "options": [
-        {"include-filter": "com.android.server.power"}
-      ]
+      "name": "FrameworksMockingServicesTests_android_server_power"
     },
     {
       "name": "FrameworksServicesTests_android_server_power"
     },
     {
-      "name": "PowerServiceTests",
-      "options": [
-        {"include-filter": "com.android.server.power"},
-        {"exclude-annotation": "org.junit.Ignore"}
-      ]
+      "name": "PowerServiceTests_server_power"
     },
     {
-      "name": "CtsStatsdAtomHostTestCases",
-      "options": [
-        {"exclude-annotation": "androidx.test.filters.FlakyTest"},
-        {"exclude-annotation": "org.junit.Ignore"},
-        {"include-filter": "android.cts.statsdatom.powermanager"}
-      ],
+      "name": "CtsStatsdAtomHostTestCases_statsdatom_powermanager",
       "file_patterns": [
         "(/|^)ThermalManagerService.java"
       ]
diff --git a/services/core/java/com/android/server/power/ThermalManagerService.java b/services/core/java/com/android/server/power/ThermalManagerService.java
index 6847a5c..dc6b164 100644
--- a/services/core/java/com/android/server/power/ThermalManagerService.java
+++ b/services/core/java/com/android/server/power/ThermalManagerService.java
@@ -1628,9 +1628,9 @@
         long mInactivityThresholdMillis = INACTIVITY_THRESHOLD_MILLIS;
 
         void updateThresholds() {
-            synchronized (mSamples) {
-                List<TemperatureThreshold> thresholds =
+            List<TemperatureThreshold> thresholds =
                         mHalWrapper.getTemperatureThresholds(true, Temperature.TYPE_SKIN);
+            synchronized (mSamples) {
                 if (Flags.allowThermalHeadroomThresholds()) {
                     Arrays.fill(mHeadroomThresholds, Float.NaN);
                 }
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 680b1ac..cb8e1a0 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -5031,9 +5031,7 @@
         if (mPretendScreenOff != pretendScreenOff) {
             mPretendScreenOff = pretendScreenOff;
             final int primaryScreenState = mPerDisplayBatteryStats[0].screenState;
-            noteScreenStateLocked(0, primaryScreenState,
-                    mClock.elapsedRealtime(), mClock.uptimeMillis(),
-                    mClock.currentTimeMillis());
+            noteScreenStateLocked(0, primaryScreenState);
         }
     }
 
@@ -5554,15 +5552,29 @@
         }
     }
 
+    private static String getScreenStateTag(
+            int display, int state, @Display.StateReason int reason) {
+        return String.format(
+                "display=%d state=%s reason=%s",
+                display, Display.stateToString(state), Display.stateReasonToString(reason));
+    }
+
     @GuardedBy("this")
     public void noteScreenStateLocked(int display, int state) {
-        noteScreenStateLocked(display, state, mClock.elapsedRealtime(), mClock.uptimeMillis(),
-                mClock.currentTimeMillis());
+        noteScreenStateLocked(display, state, Display.STATE_REASON_UNKNOWN,
+                mClock.elapsedRealtime(), mClock.uptimeMillis(), mClock.currentTimeMillis());
     }
 
     @GuardedBy("this")
     public void noteScreenStateLocked(int display, int displayState,
-            long elapsedRealtimeMs, long uptimeMs, long currentTimeMs) {
+            @Display.StateReason int displayStateReason, long elapsedRealtimeMs, long uptimeMs,
+            long currentTimeMs) {
+        if (Flags.batteryStatsScreenStateEvent()) {
+            mHistory.recordEvent(
+                    elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_DISPLAY_STATE_CHANGED,
+                    getScreenStateTag(display, displayState, displayStateReason),
+                    Process.INVALID_UID);
+        }
         // Battery stats relies on there being 4 states. To accommodate this, new states beyond the
         // original 4 are mapped to one of the originals.
         if (displayState > MAX_TRACKED_SCREEN_STATE) {
diff --git a/services/core/java/com/android/server/power/stats/flags.aconfig b/services/core/java/com/android/server/power/stats/flags.aconfig
index cc0a283..05d29f5 100644
--- a/services/core/java/com/android/server/power/stats/flags.aconfig
+++ b/services/core/java/com/android/server/power/stats/flags.aconfig
@@ -68,3 +68,11 @@
     description: "Disable deprecated BatteryUsageStatsAtom pulled atom"
     bug: "324602949"
 }
+
+flag {
+    name: "battery_stats_screen_state_event"
+    namespace: "backstage_power"
+    description: "Guards the battery stats event for screen state changes."
+    bug: "364350206"
+    is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java b/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java
index 945a340..41e3d00 100644
--- a/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java
+++ b/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java
@@ -17,6 +17,7 @@
 package com.android.server.security;
 
 import static android.security.attestationverification.AttestationVerificationManager.PARAM_CHALLENGE;
+import static android.security.attestationverification.AttestationVerificationManager.PARAM_MAX_PATCH_LEVEL_DIFF_MONTHS;
 import static android.security.attestationverification.AttestationVerificationManager.PARAM_PUBLIC_KEY;
 import static android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE;
 import static android.security.attestationverification.AttestationVerificationManager.RESULT_SUCCESS;
@@ -174,8 +175,8 @@
 
         MyDumpData dumpData = new MyDumpData();
 
-        int result =
-                verifyAttestationInternal(localBindingType, requirements, attestation, dumpData);
+        int result = verifyAttestationInternal(localBindingType, requirements, attestation,
+                dumpData);
         dumpData.mResult = result;
         mDumpLogger.logAttempt(dumpData);
         return result;
@@ -222,7 +223,8 @@
             final var attestationExtension = fromCertificate(leafCertificate);
 
             // Second: verify if the attestation satisfies the "peer device" profile.
-            if (!checkAttestationForPeerDeviceProfile(attestationExtension, dumpData)) {
+            if (!checkAttestationForPeerDeviceProfile(requirements, attestationExtension,
+                    dumpData)) {
                 failed = true;
             }
 
@@ -400,6 +402,7 @@
     }
 
     private boolean checkAttestationForPeerDeviceProfile(
+            @NonNull Bundle requirements,
             @NonNull AndroidKeystoreAttestationVerificationAttributes attestationAttributes,
             MyDumpData dumpData) {
         boolean result = true;
@@ -461,30 +464,37 @@
             result = false;
         }
 
-        // Patch level integer YYYYMM is expected to be within 1 year of today.
-        if (!isValidPatchLevel(attestationAttributes.getKeyOsPatchLevel())) {
+        int maxPatchLevelDiffMonths = requirements.getInt(PARAM_MAX_PATCH_LEVEL_DIFF_MONTHS,
+                MAX_PATCH_AGE_MONTHS);
+
+        // Patch level integer YYYYMM is expected to be within maxPatchLevelDiffMonths of today.
+        if (!isValidPatchLevel(attestationAttributes.getKeyOsPatchLevel(),
+                maxPatchLevelDiffMonths)) {
             debugVerboseLog("OS patch level is not within valid range.");
             result = false;
         } else {
             dumpData.mOsPatchLevelInRange = true;
         }
 
-        // Patch level integer YYYYMMDD is expected to be within 1 year of today.
-        if (!isValidPatchLevel(attestationAttributes.getKeyBootPatchLevel())) {
+        // Patch level integer YYYYMMDD is expected to be within maxPatchLevelDiffMonths of today.
+        if (!isValidPatchLevel(attestationAttributes.getKeyBootPatchLevel(),
+                maxPatchLevelDiffMonths)) {
             debugVerboseLog("Boot patch level is not within valid range.");
             result = false;
         } else {
             dumpData.mKeyBootPatchLevelInRange = true;
         }
 
-        if (!isValidPatchLevel(attestationAttributes.getKeyVendorPatchLevel())) {
+        if (!isValidPatchLevel(attestationAttributes.getKeyVendorPatchLevel(),
+                maxPatchLevelDiffMonths)) {
             debugVerboseLog("Vendor patch level is not within valid range.");
             result = false;
         } else {
             dumpData.mKeyVendorPatchLevelInRange = true;
         }
 
-        if (!isValidPatchLevel(attestationAttributes.getKeyBootPatchLevel())) {
+        if (!isValidPatchLevel(attestationAttributes.getKeyBootPatchLevel(),
+                maxPatchLevelDiffMonths)) {
             debugVerboseLog("Boot patch level is not within valid range.");
             result = false;
         } else {
@@ -525,7 +535,7 @@
      * is not enough. Therefore, we also confirm the patch level for the remote and local device are
      * similar.
      */
-    private boolean isValidPatchLevel(int patchLevel) {
+    private boolean isValidPatchLevel(int patchLevel, int maxPatchLevelDiffMonths) {
         LocalDate currentDate = mTestSystemDate != null
                 ? mTestSystemDate : LocalDate.now(ZoneId.systemDefault());
 
@@ -543,7 +553,9 @@
             return false;
         }
 
-        // Check local patch date is not in last year of system clock.
+        // Check local patch date is not in last year of system clock. If the local patch already
+        // has a year's worth of bugs and vulnerabilities, it has no security meanings to check the
+        // remote patch level.
         if (ChronoUnit.MONTHS.between(localPatchDate, currentDate) > MAX_PATCH_AGE_MONTHS) {
             return true;
         }
@@ -559,19 +571,9 @@
         int patchMonth = Integer.parseInt(remoteDeviceDateStr.substring(4, 6));
         LocalDate remotePatchDate = LocalDate.of(patchYear, patchMonth, 1);
 
-        // Check patch dates are within 1 year of each other
-        boolean IsRemotePatchWithinOneYearOfLocalPatch;
-        if (remotePatchDate.compareTo(localPatchDate) > 0) {
-            IsRemotePatchWithinOneYearOfLocalPatch = ChronoUnit.MONTHS.between(
-                    localPatchDate, remotePatchDate) <= MAX_PATCH_AGE_MONTHS;
-        } else if (remotePatchDate.compareTo(localPatchDate) < 0) {
-            IsRemotePatchWithinOneYearOfLocalPatch = ChronoUnit.MONTHS.between(
-                    remotePatchDate, localPatchDate) <= MAX_PATCH_AGE_MONTHS;
-        } else {
-            IsRemotePatchWithinOneYearOfLocalPatch = true;
-        }
-
-        return IsRemotePatchWithinOneYearOfLocalPatch;
+        // Check patch dates are within the max patch level diff of each other
+        return Math.abs(ChronoUnit.MONTHS.between(localPatchDate, remotePatchDate))
+                <= maxPatchLevelDiffMonths;
     }
 
     /**
diff --git a/services/core/java/com/android/server/security/TEST_MAPPING b/services/core/java/com/android/server/security/TEST_MAPPING
index 29d52ff..284e08e 100644
--- a/services/core/java/com/android/server/security/TEST_MAPPING
+++ b/services/core/java/com/android/server/security/TEST_MAPPING
@@ -1,12 +1,7 @@
 {
     "presubmit": [
         {
-            "name": "CtsSecurityTestCases",
-            "options": [
-                {
-                    "include-filter": "android.security.cts.FileIntegrityManagerTest"
-                }
-            ],
+            "name": "CtsSecurityTestCases_cts_fileintegritymanagertest",
             "file_patterns": ["FileIntegrity[^/]*\\.java"]
         }
     ]
diff --git a/services/core/java/com/android/server/statusbar/TEST_MAPPING b/services/core/java/com/android/server/statusbar/TEST_MAPPING
index 67ea557..8c7e74c 100644
--- a/services/core/java/com/android/server/statusbar/TEST_MAPPING
+++ b/services/core/java/com/android/server/statusbar/TEST_MAPPING
@@ -1,29 +1,10 @@
 {
   "presubmit": [
     {
-      "name": "CtsTileServiceTestCases",
-      "options": [
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsTileServiceTestCases"
     },
     {
-      "name": "CtsAppTestCases",
-      "options": [
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "include-filter": "android.app.cts.RequestTileServiceAddTest"
-        }
-      ]
+      "name": "CtsAppTestCases_cts_requesttileserviceaddtest"
     }
   ]
 }
\ No newline at end of file
diff --git a/services/core/java/com/android/server/timedetector/TEST_MAPPING b/services/core/java/com/android/server/timedetector/TEST_MAPPING
index 17d327e..f57b819 100644
--- a/services/core/java/com/android/server/timedetector/TEST_MAPPING
+++ b/services/core/java/com/android/server/timedetector/TEST_MAPPING
@@ -1,12 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "CtsTimeTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsTimeTestCases"
     },
     {
       "name": "FrameworksTimeServicesTests"
diff --git a/services/core/java/com/android/server/timezonedetector/TEST_MAPPING b/services/core/java/com/android/server/timezonedetector/TEST_MAPPING
index 004d7996..a237c34 100644
--- a/services/core/java/com/android/server/timezonedetector/TEST_MAPPING
+++ b/services/core/java/com/android/server/timezonedetector/TEST_MAPPING
@@ -1,12 +1,7 @@
 {
   "presubmit": [
    {
-      "name": "CtsTimeTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsTimeTestCases"
     },
     {
       "name": "FrameworksTimeServicesTests"
diff --git a/services/core/java/com/android/server/trust/TEST_MAPPING b/services/core/java/com/android/server/trust/TEST_MAPPING
index 0de7c28..4c08455 100644
--- a/services/core/java/com/android/server/trust/TEST_MAPPING
+++ b/services/core/java/com/android/server/trust/TEST_MAPPING
@@ -1,41 +1,17 @@
 {
     "presubmit": [
       {
-        "name": "TrustTests",
-        "options": [
-          {
-            "include-filter": "android.trust.test"
-          },
-          {
-            "exclude-annotation": "androidx.test.filters.FlakyTest"
-          }
-        ]
+        "name": "TrustTests_trust_test"
       }
     ],
     "postsubmit": [
       {
-        "name": "FrameworksMockingServicesTests",
-        "options": [
-          {
-            "include-filter": "com.android.server.trust"
-          },
-          {
-            "exclude-annotation": "androidx.test.filters.FlakyTest"
-          }
-        ]
+        "name": "FrameworksMockingServicesTests_android_server_trust"
       }
     ],
     "trust-tablet": [
       {
-        "name": "TrustTests",
-        "options": [
-          {
-            "include-filter": "android.trust.test"
-          },
-          {
-            "exclude-annotation": "androidx.test.filters.FlakyTest"
-          }
-        ]
+        "name": "TrustTests_trust_test"
       }
     ]
   }
\ No newline at end of file
diff --git a/services/core/java/com/android/server/uri/TEST_MAPPING b/services/core/java/com/android/server/uri/TEST_MAPPING
index 0d756bb..45e3051 100644
--- a/services/core/java/com/android/server/uri/TEST_MAPPING
+++ b/services/core/java/com/android/server/uri/TEST_MAPPING
@@ -4,24 +4,7 @@
             "name": "FrameworksServicesTests_android_server_uri"
         },
         {
-            "name": "CtsStorageHostTestCases",
-            "options": [
-                {
-                    "include-filter": "android.appsecurity.cts.ExternalStorageHostTest#testGrantUriPermission"
-                },
-                {
-                    "include-filter": "android.appsecurity.cts.ExternalStorageHostTest#testGrantUriPermission29"
-                },
-                {
-                    "include-filter": "android.appsecurity.cts.ExternalStorageHostTest#testMediaNone"
-                },
-                {
-                    "include-filter": "android.appsecurity.cts.ExternalStorageHostTest#testMediaNone28"
-                },
-                {
-                    "include-filter": "android.appsecurity.cts.ExternalStorageHostTest#testMediaNone29"
-                }
-            ]
+            "name": "CtsStorageHostTestCases_android_server_uri"
         }
     ],
     "postsubmit": [
@@ -34,12 +17,7 @@
             ]
         },
         {
-            "name": "CtsWindowManagerDeviceWindow",
-            "options": [
-                {
-                    "include-filter": "android.server.wm.window.CrossAppDragAndDropTests"
-                }
-            ]
+            "name": "CtsWindowManagerDeviceWindow_window_crossappdraganddroptests"
         }
     ]
 }
diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java b/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java
index 195e91c..49825f1 100644
--- a/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java
+++ b/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java
@@ -64,13 +64,36 @@
             String targetPkg, int targetUserId);
 
     /**
-     * Same as {@link #checkGrantUriPermissionFromIntent(Intent, int, String, int)}, but with an
-     * extra parameter {@code requireContentUriPermissionFromCaller}, which is the value from {@link
-     * android.R.attr#requireContentUriPermissionFromCaller} attribute.
+     * Same as {@link #checkGrantUriPermissionFromIntent(Intent, int, String, int)}, but with:
+     * - {@code requireContentUriPermissionFromCaller}, which is the value from {@link
+     *   android.R.attr#requireContentUriPermissionFromCaller} attribute.
+     * - {@code requestHashCode}, which is required to differentiate activity launches for logging
+     *   ContentOrFileUriEventReported message.
      */
     NeededUriGrants checkGrantUriPermissionFromIntent(Intent intent, int callingUid,
             String targetPkg, int targetUserId,
-            @RequiredContentUriPermission int requireContentUriPermissionFromCaller);
+            @RequiredContentUriPermission int requireContentUriPermissionFromCaller,
+            int requestHashCode);
+
+    /**
+     * Notify that an activity launch request has been completed and perform the following actions:
+     * - If the activity launch was unsuccessful, then clean up all the collected the content URIs
+     *   that were passed during that launch.
+     * - If the activity launch was successful, then log cog content URIs that were passed during
+     *   that launch. Specifically:
+     *   - The caller didn't have read permission to them.
+     *   - The activity's {@link android.R.attr#requireContentUriPermissionFromCaller} was set to
+     *     "none".
+     *
+     * <p>Note that:
+     * - The API has to be called after
+     *   {@link #checkGrantUriPermissionFromIntent(Intent, int, String, int, int, int)} was called.
+     * - The API is not idempotent, i.e. content URIs may be logged only once because the API clears
+     *   the content URIs after logging.
+     */
+    void notifyActivityLaunchRequestCompleted(int requestHashCode, boolean isSuccessfulLaunch,
+            String intentAction, int callingUid, String callingActivityName, int calleeUid,
+            String calleeActivityName, boolean isStartActivityForResult);
 
     /**
      * Extend a previously calculated set of permissions grants to the given
diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
index a581b08..3479b6c 100644
--- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java
+++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
@@ -24,6 +24,7 @@
 import static android.content.Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION;
 import static android.content.Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
 import static android.content.pm.ActivityInfo.CONTENT_URI_PERMISSION_NONE;
+import static android.content.pm.ActivityInfo.CONTENT_URI_PERMISSION_READ;
 import static android.content.pm.ActivityInfo.CONTENT_URI_PERMISSION_READ_OR_WRITE;
 import static android.content.pm.ActivityInfo.isRequiredContentUriPermissionRead;
 import static android.content.pm.ActivityInfo.isRequiredContentUriPermissionWrite;
@@ -39,6 +40,8 @@
 import static android.os.Process.myUid;
 
 import static com.android.internal.util.XmlUtils.writeBooleanAttribute;
+import static com.android.internal.util.FrameworkStatsLog.CONTENT_OR_FILE_URI_EVENT_REPORTED;
+import static com.android.internal.util.FrameworkStatsLog.CONTENT_OR_FILE_URI_EVENT_REPORTED__EVENT_TYPE__CONTENT_URI_WITHOUT_CALLER_READ_PERMISSION;
 import static com.android.server.uri.UriGrantsManagerService.H.PERSIST_URI_GRANTS_MSG;
 
 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
@@ -78,6 +81,7 @@
 import android.provider.Downloads;
 import android.text.format.DateUtils;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -86,6 +90,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.Preconditions;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
@@ -153,6 +158,22 @@
     private final SparseArray<ArrayMap<GrantUri, UriPermission>>
             mGrantedUriPermissions = new SparseArray<>();
 
+    /**
+     * Global map of activity launches to sets of passed content URIs. Specifically:
+     * - The caller didn't have read permission to them.
+     * - The callee activity's {@link android.R.attr#requireContentUriPermissionFromCaller} was set
+     *   to "none".
+     *
+     * <p>This map is used for logging the ContentOrFileUriEventReported message.
+     *
+     * <p>The launch id is the ActivityStarter.Request#hashCode and has to be received from
+     * ActivityStarter to {@link #checkGrantUriPermissionFromIntentUnlocked(int, String, Intent,
+     * int, NeededUriGrants, int, Integer, Integer)}.
+     */
+    @GuardedBy("mLaunchToContentUrisWithoutCallerReadPermission")
+    private final SparseArray<ArraySet<Uri>> mLaunchToContentUrisWithoutCallerReadPermission =
+            new SparseArray<>();
+
     private UriGrantsManagerService() {
         this(SystemServiceManager.ensureSystemDir(), "uri-grants");
     }
@@ -613,7 +634,8 @@
     /** Like checkGrantUriPermission, but takes an Intent. */
     private NeededUriGrants checkGrantUriPermissionFromIntentUnlocked(int callingUid,
             String targetPkg, Intent intent, int mode, NeededUriGrants needed, int targetUserId,
-            @RequiredContentUriPermission Integer requireContentUriPermissionFromCaller) {
+            @RequiredContentUriPermission Integer requireContentUriPermissionFromCaller,
+            Integer requestHashCode) {
         if (DEBUG) Slog.v(TAG,
                 "Checking URI perm to data=" + (intent != null ? intent.getData() : null)
                         + " clip=" + (intent != null ? intent.getClipData() : null)
@@ -635,8 +657,9 @@
         }
 
         if (android.security.Flags.contentUriPermissionApis()) {
-            enforceRequireContentUriPermissionFromCallerOnIntentExtraStream(intent, contentUserHint,
-                    mode, callingUid, requireContentUriPermissionFromCaller);
+            enforceRequireContentUriPermissionFromCallerOnIntentExtraStreamUnlocked(intent,
+                    contentUserHint, mode, callingUid, requireContentUriPermissionFromCaller,
+                    requestHashCode);
         }
 
         Uri data = intent.getData();
@@ -660,8 +683,9 @@
         if (data != null) {
             GrantUri grantUri = GrantUri.resolve(contentUserHint, data, mode);
             if (android.security.Flags.contentUriPermissionApis()) {
-                enforceRequireContentUriPermissionFromCaller(requireContentUriPermissionFromCaller,
-                        grantUri, callingUid);
+                enforceRequireContentUriPermissionFromCallerUnlocked(
+                        requireContentUriPermissionFromCaller, grantUri, callingUid,
+                        requestHashCode);
             }
             targetUid = checkGrantUriPermissionUnlocked(callingUid, targetPkg, grantUri, mode,
                     targetUid);
@@ -678,8 +702,9 @@
                 if (uri != null) {
                     GrantUri grantUri = GrantUri.resolve(contentUserHint, uri, mode);
                     if (android.security.Flags.contentUriPermissionApis()) {
-                        enforceRequireContentUriPermissionFromCaller(
-                                requireContentUriPermissionFromCaller, grantUri, callingUid);
+                        enforceRequireContentUriPermissionFromCallerUnlocked(
+                                requireContentUriPermissionFromCaller, grantUri, callingUid,
+                                requestHashCode);
                     }
                     targetUid = checkGrantUriPermissionUnlocked(callingUid, targetPkg,
                             grantUri, mode, targetUid);
@@ -694,7 +719,7 @@
                     if (clipIntent != null) {
                         NeededUriGrants newNeeded = checkGrantUriPermissionFromIntentUnlocked(
                                 callingUid, targetPkg, clipIntent, mode, needed, targetUserId,
-                                requireContentUriPermissionFromCaller);
+                                requireContentUriPermissionFromCaller, requestHashCode);
                         if (newNeeded != null) {
                             needed = newNeeded;
                         }
@@ -706,17 +731,32 @@
         return needed;
     }
 
-    private void enforceRequireContentUriPermissionFromCaller(
+    private void enforceRequireContentUriPermissionFromCallerUnlocked(
             @RequiredContentUriPermission Integer requireContentUriPermissionFromCaller,
-            GrantUri grantUri, int uid) {
-        // Ignore if requireContentUriPermissionFromCaller hasn't been set or the URI is a
+            GrantUri grantUri, int callingUid, Integer requestHashCode) {
+        // Exit early if requireContentUriPermissionFromCaller hasn't been set or the URI is a
         // non-content URI.
         if (requireContentUriPermissionFromCaller == null
                 || requireContentUriPermissionFromCaller == CONTENT_URI_PERMISSION_NONE
                 || !ContentResolver.SCHEME_CONTENT.equals(grantUri.uri.getScheme())) {
+            tryAddingContentUriWithoutCallerReadPermissionWhenAttributeIsNoneUnlocked(
+                    requireContentUriPermissionFromCaller, grantUri, callingUid, requestHashCode);
             return;
         }
 
+        final boolean hasPermission = hasRequireContentUriPermissionFromCallerUnlocked(
+                requireContentUriPermissionFromCaller, grantUri, callingUid);
+
+        if (!hasPermission) {
+            throw new SecurityException("You can't launch this activity because you don't have the"
+                    + " required " + ActivityInfo.requiredContentUriPermissionToShortString(
+                            requireContentUriPermissionFromCaller) + " access to " + grantUri.uri);
+        }
+    }
+
+    private boolean hasRequireContentUriPermissionFromCallerUnlocked(
+            @RequiredContentUriPermission Integer requireContentUriPermissionFromCaller,
+            GrantUri grantUri, int uid) {
         final boolean readMet = !isRequiredContentUriPermissionRead(
                 requireContentUriPermissionFromCaller)
                 || checkContentUriPermissionFullUnlocked(grantUri, uid,
@@ -727,26 +767,48 @@
                 || checkContentUriPermissionFullUnlocked(grantUri, uid,
                 Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
 
-        boolean hasPermission =
-                requireContentUriPermissionFromCaller == CONTENT_URI_PERMISSION_READ_OR_WRITE
-                        ? (readMet || writeMet) : (readMet && writeMet);
+        return requireContentUriPermissionFromCaller == CONTENT_URI_PERMISSION_READ_OR_WRITE
+                ? (readMet || writeMet) : (readMet && writeMet);
+    }
 
-        if (!hasPermission) {
-            throw new SecurityException("You can't launch this activity because you don't have the"
-                    + " required " + ActivityInfo.requiredContentUriPermissionToShortString(
-                            requireContentUriPermissionFromCaller) + " access to " + grantUri.uri);
+    private void tryAddingContentUriWithoutCallerReadPermissionWhenAttributeIsNoneUnlocked(
+            @RequiredContentUriPermission Integer requireContentUriPermissionFromCaller,
+            GrantUri grantUri, int callingUid, Integer requestHashCode) {
+        // We're interested in requireContentUriPermissionFromCaller that is set to
+        // CONTENT_URI_PERMISSION_NONE and content URIs. Hence, ignore if
+        // requireContentUriPermissionFromCaller is not set to CONTENT_URI_PERMISSION_NONE or the
+        // URI is a non-content URI.
+        if (requireContentUriPermissionFromCaller == null
+                || requireContentUriPermissionFromCaller != CONTENT_URI_PERMISSION_NONE
+                || !ContentResolver.SCHEME_CONTENT.equals(grantUri.uri.getScheme())
+                || requestHashCode == null) {
+            return;
+        }
+
+        if (!hasRequireContentUriPermissionFromCallerUnlocked(CONTENT_URI_PERMISSION_READ, grantUri,
+                callingUid)) {
+            synchronized (mLaunchToContentUrisWithoutCallerReadPermission) {
+                if (mLaunchToContentUrisWithoutCallerReadPermission.get(requestHashCode) == null) {
+                    mLaunchToContentUrisWithoutCallerReadPermission
+                            .put(requestHashCode, new ArraySet<>());
+                }
+                mLaunchToContentUrisWithoutCallerReadPermission.get(requestHashCode)
+                        .add(grantUri.uri);
+            }
         }
     }
 
-    private void enforceRequireContentUriPermissionFromCallerOnIntentExtraStream(Intent intent,
-            int contentUserHint, int mode, int callingUid,
-            @RequiredContentUriPermission Integer requireContentUriPermissionFromCaller) {
+    private void enforceRequireContentUriPermissionFromCallerOnIntentExtraStreamUnlocked(
+            Intent intent, int contentUserHint, int mode, int callingUid,
+            @RequiredContentUriPermission Integer requireContentUriPermissionFromCaller,
+            Integer requestHashCode) {
         try {
             final Uri uri = intent.getParcelableExtra(Intent.EXTRA_STREAM, Uri.class);
             if (uri != null) {
                 final GrantUri grantUri = GrantUri.resolve(contentUserHint, uri, mode);
-                enforceRequireContentUriPermissionFromCaller(
-                        requireContentUriPermissionFromCaller, grantUri, callingUid);
+                enforceRequireContentUriPermissionFromCallerUnlocked(
+                        requireContentUriPermissionFromCaller, grantUri, callingUid,
+                        requestHashCode);
             }
         } catch (BadParcelableException e) {
             Slog.w(TAG, "Failed to unparcel an URI in EXTRA_STREAM, skipping"
@@ -759,8 +821,9 @@
             if (uris != null) {
                 for (int i = uris.size() - 1; i >= 0; i--) {
                     final GrantUri grantUri = GrantUri.resolve(contentUserHint, uris.get(i), mode);
-                    enforceRequireContentUriPermissionFromCaller(
-                            requireContentUriPermissionFromCaller, grantUri, callingUid);
+                    enforceRequireContentUriPermissionFromCallerUnlocked(
+                            requireContentUriPermissionFromCaller, grantUri, callingUid,
+                            requestHashCode);
                 }
             }
         } catch (BadParcelableException e) {
@@ -769,6 +832,37 @@
         }
     }
 
+    private void notifyActivityLaunchRequestCompletedUnlocked(Integer requestHashCode,
+            boolean isSuccessfulLaunch, String intentAction, int callingUid,
+            String callingActivityName, int calleeUid, String calleeActivityName,
+            boolean isStartActivityForResult) {
+        ArraySet<Uri> contentUris;
+        synchronized (mLaunchToContentUrisWithoutCallerReadPermission) {
+            contentUris = mLaunchToContentUrisWithoutCallerReadPermission.get(requestHashCode);
+            mLaunchToContentUrisWithoutCallerReadPermission.remove(requestHashCode);
+        }
+        if (!isSuccessfulLaunch || contentUris == null) return;
+
+        final String[] authorities = new String[contentUris.size()];
+        final String[] schemes = new String[contentUris.size()];
+        for (int i = contentUris.size() - 1; i >= 0; i--) {
+            Uri uri = contentUris.valueAt(i);
+            authorities[i] = uri.getAuthority();
+            schemes[i] = uri.getScheme();
+        }
+        FrameworkStatsLog.write(CONTENT_OR_FILE_URI_EVENT_REPORTED,
+                CONTENT_OR_FILE_URI_EVENT_REPORTED__EVENT_TYPE__CONTENT_URI_WITHOUT_CALLER_READ_PERMISSION,
+                intentAction,
+                callingUid,
+                callingActivityName,
+                calleeUid,
+                calleeActivityName,
+                isStartActivityForResult,
+                String.join(",", authorities),
+                String.join(",", schemes),
+                /* uri_mime_type */ null);
+    }
+
     @GuardedBy("mLock")
     private void readGrantedUriPermissionsLocked() {
         if (DEBUG) Slog.v(TAG, "readGrantedUriPermissions()");
@@ -1645,23 +1739,36 @@
         public NeededUriGrants checkGrantUriPermissionFromIntent(Intent intent, int callingUid,
                 String targetPkg, int targetUserId) {
             return internalCheckGrantUriPermissionFromIntent(intent, callingUid, targetPkg,
-                    targetUserId, /* requireContentUriPermissionFromCaller */ null);
+                    targetUserId, /* requireContentUriPermissionFromCaller */ null,
+                    /* requestHashCode */ null);
         }
 
         @Override
         public NeededUriGrants checkGrantUriPermissionFromIntent(Intent intent, int callingUid,
-                String targetPkg, int targetUserId, int requireContentUriPermissionFromCaller) {
+                String targetPkg, int targetUserId, int requireContentUriPermissionFromCaller,
+                int requestHashCode) {
             return internalCheckGrantUriPermissionFromIntent(intent, callingUid, targetPkg,
-                    targetUserId, requireContentUriPermissionFromCaller);
+                    targetUserId, requireContentUriPermissionFromCaller, requestHashCode);
+        }
+
+        @Override
+        public void notifyActivityLaunchRequestCompleted(int requestHashCode,
+                boolean isSuccessfulLaunch, String intentAction, int callingUid,
+                String callingActivityName, int calleeUid, String calleeActivityName,
+                boolean isStartActivityForResult) {
+            UriGrantsManagerService.this.notifyActivityLaunchRequestCompletedUnlocked(
+                    requestHashCode, isSuccessfulLaunch, intentAction, callingUid,
+                    callingActivityName, calleeUid, calleeActivityName,
+                    isStartActivityForResult);
         }
 
         private NeededUriGrants internalCheckGrantUriPermissionFromIntent(Intent intent,
                 int callingUid, String targetPkg, int targetUserId,
-                @Nullable Integer requireContentUriPermissionFromCaller) {
+                @Nullable Integer requireContentUriPermissionFromCaller, Integer requestHashCode) {
             final int mode = (intent != null) ? intent.getFlags() : 0;
             return UriGrantsManagerService.this.checkGrantUriPermissionFromIntentUnlocked(
                     callingUid, targetPkg, intent, mode, null, targetUserId,
-                    requireContentUriPermissionFromCaller);
+                    requireContentUriPermissionFromCaller, requestHashCode);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java
index ab4a4d8..4c1e16c 100644
--- a/services/core/java/com/android/server/vibrator/VibrationThread.java
+++ b/services/core/java/com/android/server/vibrator/VibrationThread.java
@@ -128,15 +128,20 @@
      *  before the release callback.
      */
     boolean runVibrationOnVibrationThread(VibrationStepConductor conductor) {
-        synchronized (mLock) {
-            if (mRequestedActiveConductor != null) {
-                Slog.wtf(TAG, "Attempt to start vibration when one already running");
-                return false;
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "runVibrationOnVibrationThread");
+        try {
+            synchronized (mLock) {
+                if (mRequestedActiveConductor != null) {
+                    Slog.wtf(TAG, "Attempt to start vibration when one already running");
+                    return false;
+                }
+                mRequestedActiveConductor = conductor;
+                mLock.notifyAll();
             }
-            mRequestedActiveConductor = conductor;
-            mLock.notifyAll();
+            return true;
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
         }
-        return true;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/vibrator/VibratorController.java b/services/core/java/com/android/server/vibrator/VibratorController.java
index 4fc0b74..3c47850 100644
--- a/services/core/java/com/android/server/vibrator/VibratorController.java
+++ b/services/core/java/com/android/server/vibrator/VibratorController.java
@@ -23,6 +23,7 @@
 import android.os.Parcel;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.os.Trace;
 import android.os.VibrationEffect;
 import android.os.VibratorInfo;
 import android.os.vibrator.PrebakedSegment;
@@ -123,21 +124,26 @@
 
     /** Reruns the query to the vibrator to load the {@link VibratorInfo}, if not yet successful. */
     public void reloadVibratorInfoIfNeeded() {
-        // Early check outside lock, for quick return.
-        if (mVibratorInfoLoadSuccessful) {
-            return;
-        }
-        synchronized (mLock) {
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorController#reloadVibratorInfoIfNeeded");
+        try {
+            // Early check outside lock, for quick return.
             if (mVibratorInfoLoadSuccessful) {
                 return;
             }
-            int vibratorId = mVibratorInfo.getId();
-            VibratorInfo.Builder vibratorInfoBuilder = new VibratorInfo.Builder(vibratorId);
-            mVibratorInfoLoadSuccessful = mNativeWrapper.getInfo(vibratorInfoBuilder);
-            mVibratorInfo = vibratorInfoBuilder.build();
-            if (!mVibratorInfoLoadSuccessful) {
-                Slog.e(TAG, "Failed retry of HAL getInfo for vibrator " + vibratorId);
+            synchronized (mLock) {
+                if (mVibratorInfoLoadSuccessful) {
+                    return;
+                }
+                int vibratorId = mVibratorInfo.getId();
+                VibratorInfo.Builder vibratorInfoBuilder = new VibratorInfo.Builder(vibratorId);
+                mVibratorInfoLoadSuccessful = mNativeWrapper.getInfo(vibratorInfoBuilder);
+                mVibratorInfo = vibratorInfoBuilder.build();
+                if (!mVibratorInfoLoadSuccessful) {
+                    Slog.e(TAG, "Failed retry of HAL getInfo for vibrator " + vibratorId);
+                }
             }
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
         }
     }
 
@@ -193,8 +199,13 @@
 
     /** Return {@code true} if the underlying vibrator is currently available, false otherwise. */
     public boolean isAvailable() {
-        synchronized (mLock) {
-            return mNativeWrapper.isAvailable();
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorController#isAvailable");
+        try {
+            synchronized (mLock) {
+                return mNativeWrapper.isAvailable();
+            }
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
         }
     }
 
@@ -204,12 +215,17 @@
      * <p>This will affect the state of {@link #isUnderExternalControl()}.
      */
     public void setExternalControl(boolean externalControl) {
-        if (!mVibratorInfo.hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) {
-            return;
-        }
-        synchronized (mLock) {
-            mIsUnderExternalControl = externalControl;
-            mNativeWrapper.setExternalControl(externalControl);
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "setExternalControl(" + externalControl + ")");
+        try {
+            if (!mVibratorInfo.hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) {
+                return;
+            }
+            synchronized (mLock) {
+                mIsUnderExternalControl = externalControl;
+                mNativeWrapper.setExternalControl(externalControl);
+            }
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
         }
     }
 
@@ -218,28 +234,38 @@
      * if given {@code effect} is {@code null}.
      */
     public void updateAlwaysOn(int id, @Nullable PrebakedSegment prebaked) {
-        if (!mVibratorInfo.hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL)) {
-            return;
-        }
-        synchronized (mLock) {
-            if (prebaked == null) {
-                mNativeWrapper.alwaysOnDisable(id);
-            } else {
-                mNativeWrapper.alwaysOnEnable(id, prebaked.getEffectId(),
-                        prebaked.getEffectStrength());
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorController#updateAlwaysOn");
+        try {
+            if (!mVibratorInfo.hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL)) {
+                return;
             }
+            synchronized (mLock) {
+                if (prebaked == null) {
+                    mNativeWrapper.alwaysOnDisable(id);
+                } else {
+                    mNativeWrapper.alwaysOnEnable(id, prebaked.getEffectId(),
+                            prebaked.getEffectStrength());
+                }
+            }
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
         }
     }
 
     /** Set the vibration amplitude. This will NOT affect the state of {@link #isVibrating()}. */
     public void setAmplitude(float amplitude) {
-        synchronized (mLock) {
-            if (mVibratorInfo.hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL)) {
-                mNativeWrapper.setAmplitude(amplitude);
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorController#setAmplitude");
+        try {
+            synchronized (mLock) {
+                if (mVibratorInfo.hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL)) {
+                    mNativeWrapper.setAmplitude(amplitude);
+                }
+                if (mIsVibrating) {
+                    mCurrentAmplitude = amplitude;
+                }
             }
-            if (mIsVibrating) {
-                mCurrentAmplitude = amplitude;
-            }
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
         }
     }
 
@@ -253,13 +279,18 @@
      * do not support the input or a negative number if the operation failed.
      */
     public long on(long milliseconds, long vibrationId) {
-        synchronized (mLock) {
-            long duration = mNativeWrapper.on(milliseconds, vibrationId);
-            if (duration > 0) {
-                mCurrentAmplitude = -1;
-                notifyListenerOnVibrating(true);
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorController#on");
+        try {
+            synchronized (mLock) {
+                long duration = mNativeWrapper.on(milliseconds, vibrationId);
+                if (duration > 0) {
+                    mCurrentAmplitude = -1;
+                    notifyListenerOnVibrating(true);
+                }
+                return duration;
             }
-            return duration;
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
         }
     }
 
@@ -273,6 +304,7 @@
      * do not support the input or a negative number if the operation failed.
      */
     public long on(VibrationEffect.VendorEffect vendorEffect, long vibrationId) {
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorController#on (vendor)");
         synchronized (mLock) {
             Parcel vendorData = Parcel.obtain();
             try {
@@ -288,6 +320,7 @@
                 return duration;
             } finally {
                 vendorData.recycle();
+                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
             }
         }
     }
@@ -302,14 +335,19 @@
      * do not support the input or a negative number if the operation failed.
      */
     public long on(PrebakedSegment prebaked, long vibrationId) {
-        synchronized (mLock) {
-            long duration = mNativeWrapper.perform(prebaked.getEffectId(),
-                    prebaked.getEffectStrength(), vibrationId);
-            if (duration > 0) {
-                mCurrentAmplitude = -1;
-                notifyListenerOnVibrating(true);
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorController#on (Prebaked)");
+        try {
+            synchronized (mLock) {
+                long duration = mNativeWrapper.perform(prebaked.getEffectId(),
+                        prebaked.getEffectStrength(), vibrationId);
+                if (duration > 0) {
+                    mCurrentAmplitude = -1;
+                    notifyListenerOnVibrating(true);
+                }
+                return duration;
             }
-            return duration;
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
         }
     }
 
@@ -323,16 +361,21 @@
      * do not support the input or a negative number if the operation failed.
      */
     public long on(PrimitiveSegment[] primitives, long vibrationId) {
-        if (!mVibratorInfo.hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) {
-            return 0;
-        }
-        synchronized (mLock) {
-            long duration = mNativeWrapper.compose(primitives, vibrationId);
-            if (duration > 0) {
-                mCurrentAmplitude = -1;
-                notifyListenerOnVibrating(true);
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorController#on (Primitive)");
+        try {
+            if (!mVibratorInfo.hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) {
+                return 0;
             }
-            return duration;
+            synchronized (mLock) {
+                long duration = mNativeWrapper.compose(primitives, vibrationId);
+                if (duration > 0) {
+                    mCurrentAmplitude = -1;
+                    notifyListenerOnVibrating(true);
+                }
+                return duration;
+            }
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
         }
     }
 
@@ -345,17 +388,22 @@
      * @return The duration of the effect playing, or 0 if unsupported.
      */
     public long on(RampSegment[] primitives, long vibrationId) {
-        if (!mVibratorInfo.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)) {
-            return 0;
-        }
-        synchronized (mLock) {
-            int braking = mVibratorInfo.getDefaultBraking();
-            long duration = mNativeWrapper.composePwle(primitives, braking, vibrationId);
-            if (duration > 0) {
-                mCurrentAmplitude = -1;
-                notifyListenerOnVibrating(true);
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorController#on (PWLE)");
+        try {
+            if (!mVibratorInfo.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)) {
+                return 0;
             }
-            return duration;
+            synchronized (mLock) {
+                int braking = mVibratorInfo.getDefaultBraking();
+                long duration = mNativeWrapper.composePwle(primitives, braking, vibrationId);
+                if (duration > 0) {
+                    mCurrentAmplitude = -1;
+                    notifyListenerOnVibrating(true);
+                }
+                return duration;
+            }
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
         }
     }
 
@@ -365,10 +413,15 @@
      * <p>This will affect the state of {@link #isVibrating()}.
      */
     public void off() {
-        synchronized (mLock) {
-            mNativeWrapper.off();
-            mCurrentAmplitude = 0;
-            notifyListenerOnVibrating(false);
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorController#off");
+        try {
+            synchronized (mLock) {
+                mNativeWrapper.off();
+                mCurrentAmplitude = 0;
+                notifyListenerOnVibrating(false);
+            }
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
         }
     }
 
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 799934a..899f0b1 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -462,20 +462,31 @@
     @Override // Binder call
     public void performHapticFeedback(int uid, int deviceId, String opPkg, int constant,
             String reason, int flags, int privFlags) {
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "performHapticFeedback");
         // Note that the `performHapticFeedback` method does not take a token argument from the
         // caller, and instead, uses this service as the token. This is to mitigate performance
         // impact that would otherwise be caused due to marshal latency. Haptic feedback effects are
         // short-lived, so we don't need to cancel when the process dies.
-        performHapticFeedbackInternal(uid, deviceId, opPkg, constant, reason, /* token= */
-                this, flags, privFlags);
+        try {
+            performHapticFeedbackInternal(uid, deviceId, opPkg, constant, reason, /* token= */
+                    this, flags, privFlags);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+        }
     }
 
     @Override // Binder call
     public void performHapticFeedbackForInputDevice(int uid, int deviceId, String opPkg,
             int constant, int inputDeviceId, int inputSource, String reason, int flags,
             int privFlags) {
-        performHapticFeedbackForInputDeviceInternal(uid, deviceId, opPkg, constant, inputDeviceId,
-                inputSource, reason, /* token= */ this, flags, privFlags);
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "performHapticFeedbackForInputDevice");
+        try {
+            performHapticFeedbackForInputDeviceInternal(uid, deviceId, opPkg, constant,
+                    inputDeviceId,
+                    inputSource, reason, /* token= */ this, flags, privFlags);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+        }
     }
 
     /**
@@ -919,30 +930,25 @@
     @GuardedBy("mLock")
     @Nullable
     private Vibration.EndInfo startVibrationOnThreadLocked(VibrationStepConductor conductor) {
-        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationThreadLocked");
-        try {
-            HalVibration vib = conductor.getVibration();
-            int mode = startAppOpModeLocked(vib.callerInfo);
-            switch (mode) {
-                case AppOpsManager.MODE_ALLOWED:
-                    Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
-                    // Make sure mCurrentVibration is set while triggering the VibrationThread.
-                    mCurrentVibration = conductor;
-                    if (!mVibrationThread.runVibrationOnVibrationThread(mCurrentVibration)) {
-                        // Shouldn't happen. The method call already logs a wtf.
-                        mCurrentVibration = null;  // Aborted.
-                        return new Vibration.EndInfo(Status.IGNORED_ERROR_SCHEDULING);
-                    }
-                    return null;
-                case AppOpsManager.MODE_ERRORED:
-                    Slog.w(TAG, "Start AppOpsManager operation errored for uid "
-                            + vib.callerInfo.uid);
-                    return new Vibration.EndInfo(Status.IGNORED_ERROR_APP_OPS);
-                default:
-                    return new Vibration.EndInfo(Status.IGNORED_APP_OPS);
-            }
-        } finally {
-            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+        HalVibration vib = conductor.getVibration();
+        int mode = startAppOpModeLocked(vib.callerInfo);
+        switch (mode) {
+            case AppOpsManager.MODE_ALLOWED:
+                Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
+                // Make sure mCurrentVibration is set while triggering the VibrationThread.
+                mCurrentVibration = conductor;
+                if (!mVibrationThread.runVibrationOnVibrationThread(mCurrentVibration)) {
+                    // Shouldn't happen. The method call already logs a wtf.
+                    mCurrentVibration = null;  // Aborted.
+                    return new Vibration.EndInfo(Status.IGNORED_ERROR_SCHEDULING);
+                }
+                return null;
+            case AppOpsManager.MODE_ERRORED:
+                Slog.w(TAG, "Start AppOpsManager operation errored for uid "
+                        + vib.callerInfo.uid);
+                return new Vibration.EndInfo(Status.IGNORED_ERROR_APP_OPS);
+            default:
+                return new Vibration.EndInfo(Status.IGNORED_APP_OPS);
         }
     }
 
@@ -1050,21 +1056,16 @@
 
     @GuardedBy("mLock")
     private void reportFinishedVibrationLocked(Vibration.EndInfo vibrationEndInfo) {
-        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "reportFinishVibrationLocked");
         Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
-        try {
-            HalVibration vib = mCurrentVibration.getVibration();
-            if (DEBUG) {
-                Slog.d(TAG, "Reporting vibration " + vib.id + " finished with "
-                        + vibrationEndInfo);
-            }
-            // DO NOT write metrics at this point, wait for the VibrationThread to report the
-            // vibration was released, after all cleanup. The metrics will be reported then.
-            endVibrationLocked(vib, vibrationEndInfo, /* shouldWriteStats= */ false);
-            finishAppOpModeLocked(vib.callerInfo);
-        } finally {
-            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+        HalVibration vib = mCurrentVibration.getVibration();
+        if (DEBUG) {
+            Slog.d(TAG, "Reporting vibration " + vib.id + " finished with "
+                    + vibrationEndInfo);
         }
+        // DO NOT write metrics at this point, wait for the VibrationThread to report the
+        // vibration was released, after all cleanup. The metrics will be reported then.
+        endVibrationLocked(vib, vibrationEndInfo, /* shouldWriteStats= */ false);
+        finishAppOpModeLocked(vib.callerInfo);
     }
 
     private void onSyncedVibrationComplete(long vibrationId) {
@@ -1418,40 +1419,34 @@
 
     @GuardedBy("mLock")
     @Nullable
-    private SparseArray<PrebakedSegment> fixupAlwaysOnEffectsLocked(
-            CombinedVibration effect) {
-        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "fixupAlwaysOnEffectsLocked");
-        try {
-            SparseArray<VibrationEffect> effects;
-            if (effect instanceof CombinedVibration.Mono) {
-                VibrationEffect syncedEffect = ((CombinedVibration.Mono) effect).getEffect();
-                effects = transformAllVibratorsLocked(unused -> syncedEffect);
-            } else if (effect instanceof CombinedVibration.Stereo) {
-                effects = ((CombinedVibration.Stereo) effect).getEffects();
-            } else {
-                // Only synced combinations can be used for always-on effects.
-                return null;
-            }
-            SparseArray<PrebakedSegment> result = new SparseArray<>();
-            for (int i = 0; i < effects.size(); i++) {
-                PrebakedSegment prebaked = extractPrebakedSegment(effects.valueAt(i));
-                if (prebaked == null) {
-                    Slog.e(TAG, "Only prebaked effects supported for always-on.");
-                    return null;
-                }
-                int vibratorId = effects.keyAt(i);
-                VibratorController vibrator = mVibrators.get(vibratorId);
-                if (vibrator != null && vibrator.hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL)) {
-                    result.put(vibratorId, prebaked);
-                }
-            }
-            if (result.size() == 0) {
-                return null;
-            }
-            return result;
-        } finally {
-            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+    private SparseArray<PrebakedSegment> fixupAlwaysOnEffectsLocked(CombinedVibration effect) {
+        SparseArray<VibrationEffect> effects;
+        if (effect instanceof CombinedVibration.Mono) {
+            VibrationEffect syncedEffect = ((CombinedVibration.Mono) effect).getEffect();
+            effects = transformAllVibratorsLocked(unused -> syncedEffect);
+        } else if (effect instanceof CombinedVibration.Stereo) {
+            effects = ((CombinedVibration.Stereo) effect).getEffects();
+        } else {
+            // Only synced combinations can be used for always-on effects.
+            return null;
         }
+        SparseArray<PrebakedSegment> result = new SparseArray<>();
+        for (int i = 0; i < effects.size(); i++) {
+            PrebakedSegment prebaked = extractPrebakedSegment(effects.valueAt(i));
+            if (prebaked == null) {
+                Slog.e(TAG, "Only prebaked effects supported for always-on.");
+                return null;
+            }
+            int vibratorId = effects.keyAt(i);
+            VibratorController vibrator = mVibrators.get(vibratorId);
+            if (vibrator != null && vibrator.hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL)) {
+                result.put(vibratorId, prebaked);
+            }
+        }
+        if (result.size() == 0) {
+            return null;
+        }
+        return result;
     }
 
     @Nullable
@@ -1580,25 +1575,42 @@
 
         @Override
         public boolean prepareSyncedVibration(long requiredCapabilities, int[] vibratorIds) {
-            if ((mCapabilities & requiredCapabilities) != requiredCapabilities) {
-                // This sync step requires capabilities this device doesn't have, skipping sync...
-                return false;
+            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "prepareSyncedVibration");
+            try {
+                if ((mCapabilities & requiredCapabilities) != requiredCapabilities) {
+                    // This sync step requires capabilities this device doesn't have, skipping
+                    // sync...
+                    return false;
+                }
+                return mNativeWrapper.prepareSynced(vibratorIds);
+            } finally {
+                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
             }
-            return mNativeWrapper.prepareSynced(vibratorIds);
         }
 
         @Override
         public boolean triggerSyncedVibration(long vibrationId) {
-            return mNativeWrapper.triggerSynced(vibrationId);
+            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "triggerSyncedVibration");
+            try {
+                return mNativeWrapper.triggerSynced(vibrationId);
+            } finally {
+                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+            }
         }
 
         @Override
         public void cancelSyncedVibration() {
-            mNativeWrapper.cancelSynced();
+            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "cancelSyncedVibration");
+            try {
+                mNativeWrapper.cancelSynced();
+            } finally {
+                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+            }
         }
 
         @Override
         public void noteVibratorOn(int uid, long duration) {
+            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "noteVibratorOn");
             try {
                 if (duration <= 0) {
                     // Tried to turn vibrator ON and got:
@@ -1616,16 +1628,21 @@
                 mFrameworkStatsLogger.writeVibratorStateOnAsync(uid, duration);
             } catch (RemoteException e) {
                 Slog.e(TAG, "Error logging VibratorStateChanged to ON", e);
+            } finally {
+                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
             }
         }
 
         @Override
         public void noteVibratorOff(int uid) {
+            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "noteVibratorOff");
             try {
                 mBatteryStatsService.noteVibratorOff(uid);
                 mFrameworkStatsLogger.writeVibratorStateOffAsync(uid);
             } catch (RemoteException e) {
                 Slog.e(TAG, "Error logging VibratorStateChanged to OFF", e);
+            } finally {
+                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
             }
         }
 
@@ -1634,11 +1651,16 @@
             if (DEBUG) {
                 Slog.d(TAG, "Vibration " + vibrationId + " finished with " + vibrationEndInfo);
             }
-            synchronized (mLock) {
-                if (mCurrentVibration != null
-                        && mCurrentVibration.getVibration().id == vibrationId) {
-                    reportFinishedVibrationLocked(vibrationEndInfo);
+            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "onVibrationCompleted");
+            try {
+                synchronized (mLock) {
+                    if (mCurrentVibration != null
+                            && mCurrentVibration.getVibration().id == vibrationId) {
+                        reportFinishedVibrationLocked(vibrationEndInfo);
+                    }
                 }
+            } finally {
+                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
             }
         }
 
@@ -1647,34 +1669,40 @@
             if (DEBUG) {
                 Slog.d(TAG, "VibrationThread released after finished vibration");
             }
-            synchronized (mLock) {
-                if (DEBUG) {
-                    Slog.d(TAG, "Processing VibrationThread released callback");
-                }
-                if (Build.IS_DEBUGGABLE && mCurrentVibration != null
-                        && mCurrentVibration.getVibration().id != vibrationId) {
-                    Slog.wtf(TAG, TextUtils.formatSimple(
-                            "VibrationId mismatch on release. expected=%d, released=%d",
-                            mCurrentVibration.getVibration().id, vibrationId));
-                }
-                if (mCurrentVibration != null) {
-                    // This is when we consider the current vibration complete, so report metrics.
-                    mFrameworkStatsLogger.writeVibrationReportedAsync(
-                            mCurrentVibration.getVibration().getStatsInfo(
-                                    /* completionUptimeMillis= */ SystemClock.uptimeMillis()));
-                    mCurrentVibration = null;
-                }
-                if (mNextVibration != null) {
-                    VibrationStepConductor nextConductor = mNextVibration;
-                    mNextVibration = null;
-                    Vibration.EndInfo vibrationEndInfo = startVibrationOnThreadLocked(
-                            nextConductor);
-                    if (vibrationEndInfo != null) {
-                        // Failed to start the vibration, end it and report metrics right away.
-                        endVibrationLocked(nextConductor.getVibration(),
-                                vibrationEndInfo, /* shouldWriteStats= */ true);
+            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "onVibrationThreadReleased: " + vibrationId);
+            try {
+                synchronized (mLock) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "Processing VibrationThread released callback");
+                    }
+                    if (Build.IS_DEBUGGABLE && mCurrentVibration != null
+                            && mCurrentVibration.getVibration().id != vibrationId) {
+                        Slog.wtf(TAG, TextUtils.formatSimple(
+                                "VibrationId mismatch on release. expected=%d, released=%d",
+                                mCurrentVibration.getVibration().id, vibrationId));
+                    }
+                    if (mCurrentVibration != null) {
+                        // This is when we consider the current vibration complete, so report
+                        // metrics.
+                        mFrameworkStatsLogger.writeVibrationReportedAsync(
+                                mCurrentVibration.getVibration().getStatsInfo(
+                                        /* completionUptimeMillis= */ SystemClock.uptimeMillis()));
+                        mCurrentVibration = null;
+                    }
+                    if (mNextVibration != null) {
+                        VibrationStepConductor nextConductor = mNextVibration;
+                        mNextVibration = null;
+                        Vibration.EndInfo vibrationEndInfo = startVibrationOnThreadLocked(
+                                nextConductor);
+                        if (vibrationEndInfo != null) {
+                            // Failed to start the vibration, end it and report metrics right away.
+                            endVibrationLocked(nextConductor.getVibration(),
+                                    vibrationEndInfo, /* shouldWriteStats= */ true);
+                        }
                     }
                 }
+            } finally {
+                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
             }
         }
     }
@@ -1917,22 +1945,17 @@
     @GuardedBy("mLock")
     private void endExternalVibrateLocked(Vibration.EndInfo vibrationEndInfo,
             boolean continueExternalControl) {
-        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "endExternalVibrateLocked");
-        try {
-            if (mCurrentExternalVibration == null) {
-                return;
-            }
-            mCurrentExternalVibration.unlinkToDeath();
-            if (!continueExternalControl) {
-                setExternalControl(false, mCurrentExternalVibration.stats);
-            }
-            // The external control was turned off, end it and report metrics right away.
-            endVibrationLocked(mCurrentExternalVibration, vibrationEndInfo,
-                    /* shouldWriteStats= */ true);
-            mCurrentExternalVibration = null;
-        } finally {
-            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+        if (mCurrentExternalVibration == null) {
+            return;
         }
+        mCurrentExternalVibration.unlinkToDeath();
+        if (!continueExternalControl) {
+            setExternalControl(false, mCurrentExternalVibration.stats);
+        }
+        // The external control was turned off, end it and report metrics right away.
+        endVibrationLocked(mCurrentExternalVibration, vibrationEndInfo,
+                /* shouldWriteStats= */ true);
+        mCurrentExternalVibration = null;
     }
 
     private HapticFeedbackVibrationProvider getHapticVibrationProvider() {
@@ -1987,143 +2010,160 @@
 
         @Override
         public ExternalVibrationScale onExternalVibrationStart(ExternalVibration vib) {
-            // Create Vibration.Stats as close to the received request as possible, for tracking.
-            ExternalVibrationSession externalVibration = new ExternalVibrationSession(vib);
-            // Mute the request until we run all the checks and accept the vibration.
-            externalVibration.muteScale();
-            boolean alreadyUnderExternalControl = false;
-            boolean waitForCompletion = false;
+            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "onExternalVibrationStart");
+            try {
+                // Create Vibration.Stats as close to the received request as possible, for
+                // tracking.
+                ExternalVibrationSession externalVibration = new ExternalVibrationSession(vib);
+                // Mute the request until we run all the checks and accept the vibration.
+                externalVibration.muteScale();
+                boolean alreadyUnderExternalControl = false;
+                boolean waitForCompletion = false;
 
-            synchronized (mLock) {
-                if (!hasExternalControlCapability()) {
-                    endVibrationLocked(externalVibration,
-                            new Vibration.EndInfo(Status.IGNORED_UNSUPPORTED),
-                            /* shouldWriteStats= */ true);
-                    return externalVibration.getScale();
-                }
+                synchronized (mLock) {
+                    if (!hasExternalControlCapability()) {
+                        endVibrationLocked(externalVibration,
+                                new Vibration.EndInfo(Status.IGNORED_UNSUPPORTED),
+                                /* shouldWriteStats= */ true);
+                        return externalVibration.getScale();
+                    }
 
-                if (ActivityManager.checkComponentPermission(android.Manifest.permission.VIBRATE,
-                        vib.getUid(), -1 /*owningUid*/, true /*exported*/)
-                        != PackageManager.PERMISSION_GRANTED) {
-                    Slog.w(TAG, "pkg=" + vib.getPackage() + ", uid=" + vib.getUid()
-                            + " tried to play externally controlled vibration"
-                            + " without VIBRATE permission, ignoring.");
-                    endVibrationLocked(externalVibration,
-                            new Vibration.EndInfo(Status.IGNORED_MISSING_PERMISSION),
-                            /* shouldWriteStats= */ true);
-                    return externalVibration.getScale();
-                }
+                    if (ActivityManager.checkComponentPermission(
+                            android.Manifest.permission.VIBRATE,
+                            vib.getUid(), -1 /*owningUid*/, true /*exported*/)
+                            != PackageManager.PERMISSION_GRANTED) {
+                        Slog.w(TAG, "pkg=" + vib.getPackage() + ", uid=" + vib.getUid()
+                                + " tried to play externally controlled vibration"
+                                + " without VIBRATE permission, ignoring.");
+                        endVibrationLocked(externalVibration,
+                                new Vibration.EndInfo(Status.IGNORED_MISSING_PERMISSION),
+                                /* shouldWriteStats= */ true);
+                        return externalVibration.getScale();
+                    }
 
-                Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationLocked(
-                        externalVibration.callerInfo);
+                    Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationLocked(
+                            externalVibration.callerInfo);
 
-                if (vibrationEndInfo == null
-                        && mCurrentExternalVibration != null
-                        && mCurrentExternalVibration.isHoldingSameVibration(vib)) {
-                    // We are already playing this external vibration, so we can return the same
-                    // scale calculated in the previous call to this method.
-                    return mCurrentExternalVibration.getScale();
-                }
+                    if (vibrationEndInfo == null
+                            && mCurrentExternalVibration != null
+                            && mCurrentExternalVibration.isHoldingSameVibration(vib)) {
+                        // We are already playing this external vibration, so we can return the same
+                        // scale calculated in the previous call to this method.
+                        return mCurrentExternalVibration.getScale();
+                    }
 
-                if (vibrationEndInfo == null) {
-                    // Check if ongoing vibration is more important than this vibration.
-                    vibrationEndInfo = shouldIgnoreVibrationForOngoingLocked(externalVibration);
-                }
+                    if (vibrationEndInfo == null) {
+                        // Check if ongoing vibration is more important than this vibration.
+                        vibrationEndInfo = shouldIgnoreVibrationForOngoingLocked(externalVibration);
+                    }
 
-                if (vibrationEndInfo != null) {
-                    endVibrationLocked(externalVibration, vibrationEndInfo,
-                            /* shouldWriteStats= */ true);
-                    return externalVibration.getScale();
-                }
+                    if (vibrationEndInfo != null) {
+                        endVibrationLocked(externalVibration, vibrationEndInfo,
+                                /* shouldWriteStats= */ true);
+                        return externalVibration.getScale();
+                    }
 
-                if (mCurrentExternalVibration == null) {
-                    // If we're not under external control right now, then cancel any normal
-                    // vibration that may be playing and ready the vibrator for external control.
-                    if (mCurrentVibration != null) {
+                    if (mCurrentExternalVibration == null) {
+                        // If we're not under external control right now, then cancel any normal
+                        // vibration that may be playing and ready the vibrator for external
+                        // control.
+                        if (mCurrentVibration != null) {
+                            externalVibration.stats.reportInterruptedAnotherVibration(
+                                    mCurrentVibration.getVibration().callerInfo);
+                            clearNextVibrationLocked(
+                                    new Vibration.EndInfo(Status.IGNORED_FOR_EXTERNAL,
+                                            externalVibration.callerInfo));
+                            mCurrentVibration.notifyCancelled(
+                                    new Vibration.EndInfo(Status.CANCELLED_SUPERSEDED,
+                                            externalVibration.callerInfo),
+                                    /* immediate= */ true);
+                            waitForCompletion = true;
+                        }
+                    } else {
+                        // At this point we have an externally controlled vibration playing already.
+                        // Since the interface defines that only one externally controlled
+                        // vibration can
+                        // play at a time, we need to first mute the ongoing vibration and then
+                        // return
+                        // a scale from this function for the new one, so we can be assured that the
+                        // ongoing will be muted in favor of the new vibration.
+                        //
+                        // Note that this doesn't support multiple concurrent external controls,
+                        // as we would need to mute the old one still if it came from a different
+                        // controller.
+                        alreadyUnderExternalControl = true;
+                        mCurrentExternalVibration.notifyEnded();
                         externalVibration.stats.reportInterruptedAnotherVibration(
-                                mCurrentVibration.getVibration().callerInfo);
-                        clearNextVibrationLocked(
-                                new Vibration.EndInfo(Status.IGNORED_FOR_EXTERNAL,
-                                        externalVibration.callerInfo));
-                        mCurrentVibration.notifyCancelled(
+                                mCurrentExternalVibration.callerInfo);
+                        endExternalVibrateLocked(
                                 new Vibration.EndInfo(Status.CANCELLED_SUPERSEDED,
                                         externalVibration.callerInfo),
-                                /* immediate= */ true);
-                        waitForCompletion = true;
+                                /* continueExternalControl= */ true);
                     }
-                } else {
-                    // At this point we have an externally controlled vibration playing already.
-                    // Since the interface defines that only one externally controlled vibration can
-                    // play at a time, we need to first mute the ongoing vibration and then return
-                    // a scale from this function for the new one, so we can be assured that the
-                    // ongoing will be muted in favor of the new vibration.
-                    //
-                    // Note that this doesn't support multiple concurrent external controls, as we
-                    // would need to mute the old one still if it came from a different controller.
-                    alreadyUnderExternalControl = true;
-                    mCurrentExternalVibration.notifyEnded();
-                    externalVibration.stats.reportInterruptedAnotherVibration(
-                            mCurrentExternalVibration.callerInfo);
-                    endExternalVibrateLocked(
-                            new Vibration.EndInfo(Status.CANCELLED_SUPERSEDED,
-                                    externalVibration.callerInfo),
-                            /* continueExternalControl= */ true);
-                }
 
-                VibrationAttributes attrs = fixupVibrationAttributes(vib.getVibrationAttributes(),
-                        /* effect= */ null);
-                if (attrs.isFlagSet(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) {
-                    // Force update of user settings before checking if this vibration effect should
-                    // be ignored or scaled.
-                    mVibrationSettings.update();
-                }
-
-                mCurrentExternalVibration = externalVibration;
-                externalVibration.linkToDeath(this::onExternalVibrationBinderDied);
-                externalVibration.scale(mVibrationScaler, attrs.getUsage());
-            }
-
-            if (waitForCompletion) {
-                if (!mVibrationThread.waitForThreadIdle(VIBRATION_CANCEL_WAIT_MILLIS)) {
-                    Slog.e(TAG, "Timed out waiting for vibration to cancel");
-                    synchronized (mLock) {
-                        // Trigger endExternalVibrateLocked to unlink to death recipient.
-                        endExternalVibrateLocked(
-                                new Vibration.EndInfo(Status.IGNORED_ERROR_CANCELLING),
-                                /* continueExternalControl= */ false);
-                        // Mute the request, vibration will be ignored.
-                        externalVibration.muteScale();
+                    VibrationAttributes attrs = fixupVibrationAttributes(
+                            vib.getVibrationAttributes(),
+                            /* effect= */ null);
+                    if (attrs.isFlagSet(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) {
+                        // Force update of user settings before checking if this vibration effect
+                        // should be ignored or scaled.
+                        mVibrationSettings.update();
                     }
-                    return externalVibration.getScale();
+
+                    mCurrentExternalVibration = externalVibration;
+                    externalVibration.linkToDeath(this::onExternalVibrationBinderDied);
+                    externalVibration.scale(mVibrationScaler, attrs.getUsage());
                 }
-            }
-            if (!alreadyUnderExternalControl) {
+
+                if (waitForCompletion) {
+                    if (!mVibrationThread.waitForThreadIdle(VIBRATION_CANCEL_WAIT_MILLIS)) {
+                        Slog.e(TAG, "Timed out waiting for vibration to cancel");
+                        synchronized (mLock) {
+                            // Trigger endExternalVibrateLocked to unlink to death recipient.
+                            endExternalVibrateLocked(
+                                    new Vibration.EndInfo(Status.IGNORED_ERROR_CANCELLING),
+                                    /* continueExternalControl= */ false);
+                            // Mute the request, vibration will be ignored.
+                            externalVibration.muteScale();
+                        }
+                        return externalVibration.getScale();
+                    }
+                }
+                if (!alreadyUnderExternalControl) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "Vibrator going under external control.");
+                    }
+                    setExternalControl(true, externalVibration.stats);
+                }
                 if (DEBUG) {
-                    Slog.d(TAG, "Vibrator going under external control.");
+                    Slog.d(TAG, "Playing external vibration: " + vib);
                 }
-                setExternalControl(true, externalVibration.stats);
+                // Vibrator will start receiving data from external channels after this point.
+                // Report current time as the vibration start time, for debugging.
+                externalVibration.stats.reportStarted();
+                return externalVibration.getScale();
+            } finally {
+                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
             }
-            if (DEBUG) {
-                Slog.d(TAG, "Playing external vibration: " + vib);
-            }
-            // Vibrator will start receiving data from external channels after this point.
-            // Report current time as the vibration start time, for debugging.
-            externalVibration.stats.reportStarted();
-            return externalVibration.getScale();
         }
 
         @Override
         public void onExternalVibrationStop(ExternalVibration vib) {
-            synchronized (mLock) {
-                if (mCurrentExternalVibration != null
-                        && mCurrentExternalVibration.isHoldingSameVibration(vib)) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "Stopping external vibration: " + vib);
+            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "onExternalVibrationStop");
+            try {
+                synchronized (mLock) {
+                    if (mCurrentExternalVibration != null
+                            && mCurrentExternalVibration.isHoldingSameVibration(vib)) {
+                        if (DEBUG) {
+                            Slog.d(TAG, "Stopping external vibration: " + vib);
+                        }
+                        endExternalVibrateLocked(
+                                new Vibration.EndInfo(Status.FINISHED),
+                                /* continueExternalControl= */ false);
                     }
-                    endExternalVibrateLocked(
-                            new Vibration.EndInfo(Status.FINISHED),
-                            /* continueExternalControl= */ false);
                 }
+            } finally {
+                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index f52a74f..185dd0c 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2641,7 +2641,7 @@
             return true;
         }
         // Only do transfer after transaction has done when starting window exist.
-        if (mStartingData != null && mStartingData.mWaitForSyncTransactionCommit) {
+        if (mStartingData != null && mStartingData.mWaitForSyncTransactionCommitCount > 0) {
             mStartingData.mRemoveAfterTransaction = AFTER_TRANSACTION_COPY_TO_CLIENT;
             return true;
         }
@@ -2804,9 +2804,11 @@
 
     @Override
     void waitForSyncTransactionCommit(ArraySet<WindowContainer> wcAwaitingCommit) {
+        // Only add once per transition.
+        final boolean added = wcAwaitingCommit.contains(this);
         super.waitForSyncTransactionCommit(wcAwaitingCommit);
-        if (mStartingData != null) {
-            mStartingData.mWaitForSyncTransactionCommit = true;
+        if (!added && mStartingData != null) {
+            mStartingData.mWaitForSyncTransactionCommitCount++;
         }
     }
 
@@ -2817,7 +2819,7 @@
             return;
         }
         final StartingData lastData = mStartingData;
-        lastData.mWaitForSyncTransactionCommit = false;
+        lastData.mWaitForSyncTransactionCommitCount--;
         if (lastData.mRemoveAfterTransaction == AFTER_TRANSACTION_REMOVE_DIRECTLY) {
             removeStartingWindowAnimation(lastData.mPrepareRemoveAnimation);
         } else if (lastData.mRemoveAfterTransaction == AFTER_TRANSACTION_COPY_TO_CLIENT) {
@@ -2847,7 +2849,7 @@
         final boolean animate;
         final boolean hasImeSurface;
         if (mStartingData != null) {
-            if (mStartingData.mWaitForSyncTransactionCommit
+            if (mStartingData.mWaitForSyncTransactionCommitCount > 0
                     || mSyncState != SYNC_STATE_NONE) {
                 mStartingData.mRemoveAfterTransaction = AFTER_TRANSACTION_REMOVE_DIRECTLY;
                 mStartingData.mPrepareRemoveAnimation = prepareAnimation;
@@ -8150,13 +8152,17 @@
      * into account orientation per-app overrides applied by the device manufacturers.
      */
     @Override
+    @ActivityInfo.ScreenOrientation
     protected int getOverrideOrientation() {
-        if (mWmService.mConstants.mIgnoreActivityOrientationRequest
-                && info.applicationInfo.category != ApplicationInfo.CATEGORY_GAME) {
-            return ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+        final int candidateOrientation;
+        if (!mWmService.mConstants.mIgnoreActivityOrientationRequest
+                || info.applicationInfo.category == ApplicationInfo.CATEGORY_GAME) {
+            candidateOrientation = super.getOverrideOrientation();
+        } else {
+            candidateOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
         }
         return mAppCompatController.getOrientationPolicy()
-                .overrideOrientationIfNeeded(super.getOverrideOrientation());
+                .overrideOrientationIfNeeded(candidateOrientation);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 1822a80..bc11bac 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -603,7 +603,8 @@
                             .checkGrantUriPermissionFromIntent(intent, resolvedCallingUid,
                                     activityInfo.applicationInfo.packageName,
                                     UserHandle.getUserId(activityInfo.applicationInfo.uid),
-                                    activityInfo.requireContentUriPermissionFromCaller);
+                                    activityInfo.requireContentUriPermissionFromCaller,
+                                    /* requestHashCode */ this.hashCode());
                 } else {
                     intentGrants = supervisor.mService.mUgmInternal
                             .checkGrantUriPermissionFromIntent(intent, resolvedCallingUid,
@@ -717,6 +718,9 @@
      * @return The starter result.
      */
     int execute() {
+        // Required for logging ContentOrFileUriEventReported in the finally block.
+        String callerActivityName = null;
+        ActivityRecord launchingRecord = null;
         try {
             onExecutionStarted();
 
@@ -737,6 +741,7 @@
                         ?  Binder.getCallingUid() : mRequest.realCallingUid;
                 launchingState = mSupervisor.getActivityMetricsLogger().notifyActivityLaunching(
                         mRequest.intent, caller, callingUid);
+                callerActivityName = caller != null ? caller.info.name : null;
             }
 
             if (mRequest.intent != null) {
@@ -812,7 +817,7 @@
                 final ActivityOptions originalOptions = mRequest.activityOptions != null
                         ? mRequest.activityOptions.getOriginalOptions() : null;
                 // Only track the launch time of activity that will be resumed.
-                final ActivityRecord launchingRecord = mDoResume ? mLastStartActivityRecord : null;
+                launchingRecord = mDoResume ? mLastStartActivityRecord : null;
                 // If the new record is the one that started, a new activity has created.
                 final boolean newActivityCreated = mStartActivity == launchingRecord;
                 // Notify ActivityMetricsLogger that the activity has launched.
@@ -828,6 +833,23 @@
                 return getExternalResult(res);
             }
         } finally {
+            // Notify UriGrantsManagerService that activity launch completed. Required for logging
+            // the ContentOrFileUriEventReported message.
+            mSupervisor.mService.mUgmInternal.notifyActivityLaunchRequestCompleted(
+                    mRequest.hashCode(),
+                    // isSuccessfulLaunch
+                    launchingRecord != null,
+                    // Intent action
+                    mRequest.intent != null ? mRequest.intent.getAction() : null,
+                    mRequest.realCallingUid,
+                    callerActivityName,
+                    // Callee UID
+                    mRequest.activityInfo != null
+                            ? mRequest.activityInfo.applicationInfo.uid : INVALID_UID,
+                    // Callee Activity name
+                    mRequest.activityInfo != null ? mRequest.activityInfo.name : null,
+                    // isStartActivityForResult
+                    launchingRecord != null && launchingRecord.resultTo != null);
             onExecutionComplete();
         }
     }
diff --git a/services/core/java/com/android/server/wm/AppCompatConfigurationPersister.java b/services/core/java/com/android/server/wm/AppCompatConfigurationPersister.java
index 852ce04..9c861fe 100644
--- a/services/core/java/com/android/server/wm/AppCompatConfigurationPersister.java
+++ b/services/core/java/com/android/server/wm/AppCompatConfigurationPersister.java
@@ -16,16 +16,13 @@
 
 package com.android.server.wm;
 
-import static android.os.StrictMode.setThreadPolicy;
-
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
+import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Environment;
-import android.os.StrictMode;
-import android.os.StrictMode.ThreadPolicy;
 import android.util.AtomicFile;
 import android.util.Slog;
 
@@ -122,7 +119,7 @@
         final File prefFiles = new File(configFolder, letterboxConfigurationFileName);
         mConfigurationFile = new AtomicFile(prefFiles);
         mPersisterQueue = persisterQueue;
-        runWithDiskReadsThreadPolicy(this::readCurrentConfiguration);
+        readCurrentConfiguration();
     }
 
     /**
@@ -212,6 +209,7 @@
                 mDefaultTabletopModeReachabilitySupplier.get();
     }
 
+    @MainThread
     private void readCurrentConfiguration() {
         if (!mConfigurationFile.exists()) {
             useDefaultValue();
@@ -272,20 +270,6 @@
         }
     }
 
-    // The LetterboxConfigurationDeviceConfig needs to access the
-    // file with the current reachability position once when the
-    // device boots. Because DisplayThread uses allowIo=false
-    // accessing a file triggers a DiskReadViolation.
-    // Here we use StrictMode to allow the current thread to read
-    // the AtomicFile once in the current thread restoring the
-    // original ThreadPolicy after that.
-    private void runWithDiskReadsThreadPolicy(Runnable runnable) {
-        final ThreadPolicy currentPolicy = StrictMode.getThreadPolicy();
-        setThreadPolicy(new ThreadPolicy.Builder().permitDiskReads().build());
-        runnable.run();
-        setThreadPolicy(currentPolicy);
-    }
-
     private static class UpdateValuesCommand implements
             PersisterQueue.WriteQueueItem<UpdateValuesCommand> {
 
diff --git a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
index c3db7dd..cc6904f 100644
--- a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
+++ b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
@@ -37,8 +37,7 @@
 import android.os.SystemProperties;
 import android.util.Size;
 import android.view.Gravity;
-
-import com.android.server.wm.utils.DesktopModeFlagsUtil;
+import android.window.flags.DesktopModeFlags;
 
 import java.util.function.Consumer;
 
@@ -104,7 +103,7 @@
         final TaskDisplayArea displayArea = task.getDisplayArea();
         final Rect screenBounds = displayArea.getBounds();
         final Size idealSize = calculateIdealSize(screenBounds, DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
-        if (!DesktopModeFlagsUtil.DYNAMIC_INITIAL_BOUNDS.isEnabled(activity.mWmService.mContext)) {
+        if (!DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(activity.mWmService.mContext)) {
             return centerInScreen(idealSize, screenBounds);
         }
         if (activity.mAppCompatController.getAppCompatAspectRatioOverrides()
diff --git a/services/core/java/com/android/server/wm/DesktopModeHelper.java b/services/core/java/com/android/server/wm/DesktopModeHelper.java
index e0c0c2c..61fbb96 100644
--- a/services/core/java/com/android/server/wm/DesktopModeHelper.java
+++ b/services/core/java/com/android/server/wm/DesktopModeHelper.java
@@ -19,10 +19,10 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.os.SystemProperties;
+import android.window.flags.DesktopModeFlags;
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.wm.utils.DesktopModeFlagsUtil;
 
 /**
  * Constants for desktop mode feature
@@ -36,7 +36,7 @@
 
     /** Whether desktop mode is enabled. */
     static boolean isDesktopModeEnabled(@NonNull Context context) {
-        return DesktopModeFlagsUtil.DESKTOP_WINDOWING_MODE.isEnabled(context);
+        return DesktopModeFlags.DESKTOP_WINDOWING_MODE.isEnabled(context);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 10e0641..21212e5 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -707,6 +707,8 @@
     @Retention(RetentionPolicy.SOURCE)
     @interface InputMethodTarget {}
 
+    /** The surface parent window of the IME container. */
+    private WindowContainer mInputMethodSurfaceParentWindow;
     /** The surface parent of the IME container. */
     @VisibleForTesting
     SurfaceControl mInputMethodSurfaceParent;
@@ -1529,6 +1531,10 @@
         return mDisplayRotation.getLastOrientation();
     }
 
+    WindowContainer getImeParentWindow() {
+        return mInputMethodSurfaceParentWindow;
+    }
+
     void registerRemoteAnimations(RemoteAnimationDefinition definition) {
         mAppTransitionController.registerRemoteAnimations(definition);
     }
@@ -4733,13 +4739,17 @@
                 Slog.i(TAG_WM, "ImeContainer is organized. Skip updateImeParent.");
             }
             // Leave the ImeContainer where the DisplayAreaPolicy placed it.
-            // FEATURE_IME is organized by vendor so they are responible for placing the surface.
+            // FEATURE_IME is organized by vendor so they are responsible for placing the surface.
+            mInputMethodSurfaceParentWindow = null;
             mInputMethodSurfaceParent = null;
             return;
         }
 
-        final SurfaceControl newParent = computeImeParent();
+        final var newParentWindow = computeImeParent();
+        final SurfaceControl newParent =
+                newParentWindow != null ? newParentWindow.getSurfaceControl() : null;
         if (newParent != null && newParent != mInputMethodSurfaceParent) {
+            mInputMethodSurfaceParentWindow = newParentWindow;
             mInputMethodSurfaceParent = newParent;
             getSyncTransaction().reparent(mImeWindowsContainer.mSurfaceControl, newParent);
             if (DEBUG_IME_VISIBILITY) {
@@ -4800,7 +4810,7 @@
      * Computes the window the IME should be attached to.
      */
     @VisibleForTesting
-    SurfaceControl computeImeParent() {
+    WindowContainer computeImeParent() {
         if (!ImeTargetVisibilityPolicy.canComputeImeParent(mImeLayeringTarget, mImeInputTarget)) {
             return null;
         }
@@ -4808,11 +4818,10 @@
         // screen. If it's not covering the entire screen the IME might extend beyond the apps
         // bounds.
         if (shouldImeAttachedToApp()) {
-            return mImeLayeringTarget.mActivityRecord.getSurfaceControl();
+            return mImeLayeringTarget.mActivityRecord;
         }
         // Otherwise, we just attach it to where the display area policy put it.
-        return mImeWindowsContainer.getParent() != null
-                ? mImeWindowsContainer.getParent().getSurfaceControl() : null;
+        return mImeWindowsContainer.getParent();
     }
 
     void setLayoutNeeded() {
diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
index 169a76f..5514294e 100644
--- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java
+++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
@@ -33,6 +33,7 @@
 import android.util.proto.ProtoOutputStream;
 import android.view.InputApplicationHandle;
 import android.view.InputChannel;
+import android.view.WindowInsets;
 import android.window.InputTransferToken;
 
 import com.android.internal.protolog.ProtoLog;
@@ -222,6 +223,10 @@
 
         private boolean mIsFocusable;
 
+        // The EmbeddedWindow can only request the IME. All other insets types are requested by
+        // the host window.
+        private @WindowInsets.Type.InsetsType int mRequestedVisibleTypes = 0;
+
         /**
          * @param session  calling session to check ownership of the window
          * @param clientToken client token used to clean up the map if the embedding process dies
@@ -311,6 +316,27 @@
         }
 
         @Override
+        public boolean isRequestedVisible(@WindowInsets.Type.InsetsType int types) {
+            return (mRequestedVisibleTypes & types) != 0;
+        }
+
+        @Override
+        public @WindowInsets.Type.InsetsType int getRequestedVisibleTypes() {
+            return mRequestedVisibleTypes;
+        }
+
+        /**
+         * Only the IME can be requested from the EmbeddedWindow.
+         * @param requestedVisibleTypes other types than {@link WindowInsets.Type.IME} are
+         *                              not sent to system server via WindowlessWindowManager.
+         */
+        void setRequestedVisibleTypes(@WindowInsets.Type.InsetsType int requestedVisibleTypes) {
+            if (mRequestedVisibleTypes != requestedVisibleTypes) {
+                mRequestedVisibleTypes = requestedVisibleTypes;
+            }
+        }
+
+        @Override
         public int getPid() {
             return mOwnerPid;
         }
@@ -375,6 +401,11 @@
 
         @Override
         public boolean shouldControlIme() {
+            if (android.view.inputmethod.Flags.refactorInsetsController()) {
+                // EmbeddedWindow should never be able to control the IME directly, but only the
+                // RemoteInsetsControlTarget.
+                return false;
+            }
             return mHostWindowState != null;
         }
 
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 43c3d05..e178203 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -268,7 +268,7 @@
 
     // TODO(b/353463205) change statsToken to be NonNull, after the flag is permanently enabled
     @Override
-    protected boolean updateClientVisibility(InsetsControlTarget caller,
+    protected boolean updateClientVisibility(InsetsTarget caller,
             @Nullable ImeTracker.Token statsToken) {
         InsetsControlTarget controlTarget = getControlTarget();
         if (caller != controlTarget) {
@@ -283,12 +283,13 @@
                         ImeTracker.forLogging().onProgress(statsToken,
                                 ImeTracker.PHASE_WM_SET_REMOTE_TARGET_IME_VISIBILITY);
                         controlTarget.setImeInputTargetRequestedVisibility(imeVisible);
-                    } else {
+                    } else if (caller instanceof InsetsControlTarget) {
                         // In case of a virtual display that cannot show the IME, the
                         // controlTarget will be null here, as no controlTarget was set yet. In
                         // that case, proceed similar to the multi window mode (fallback =
                         // RemoteInsetsControlTarget of the default display)
-                        controlTarget = mDisplayContent.getImeHostOrFallback(caller.getWindow());
+                        controlTarget = mDisplayContent.getImeHostOrFallback(
+                                ((InsetsControlTarget) caller).getWindow());
 
                         if (controlTarget != caller) {
                             ImeTracker.forLogging().onProgress(statsToken,
@@ -300,8 +301,7 @@
                         }
                     }
 
-                    WindowState windowState = caller.getWindow();
-                    invokeOnImeRequestedChangedListener(windowState, statsToken);
+                    invokeOnImeRequestedChangedListener(caller, statsToken);
                 } else {
                     // TODO(b/353463205) add ImeTracker?
                 }
@@ -309,20 +309,16 @@
             return false;
         }
         boolean changed = super.updateClientVisibility(caller, statsToken);
-        if (!Flags.refactorInsetsController()) {
+        if (!Flags.refactorInsetsController() && caller instanceof InsetsControlTarget) {
             if (changed && caller.isRequestedVisible(mSource.getType())) {
-                reportImeDrawnForOrganizerIfNeeded(caller);
+                reportImeDrawnForOrganizerIfNeeded((InsetsControlTarget) caller);
             }
         }
         changed |= mDisplayContent.onImeInsetsClientVisibilityUpdate();
         if (Flags.refactorInsetsController()) {
             if (changed) {
-                // RemoteInsetsControlTarget does not have a window. In this case, we use the
-                // windowState from the imeInputTarget
-                WindowState windowState = caller.getWindow() != null ? caller.getWindow()
-                        : ((mDisplayContent.getImeInputTarget() != null)
-                                ? mDisplayContent.getImeInputTarget().getWindowState() : null);
-                invokeOnImeRequestedChangedListener(windowState, statsToken);
+                invokeOnImeRequestedChangedListener(mDisplayContent.getImeInputTarget(),
+                        statsToken);
             } else {
                 // TODO(b/329229469) change phase and check cancelled / failed
                 ImeTracker.forLogging().onCancelled(statsToken,
@@ -334,32 +330,31 @@
 
     void onInputTargetChanged(InputTarget target) {
         if (Flags.refactorInsetsController() && target != null) {
-            WindowState targetWin = target.getWindowState();
             InsetsControlTarget imeControlTarget = getControlTarget();
-            if (target != imeControlTarget && targetWin != null) {
+            if (target != imeControlTarget) {
                 // If the targetWin is not the imeControlTarget (=RemoteInsetsControlTarget) let it
                 // know about the new requestedVisibleTypes for the IME.
                 if (imeControlTarget != null) {
                     imeControlTarget.setImeInputTargetRequestedVisibility(
-                            (targetWin.getRequestedVisibleTypes() & WindowInsets.Type.ime()) != 0);
+                            (target.getRequestedVisibleTypes() & WindowInsets.Type.ime()) != 0);
                 }
             }
         }
     }
 
     // TODO(b/353463205) check callers to see if we can make statsToken @NonNull
-    private void invokeOnImeRequestedChangedListener(WindowState windowState,
+    private void invokeOnImeRequestedChangedListener(InsetsTarget insetsTarget,
             @Nullable ImeTracker.Token statsToken) {
         final var imeListener = mDisplayContent.mWmService.mOnImeRequestedChangedListener;
         if (imeListener != null) {
-            if (windowState != null) {
+            if (insetsTarget != null) {
                 ImeTracker.forLogging().onProgress(statsToken,
                         ImeTracker.PHASE_WM_POSTING_CHANGED_IME_VISIBILITY);
                 mDisplayContent.mWmService.mH.post(() -> {
                     ImeTracker.forLogging().onProgress(statsToken,
                             ImeTracker.PHASE_WM_INVOKING_IME_REQUESTED_LISTENER);
-                    imeListener.onImeRequestedChanged(windowState.mClient.asBinder(),
-                            windowState.isRequestedVisible(WindowInsets.Type.ime()), statsToken);
+                    imeListener.onImeRequestedChanged(insetsTarget.getWindowToken(),
+                            insetsTarget.isRequestedVisible(WindowInsets.Type.ime()), statsToken);
                 });
             } else {
                 ImeTracker.forLogging().onFailed(statsToken,
@@ -676,7 +671,7 @@
         return target == mDisplayContent.getImeFallback();
     }
 
-    private boolean isImeInputTarget(@NonNull InsetsControlTarget target) {
+    private boolean isImeInputTarget(@NonNull InsetsTarget target) {
         return target == mDisplayContent.getImeInputTarget();
     }
 
diff --git a/services/core/java/com/android/server/wm/InputTarget.java b/services/core/java/com/android/server/wm/InputTarget.java
index 0c0b794..40ce9db 100644
--- a/services/core/java/com/android/server/wm/InputTarget.java
+++ b/services/core/java/com/android/server/wm/InputTarget.java
@@ -16,7 +16,6 @@
 
 package com.android.server.wm;
 
-import android.os.IBinder;
 import android.util.proto.ProtoOutputStream;
 
 /**
@@ -25,16 +24,13 @@
  * Both WindowState and EmbeddedWindows can receive input. This consolidates some common properties
  * of both targets.
  */
-interface InputTarget {
+interface InputTarget extends InsetsTarget {
     /* Get the WindowState associated with the target. */
     WindowState getWindowState();
 
     /* Display id of the target. */
     int getDisplayId();
 
-    /* Client IWindow for the target. */
-    IBinder getWindowToken();
-
     /* Owning pid of the target. */
     int getPid();
     int getUid();
diff --git a/services/core/java/com/android/server/wm/InsetsControlTarget.java b/services/core/java/com/android/server/wm/InsetsControlTarget.java
index 07e249a..7043aacf 100644
--- a/services/core/java/com/android/server/wm/InsetsControlTarget.java
+++ b/services/core/java/com/android/server/wm/InsetsControlTarget.java
@@ -18,6 +18,7 @@
 
 import android.annotation.Nullable;
 import android.inputmethodservice.InputMethodService;
+import android.os.IBinder;
 import android.view.WindowInsets;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.inputmethod.ImeTracker;
@@ -25,7 +26,7 @@
 /**
  * Generalization of an object that can control insets state.
  */
-interface InsetsControlTarget {
+interface InsetsControlTarget extends InsetsTarget {
 
     /**
      * Notifies the control target that the insets control has changed.
@@ -42,16 +43,17 @@
         return null;
     }
 
-    /**
-     * @return {@code true} if any of the {@link InsetsType} is requested visible by this target.
-     */
+    @Override
+    default IBinder getWindowToken() {
+        return null;
+    }
+
+    @Override
     default boolean isRequestedVisible(@InsetsType int types) {
         return (WindowInsets.Type.defaultVisible() & types) != 0;
     }
 
-    /**
-     * @return {@link InsetsType}s which are requested visible by this target.
-     */
+    @Override
     default @InsetsType int getRequestedVisibleTypes() {
         return WindowInsets.Type.defaultVisible();
     }
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 129078b..b414a862 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -434,7 +434,7 @@
         return originalState;
     }
 
-    void onRequestedVisibleTypesChanged(InsetsControlTarget caller,
+    void onRequestedVisibleTypesChanged(InsetsTarget caller,
             @Nullable ImeTracker.Token statsToken) {
         mStateController.onRequestedVisibleTypesChanged(caller, statsToken);
         checkAbortTransient(caller);
@@ -449,7 +449,7 @@
      *
      * @param caller who changed the insets state.
      */
-    private void checkAbortTransient(InsetsControlTarget caller) {
+    private void checkAbortTransient(InsetsTarget caller) {
         if (mShowingTransientTypes == 0) {
             return;
         }
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 8f90b2d..f0a4763 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -529,13 +529,27 @@
             setClientVisible((WindowInsets.Type.defaultVisible() & mSource.getType()) != 0);
             return;
         }
+        boolean initiallyVisible = mClientVisible;
         final Point surfacePosition = getWindowFrameSurfacePosition();
         mAdapter = new ControlAdapter(surfacePosition);
         if (mSource.getType() == WindowInsets.Type.ime()) {
+            if (android.view.inputmethod.Flags.refactorInsetsController()) {
+                if (mClientVisible && mServerVisible) {
+                    WindowContainer imeParentWindow = mDisplayContent.getImeParentWindow();
+                    // If the IME is attached to an app window, only consider it initially visible
+                    // if the parent is visible and wasn't part of a transition.
+                    initiallyVisible =
+                            imeParentWindow != null && !imeParentWindow.inTransitionSelfOrParent()
+                                    && imeParentWindow.isVisible()
+                                    && imeParentWindow.isVisibleRequested();
+                } else {
+                    initiallyVisible = false;
+                }
+            }
             setClientVisible(target.isRequestedVisible(WindowInsets.Type.ime()));
         }
         final Transaction t = mWindowContainer.getSyncTransaction();
-        mWindowContainer.startAnimation(t, mAdapter, !mClientVisible /* hidden */,
+        mWindowContainer.startAnimation(t, mAdapter, !initiallyVisible /* hidden */,
                 ANIMATION_TYPE_INSETS_CONTROL);
 
         // The leash was just created. We cannot dispatch it until its surface transaction is
@@ -545,14 +559,16 @@
         final SurfaceControl leash = mAdapter.mCapturedLeash;
         mControlTarget = target;
         updateVisibility();
-        boolean initiallyVisible = mClientVisible;
         if (mSource.getType() == WindowInsets.Type.ime()) {
-            // The IME cannot be initially visible, see ControlAdapter#startAnimation below.
-            // Also, the ImeInsetsSourceConsumer clears the client visibility upon losing control,
-            // but this won't have reached here yet by the time the new control is created.
-            // Note: The DisplayImeController needs the correct previous client's visibility, so we
-            // only override the initiallyVisible here.
-            initiallyVisible = false;
+            if (!android.view.inputmethod.Flags.refactorInsetsController()) {
+                // The IME cannot be initially visible, see ControlAdapter#startAnimation below.
+                // Also, the ImeInsetsSourceConsumer clears the client visibility upon losing
+                // control,  but this won't have reached here yet by the time the new control is
+                // created.
+                // Note: The DisplayImeController needs the correct previous client's visibility,
+                // so we only override the initiallyVisible here.
+                initiallyVisible = false;
+            }
         }
         mControl = new InsetsSourceControl(mSource.getId(), mSource.getType(), leash,
                 initiallyVisible, surfacePosition, getInsetsHint());
@@ -598,7 +614,7 @@
         mSeamlessRotating = false;
     }
 
-    boolean updateClientVisibility(InsetsControlTarget caller,
+    boolean updateClientVisibility(InsetsTarget caller,
             @Nullable ImeTracker.Token statsToken) {
         final boolean requestedVisible = caller.isRequestedVisible(mSource.getType());
         if (caller != mControlTarget || requestedVisible == mClientVisible) {
@@ -799,7 +815,7 @@
                 @AnimationType int type, @NonNull OnAnimationFinishedCallback finishCallback) {
             // TODO(b/166736352): Check if we still need to control the IME visibility here.
             if (mSource.getType() == WindowInsets.Type.ime()) {
-                if (!android.view.inputmethod.Flags.refactorInsetsController() || !mClientVisible) {
+                if (!android.view.inputmethod.Flags.refactorInsetsController()) {
                     // TODO: use 0 alpha and remove t.hide() once b/138459974 is fixed.
                     t.setAlpha(animationLeash, 1 /* alpha */);
                     t.hide(animationLeash);
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 481ecd3..3e39a45 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -219,7 +219,7 @@
         }
     }
 
-    void onRequestedVisibleTypesChanged(InsetsControlTarget caller,
+    void onRequestedVisibleTypesChanged(InsetsTarget caller,
             @Nullable ImeTracker.Token statsToken) {
         boolean changed = false;
         for (int i = mProviders.size() - 1; i >= 0; i--) {
@@ -238,7 +238,7 @@
         }
     }
 
-    @InsetsType int getFakeControllingTypes(InsetsControlTarget target) {
+    @InsetsType int getFakeControllingTypes(InsetsTarget target) {
         @InsetsType int types = 0;
         for (int i = mProviders.size() - 1; i >= 0; i--) {
             final InsetsSourceProvider provider = mProviders.valueAt(i);
diff --git a/services/core/java/com/android/server/wm/InsetsTarget.java b/services/core/java/com/android/server/wm/InsetsTarget.java
new file mode 100644
index 0000000..b918ca3
--- /dev/null
+++ b/services/core/java/com/android/server/wm/InsetsTarget.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.annotation.Nullable;
+import android.os.IBinder;
+import android.view.WindowInsets;
+
+/**
+ * A common parent for {@link InputTarget} and {@link InsetsControlTarget}: Some types (like the
+ * {@link EmbeddedWindowController.EmbeddedWindow}) should not be a control target for insets in
+ * general, but should be able to request the IME. To archive this, the InsetsTarget contains the
+ * minimal information that those interfaces share (and what is needed to show the IME.
+ */
+public interface InsetsTarget {
+
+    /**
+     * @return Client IWindow token for the target.
+     */
+    @Nullable
+    IBinder getWindowToken();
+
+    /**
+     * @param types The {@link WindowInsets.Type}s which requestedVisibility status is returned.
+     * @return {@code true} if any of the {@link WindowInsets.Type.InsetsType} is requested
+     * visible by this target.
+     */
+    boolean isRequestedVisible(@WindowInsets.Type.InsetsType int types);
+
+    /**
+     * @return {@link WindowInsets.Type.InsetsType}s which are requested visible by this target.
+     */
+    @WindowInsets.Type.InsetsType int getRequestedVisibleTypes();
+}
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 2ea2aeb..5550f3e 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -705,8 +705,25 @@
                 win.getDisplayContent().getInsetsPolicy().onRequestedVisibleTypesChanged(win,
                         imeStatsToken);
             } else {
-                ImeTracker.forLogging().onFailed(imeStatsToken,
-                        ImeTracker.PHASE_WM_UPDATE_REQUESTED_VISIBLE_TYPES);
+                EmbeddedWindowController.EmbeddedWindow embeddedWindow = null;
+                if (android.view.inputmethod.Flags.refactorInsetsController()) {
+                    embeddedWindow = mService.mEmbeddedWindowController.getByWindowToken(
+                            window.asBinder());
+                }
+                if (embeddedWindow != null) {
+                    // If there is no WindowState for the IWindow, it could be still an
+                    // EmbeddedWindow. Therefore, check the EmbeddedWindowController as well
+                    // TODO(b/329229469) Use different phase here
+                    ImeTracker.forLogging().onProgress(imeStatsToken,
+                            ImeTracker.PHASE_WM_UPDATE_REQUESTED_VISIBLE_TYPES);
+                    embeddedWindow.setRequestedVisibleTypes(
+                            requestedVisibleTypes & WindowInsets.Type.ime());
+                    embeddedWindow.getDisplayContent().getInsetsPolicy()
+                            .onRequestedVisibleTypesChanged(embeddedWindow, imeStatsToken);
+                } else {
+                    ImeTracker.forLogging().onFailed(imeStatsToken,
+                            ImeTracker.PHASE_WM_UPDATE_REQUESTED_VISIBLE_TYPES);
+                }
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/StartingData.java b/services/core/java/com/android/server/wm/StartingData.java
index 24fb207..22c7e8c 100644
--- a/services/core/java/com/android/server/wm/StartingData.java
+++ b/services/core/java/com/android/server/wm/StartingData.java
@@ -69,7 +69,7 @@
      * Note this isn't equal to transition playing, the period should be
      * Sync finishNow -> Start transaction apply.
      */
-    boolean mWaitForSyncTransactionCommit;
+    int mWaitForSyncTransactionCommitCount;
 
     /**
      * For Shell transition.
@@ -112,7 +112,7 @@
     public String toString() {
         return getClass().getSimpleName() + "{"
                 + Integer.toHexString(System.identityHashCode(this))
-                + " waitForSyncTransactionCommit=" + mWaitForSyncTransactionCommit
+                + " mWaitForSyncTransactionCommitCount=" + mWaitForSyncTransactionCommitCount
                 + " removeAfterTransaction= " + mRemoveAfterTransaction
                 + "}";
     }
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 4eba36f..655a6fb 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -2875,19 +2875,12 @@
             return out;
         }
 
-        // Get the animation theme from the top-most application window
-        // when Flags.customAnimationsBehindTranslucent() is false.
         final AnimationOptions animOptionsForActivityTransition =
                 calculateAnimationOptionsForActivityTransition(type, sortedTargets);
-
         if (!Flags.moveAnimationOptionsToChange() && animOptionsForActivityTransition != null) {
             out.setAnimationOptions(animOptionsForActivityTransition);
         }
 
-        // Store the animation options of the topmost non-translucent change
-        // (Used when Flags.customAnimationsBehindTranslucent() is true)
-        AnimationOptions activityAboveAnimationOptions = null;
-
         final ArraySet<WindowContainer> occludedAtEndContainers = new ArraySet<>();
         // Convert all the resolved ChangeInfos into TransactionInfo.Change objects in order.
         final int count = sortedTargets.size();
@@ -3006,26 +2999,9 @@
                 change.setBackgroundColor(ColorUtils.setAlphaComponent(backgroundColor, 255));
             }
 
-            // Calculate the animation options for this change
+            AnimationOptions animOptions = null;
             if (Flags.moveAnimationOptionsToChange()) {
-                AnimationOptions animOptions = null;
-                if (Flags.customAnimationsBehindTranslucent() && activityRecord != null) {
-                    if (activityAboveAnimationOptions != null) {
-                        // Inherit the options from one of the changes on top of this
-                        animOptions = activityAboveAnimationOptions;
-                    } else {
-                        // Create the options based on this change's custom animations and layout
-                        // parameters
-                        animOptions = getOptions(activityRecord /* customAnimActivity */,
-                                activityRecord /* animLpActivity */);
-                        if (!change.hasFlags(FLAG_TRANSLUCENT)) {
-                            // If this change is not translucent, its options are going to be
-                            // inherited by the changes below
-                            activityAboveAnimationOptions = animOptions;
-                        }
-                    }
-                } else if (activityRecord != null && animOptionsForActivityTransition != null) {
-                    // Use the same options from the top activity for all the activities
+                if (activityRecord != null && animOptionsForActivityTransition != null) {
                     animOptions = animOptionsForActivityTransition;
                 } else if (Flags.activityEmbeddingOverlayPresentationFlag()
                         && isEmbeddedTaskFragment) {
@@ -3074,42 +3050,28 @@
     @Nullable
     private static AnimationOptions calculateAnimationOptionsForActivityTransition(
             @TransitionType int type, @NonNull ArrayList<ChangeInfo> sortedTargets) {
+        TransitionInfo.AnimationOptions animOptions = null;
+
+        // Check if the top-most app is an activity (ie. activity->activity). If so, make sure
+        // to honor its custom transition options.
         WindowContainer<?> topApp = null;
         for (int i = 0; i < sortedTargets.size(); i++) {
-            if (!isWallpaper(sortedTargets.get(i).mContainer)) {
-                topApp = sortedTargets.get(i).mContainer;
-                break;
-            }
+            if (isWallpaper(sortedTargets.get(i).mContainer)) continue;
+            topApp = sortedTargets.get(i).mContainer;
+            break;
         }
-        ActivityRecord animLpActivity = findAnimLayoutParamsActivityRecord(type, sortedTargets);
-        return getOptions(topApp.asActivityRecord() /* customAnimActivity */,
-                animLpActivity /* animLpActivity */);
-    }
-
-    /**
-     * Updates and returns animOptions with the layout parameters of animLpActivity
-     * @param customAnimActivity the activity that drives the custom animation options
-     * @param animLpActivity the activity that drives the animation options with its layout
-     *                       parameters
-     * @return the options extracted from the provided activities
-     */
-    @Nullable
-    private static AnimationOptions getOptions(@Nullable ActivityRecord customAnimActivity,
-            @Nullable ActivityRecord animLpActivity) {
-        AnimationOptions animOptions = null;
-        // Custom
-        if (customAnimActivity != null) {
-            animOptions = addCustomActivityTransition(customAnimActivity, true /* open */,
-                    animOptions);
-            animOptions = addCustomActivityTransition(customAnimActivity, false /* open */,
+        if (topApp.asActivityRecord() != null) {
+            final ActivityRecord topActivity = topApp.asActivityRecord();
+            animOptions = addCustomActivityTransition(topActivity, true/* open */,
+                    null /* animOptions */);
+            animOptions = addCustomActivityTransition(topActivity, false/* open */,
                     animOptions);
         }
-
-        // Layout parameters
+        final ActivityRecord animLpActivity =
+                findAnimLayoutParamsActivityRecord(type, sortedTargets);
         final WindowState mainWindow = animLpActivity != null
                 ? animLpActivity.findMainWindow() : null;
-        final WindowManager.LayoutParams animLp = mainWindow != null ? mainWindow.mAttrs : null;
-
+        WindowManager.LayoutParams animLp = mainWindow != null ? mainWindow.mAttrs : null;
         if (animLp != null && animLp.type != TYPE_APPLICATION_STARTING
                 && animLp.windowAnimations != 0) {
             // Don't send animation options if no windowAnimations have been set or if the we
@@ -3249,9 +3211,10 @@
         return ancestor;
     }
 
-    @Nullable
-    private static ActivityRecord findAnimLayoutParamsActivityRecord(
-            @TransitionType int transit, @NonNull List<ChangeInfo> sortedTargets) {
+    private static ActivityRecord findAnimLayoutParamsActivityRecord(int type,
+            ArrayList<ChangeInfo> sortedTargets) {
+        // Find the layout params of the top-most application window that is part of the
+        // transition, which is what will control the animation theme.
         final ArraySet<Integer> activityTypes = new ArraySet<>();
         final int targetCount = sortedTargets.size();
         for (int i = 0; i < targetCount; ++i) {
@@ -3271,7 +3234,12 @@
             // activity through the layout parameter animation style.
             return null;
         }
+        return findAnimLayoutParamsActivityRecord(sortedTargets, type, activityTypes);
+    }
 
+    private static ActivityRecord findAnimLayoutParamsActivityRecord(
+            List<ChangeInfo> sortedTargets,
+            @TransitionType int transit, ArraySet<Integer> activityTypes) {
         // Remote animations always win, but fullscreen windows override non-fullscreen windows.
         ActivityRecord result = lookForTopWindowWithFilter(sortedTargets,
                 w -> w.getRemoteAnimationDefinition() != null
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 459a509..33f2dd1 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1186,9 +1186,13 @@
     public static WindowManagerService main(final Context context, final InputManagerService im,
             final boolean showBootMsgs, WindowManagerPolicy policy,
             ActivityTaskManagerService atm) {
+        // Using SysUI context to have access to Material colors extracted from Wallpaper.
+        final AppCompatConfiguration appCompat = new AppCompatConfiguration(
+                ActivityThread.currentActivityThread().getSystemUiContext());
+
         final WindowManagerService wms = main(context, im, showBootMsgs, policy, atm,
                 new DisplayWindowSettingsProvider(), SurfaceControl.Transaction::new,
-                SurfaceControl.Builder::new);
+                SurfaceControl.Builder::new, appCompat);
         WindowManagerGlobal.setWindowManagerServiceForSystemProcess(wms);
         return wms;
     }
@@ -1202,12 +1206,14 @@
             final boolean showBootMsgs, WindowManagerPolicy policy, ActivityTaskManagerService atm,
             DisplayWindowSettingsProvider displayWindowSettingsProvider,
             Supplier<SurfaceControl.Transaction> transactionFactory,
-            Supplier<SurfaceControl.Builder> surfaceControlFactory) {
+            Supplier<SurfaceControl.Builder> surfaceControlFactory,
+            AppCompatConfiguration appCompat) {
+
         final WindowManagerService[] wms = new WindowManagerService[1];
         DisplayThread.getHandler().runWithScissors(() ->
                 wms[0] = new WindowManagerService(context, im, showBootMsgs, policy, atm,
                         displayWindowSettingsProvider, transactionFactory,
-                        surfaceControlFactory), 0);
+                        surfaceControlFactory, appCompat), 0);
         return wms[0];
     }
 
@@ -1231,7 +1237,8 @@
             boolean showBootMsgs, WindowManagerPolicy policy, ActivityTaskManagerService atm,
             DisplayWindowSettingsProvider displayWindowSettingsProvider,
             Supplier<SurfaceControl.Transaction> transactionFactory,
-            Supplier<SurfaceControl.Builder> surfaceControlFactory) {
+            Supplier<SurfaceControl.Builder> surfaceControlFactory,
+            AppCompatConfiguration appCompat) {
         installLock(this, INDEX_WINDOW);
         mGlobalLock = atm.getGlobalLock();
         mAtmService = atm;
@@ -1283,9 +1290,7 @@
                     | WindowInsets.Type.navigationBars();
         }
 
-        mAppCompatConfiguration = new AppCompatConfiguration(
-                // Using SysUI context to have access to Material colors extracted from Wallpaper.
-                ActivityThread.currentActivityThread().getSystemUiContext());
+        mAppCompatConfiguration = appCompat;
 
         mInputManager = inputManager; // Must be before createDisplayContentLocked.
         mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index b527630..1640ad3 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2754,10 +2754,16 @@
      * @param outRegion The region to update.
      */
     private void updateRegionForModalActivityWindow(Region outRegion) {
-        // If the inner bounds of letterbox is available, then it will be used as the
-        // touchable region so it won't cover the touchable letterbox and the touch
-        // events can slip to activity from letterbox.
-        mActivityRecord.getLetterboxInnerBounds(mTmpRect);
+        if (Flags.scrollingFromLetterbox()) {
+            // Touchable region expands to the letterbox area to react to scrolls from letterbox.
+            mTmpRect.setEmpty();
+        } else {
+            // If the activity is letterboxed and scrolling from letterbox is disabled, limit the
+            // touchable region to the activity. This way, the letterbox area is exposed to react
+            // to touch events, and the touch events can slip from the activity from letterbox.
+            mActivityRecord.getLetterboxInnerBounds(mTmpRect);
+        }
+
         if (mTmpRect.isEmpty()) {
             final Rect transformedBounds = mActivityRecord.getFixedRotationTransformDisplayBounds();
             if (transformedBounds != null) {
diff --git a/services/core/java/com/android/server/wm/utils/TEST_MAPPING b/services/core/java/com/android/server/wm/utils/TEST_MAPPING
index aa69d2a..6f34cd0 100644
--- a/services/core/java/com/android/server/wm/utils/TEST_MAPPING
+++ b/services/core/java/com/android/server/wm/utils/TEST_MAPPING
@@ -1,18 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "WmTests",
-      "options": [
-        {
-          "include-filter": "com.android.server.wm.utils"
-        },
-        {
-          "include-annotation": "android.platform.test.annotations.Presubmit"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "WmTests_wm_utils_Presubmit"
     }
   ]
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index b6e45fc8..6314b85 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -10625,8 +10625,16 @@
                 final DevicePolicyData policyData = getUserData(userId);
                 if (transitionCheckNeeded) {
                     // Optional state transition check for non-ADB case.
-                    checkUserProvisioningStateTransition(policyData.mUserProvisioningState,
-                            newState);
+                    try {
+                        checkUserProvisioningStateTransition(
+                                policyData.mUserProvisioningState,
+                                newState);
+
+                    } catch (IllegalStateException e) {
+                        Slogf.e(LOG_TAG,
+                                "Exception caught while changing provisioning state", e);
+                        throw e;
+                    }
                 }
                 policyData.mUserProvisioningState = newState;
                 saveSettingsLocked(userId);
@@ -10637,6 +10645,10 @@
     }
 
     private void checkUserProvisioningStateTransition(int currentState, int newState) {
+        if (Flags.userProvisioningSameState()) {
+            Preconditions.checkState(newState != currentState, "New state cannot"
+                    + " be the same as the current state: [" + newState + "]");
+        }
         // Valid transitions for normal use-cases.
         switch (currentState) {
             case DevicePolicyManager.STATE_USER_UNMANAGED:
diff --git a/services/foldables/devicestateprovider/TEST_MAPPING b/services/foldables/devicestateprovider/TEST_MAPPING
index 47de131..0538381 100644
--- a/services/foldables/devicestateprovider/TEST_MAPPING
+++ b/services/foldables/devicestateprovider/TEST_MAPPING
@@ -1,12 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "foldable-device-state-provider-tests",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "foldable-device-state-provider-tests"
     }
   ]
 }
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/Android.bp b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/Android.bp
index 1db9e8d..6393e11 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/Android.bp
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/Android.bp
@@ -1,7 +1,7 @@
 aconfig_declarations {
     name: "device_state_flags",
     package: "com.android.server.policy.feature.flags",
-    container: "system_ext",
+    container: "system",
     srcs: [
         "device_state_flags.aconfig",
     ],
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig
index f827b55..21e33dd 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig
@@ -1,5 +1,5 @@
 package: "com.android.server.policy.feature.flags"
-container: "system_ext"
+container: "system"
 
 flag {
     name: "enable_dual_display_blocking"
diff --git a/services/incremental/TEST_MAPPING b/services/incremental/TEST_MAPPING
index 4c9403c..cbb9962 100644
--- a/services/incremental/TEST_MAPPING
+++ b/services/incremental/TEST_MAPPING
@@ -1,12 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "CtsPackageManagerStatsHostTestCases",
-      "options": [
-        {
-          "include-filter": "com.android.cts.packagemanager.stats.host.PackageInstallerV2StatsTests"
-        }
-      ]
+      "name": "CtsPackageManagerStatsHostTestCases_host_packageinstallerv2statstests"
     },
     {
       "name": "CtsPackageManagerIncrementalStatsHostTestCases",
diff --git a/services/people/java/com/android/server/people/TEST_MAPPING b/services/people/java/com/android/server/people/TEST_MAPPING
index 55b355c..8677337 100644
--- a/services/people/java/com/android/server/people/TEST_MAPPING
+++ b/services/people/java/com/android/server/people/TEST_MAPPING
@@ -1,12 +1,7 @@
 {
     "presubmit": [
         {
-            "name": "FrameworksServicesTests",
-            "options": [
-                {
-                    "include-filter": "com.android.server.people.data"
-                }
-            ]
+            "name": "FrameworksServicesTests_people_data"
         }
     ]
 }
\ No newline at end of file
diff --git a/services/permission/TEST_MAPPING b/services/permission/TEST_MAPPING
index 4de4a56..af4aaf9 100644
--- a/services/permission/TEST_MAPPING
+++ b/services/permission/TEST_MAPPING
@@ -105,26 +105,10 @@
             ]
         },
         {
-            "name": "CtsVirtualDevicesAudioTestCases",
-            "options": [
-                {
-                    "exclude-annotation": "androidx.test.filters.FlakyTest"
-                },
-                {
-                    "include-filter": "android.virtualdevice.cts.audio.VirtualAudioPermissionTest"
-                }
-            ]
+            "name": "CtsVirtualDevicesAudioTestCases_audio_virtualaudiopermissiontest"
         },
         {
-            "name": "CtsVirtualDevicesAppLaunchTestCases",
-            "options": [
-                {
-                    "exclude-annotation": "androidx.test.filters.FlakyTest"
-                },
-                {
-                    "include-filter": "android.virtualdevice.cts.applaunch.VirtualDevicePermissionTest"
-                }
-            ]
+            "name": "CtsVirtualDevicesAppLaunchTestCases_applaunch_virtualdevicepermissiontest"
         }
     ],
     "imports": [
diff --git a/services/print/java/com/android/server/print/TEST_MAPPING b/services/print/java/com/android/server/print/TEST_MAPPING
index 4fa8822..1033b1a 100644
--- a/services/print/java/com/android/server/print/TEST_MAPPING
+++ b/services/print/java/com/android/server/print/TEST_MAPPING
@@ -1,12 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "CtsPrintTestCases",
-      "options": [
-        {
-          "include-annotation": "android.platform.test.annotations.Presubmit"
-        }
-      ]
+      "name": "CtsPrintTestCases_Presubmit"
     }
   ]
 }
diff --git a/services/tests/InputMethodSystemServerTests/TEST_MAPPING b/services/tests/InputMethodSystemServerTests/TEST_MAPPING
index de9f771..7313941 100644
--- a/services/tests/InputMethodSystemServerTests/TEST_MAPPING
+++ b/services/tests/InputMethodSystemServerTests/TEST_MAPPING
@@ -1,22 +1,12 @@
 {
   "presubmit": [
     {
-      "name": "FrameworksInputMethodSystemServerTests",
-      "options": [
-        {"include-filter": "com.android.server.inputmethod"},
-        {"exclude-annotation": "androidx.test.filters.FlakyTest"},
-        {"exclude-annotation": "org.junit.Ignore"}
-      ]
+      "name": "FrameworksInputMethodSystemServerTests_server_inputmethod"
     }
   ],
   "postsubmit": [
     {
-      "name": "FrameworksImeTests",
-      "options": [
-        {"include-filter": "com.android.inputmethodservice"},
-        {"exclude-annotation": "androidx.test.filters.FlakyTest"},
-        {"exclude-annotation": "org.junit.Ignore"}
-      ]
+      "name": "FrameworksImeTests_android_inputmethodservice"
     }
   ]
 }
diff --git a/services/tests/PackageManagerServiceTests/TEST_MAPPING b/services/tests/PackageManagerServiceTests/TEST_MAPPING
index 5d96af9..13ba317 100644
--- a/services/tests/PackageManagerServiceTests/TEST_MAPPING
+++ b/services/tests/PackageManagerServiceTests/TEST_MAPPING
@@ -4,21 +4,7 @@
       "name": "AppEnumerationInternalTests"
     },
     {
-      "name": "PackageManagerServiceServerTests",
-      "options": [
-        {
-          "include-filter": "com.android.server.pm."
-        },
-        {
-          "include-annotation": "android.platform.test.annotations.Presubmit"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        }
-      ]
+      "name": "PackageManagerServiceServerTests_server_pm_Presubmit"
     }
   ],
   "postsubmit": [
@@ -26,21 +12,7 @@
       "name": "PackageManagerServiceHostTests"
     },
     {
-      "name": "PackageManagerServiceServerTests",
-      "options": [
-        {
-          "include-filter": "com.android.server.pm."
-        },
-        {
-          "include-annotation": "android.platform.test.annotations.Postsubmit"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        }
-      ]
+      "name": "PackageManagerServiceServerTests_server_pm_Postsubmit"
     }
   ],
   "kernel-presubmit": [
diff --git a/services/tests/appfunctions/Android.bp b/services/tests/appfunctions/Android.bp
index b5cf986..9560ec9 100644
--- a/services/tests/appfunctions/Android.bp
+++ b/services/tests/appfunctions/Android.bp
@@ -45,8 +45,8 @@
     ],
 
     libs: [
-        "android.test.base",
-        "android.test.runner",
+        "android.test.base.stubs.system",
+        "android.test.runner.stubs.system",
     ],
 
     certificate: "platform",
diff --git a/services/tests/appfunctions/src/android/app/appfunctions/AppFunctionRuntimeMetadataTest.kt b/services/tests/appfunctions/src/android/app/appfunctions/AppFunctionRuntimeMetadataTest.kt
index 650e520..dbbb2fe 100644
--- a/services/tests/appfunctions/src/android/app/appfunctions/AppFunctionRuntimeMetadataTest.kt
+++ b/services/tests/appfunctions/src/android/app/appfunctions/AppFunctionRuntimeMetadataTest.kt
@@ -101,4 +101,15 @@
 
         assertThat(actualPackageName).isEqualTo(expectedPackageName)
     }
+
+    @Test
+    fun testBuild() {
+        val runtimeMetadata = AppFunctionRuntimeMetadata.Builder("com.pkg", "funcId").build()
+
+        assertThat(runtimeMetadata.packageName).isEqualTo("com.pkg")
+        assertThat(runtimeMetadata.functionId).isEqualTo("funcId")
+        assertThat(runtimeMetadata.enabled).isNull()
+        assertThat(runtimeMetadata.appFunctionStaticMetadataQualifiedId)
+            .isEqualTo("android\$apps-db/app_functions#com.pkg/funcId")
+    }
 }
diff --git a/services/tests/appfunctions/src/com/android/server/appfunctions/FutureAppSearchSessionTest.kt b/services/tests/appfunctions/src/com/android/server/appfunctions/FutureAppSearchSessionTest.kt
index edcbb9e..e761e8d 100644
--- a/services/tests/appfunctions/src/com/android/server/appfunctions/FutureAppSearchSessionTest.kt
+++ b/services/tests/appfunctions/src/com/android/server/appfunctions/FutureAppSearchSessionTest.kt
@@ -84,7 +84,7 @@
             val schema = session.setSchema(setSchemaRequest)
             assertThat(schema.get()).isNotNull()
             val appFunctionRuntimeMetadata =
-                AppFunctionRuntimeMetadata.Builder(TEST_PACKAGE_NAME, TEST_FUNCTION_ID, "").build()
+                AppFunctionRuntimeMetadata.Builder(TEST_PACKAGE_NAME, TEST_FUNCTION_ID).build()
             val putDocumentsRequest: PutDocumentsRequest =
                 PutDocumentsRequest.Builder()
                     .addGenericDocuments(appFunctionRuntimeMetadata)
@@ -110,7 +110,7 @@
             val schema = session.setSchema(setSchemaRequest)
             assertThat(schema.get()).isNotNull()
             val appFunctionRuntimeMetadata =
-                AppFunctionRuntimeMetadata.Builder(TEST_PACKAGE_NAME, TEST_FUNCTION_ID, "").build()
+                AppFunctionRuntimeMetadata.Builder(TEST_PACKAGE_NAME, TEST_FUNCTION_ID).build()
             val putDocumentsRequest: PutDocumentsRequest =
                 PutDocumentsRequest.Builder()
                     .addGenericDocuments(appFunctionRuntimeMetadata)
@@ -144,7 +144,7 @@
             val schema = session.setSchema(setSchemaRequest)
             assertThat(schema.get()).isNotNull()
             val appFunctionRuntimeMetadata =
-                AppFunctionRuntimeMetadata.Builder(TEST_PACKAGE_NAME, TEST_FUNCTION_ID, "").build()
+                AppFunctionRuntimeMetadata.Builder(TEST_PACKAGE_NAME, TEST_FUNCTION_ID).build()
             val putDocumentsRequest: PutDocumentsRequest =
                 PutDocumentsRequest.Builder()
                     .addGenericDocuments(appFunctionRuntimeMetadata)
@@ -175,7 +175,7 @@
                     .build()
             session.setSchema(setSchemaRequest).get()
             val appFunctionRuntimeMetadata =
-                AppFunctionRuntimeMetadata.Builder(TEST_PACKAGE_NAME, TEST_FUNCTION_ID, "").build()
+                AppFunctionRuntimeMetadata.Builder(TEST_PACKAGE_NAME, TEST_FUNCTION_ID).build()
             val putDocumentsRequest: PutDocumentsRequest =
                 PutDocumentsRequest.Builder()
                     .addGenericDocuments(appFunctionRuntimeMetadata)
diff --git a/services/tests/appfunctions/src/com/android/server/appfunctions/FutureGlobalSearchSessionTest.kt b/services/tests/appfunctions/src/com/android/server/appfunctions/FutureGlobalSearchSessionTest.kt
index 38cba65..7fe7263 100644
--- a/services/tests/appfunctions/src/com/android/server/appfunctions/FutureGlobalSearchSessionTest.kt
+++ b/services/tests/appfunctions/src/com/android/server/appfunctions/FutureGlobalSearchSessionTest.kt
@@ -94,8 +94,7 @@
             val schema = session.setSchema(setSchemaRequest)
             assertThat(schema.get()).isNotNull()
             val appFunctionRuntimeMetadata =
-                AppFunctionRuntimeMetadata.Builder(TEST_TARGET_PKG_NAME, TEST_FUNCTION_ID, "")
-                    .build()
+                AppFunctionRuntimeMetadata.Builder(TEST_TARGET_PKG_NAME, TEST_FUNCTION_ID).build()
             val putDocumentsRequest: PutDocumentsRequest =
                 PutDocumentsRequest.Builder()
                     .addGenericDocuments(appFunctionRuntimeMetadata)
diff --git a/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt b/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
index 3ebf689..63cf7bf 100644
--- a/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
+++ b/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
@@ -16,17 +16,28 @@
 package com.android.server.appfunctions
 
 import android.app.appfunctions.AppFunctionRuntimeMetadata
+import android.app.appsearch.AppSearchBatchResult
 import android.app.appsearch.AppSearchManager
-import android.app.appsearch.AppSearchManager.SearchContext
+import android.app.appsearch.AppSearchResult
+import android.app.appsearch.AppSearchSchema
+import android.app.appsearch.GenericDocument
+import android.app.appsearch.GetByDocumentIdRequest
+import android.app.appsearch.GetSchemaResponse
 import android.app.appsearch.PutDocumentsRequest
+import android.app.appsearch.RemoveByDocumentIdRequest
+import android.app.appsearch.SearchResult
+import android.app.appsearch.SearchSpec
 import android.app.appsearch.SetSchemaRequest
+import android.app.appsearch.SetSchemaResponse
 import android.util.ArrayMap
 import android.util.ArraySet
+import android.util.Log
 import androidx.test.platform.app.InstrumentationRegistry
+import com.android.internal.infra.AndroidFuture
+import com.android.server.appfunctions.FutureAppSearchSession.FutureSearchResults
 import com.google.common.truth.Truth.assertThat
 import com.google.common.util.concurrent.MoreExecutors
-import org.junit.After
-import org.junit.Before
+import java.util.concurrent.atomic.AtomicBoolean
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
@@ -36,46 +47,21 @@
     private val context = InstrumentationRegistry.getInstrumentation().targetContext
     private val appSearchManager = context.getSystemService(AppSearchManager::class.java)
     private val testExecutor = MoreExecutors.directExecutor()
-
-    @Before
-    @After
-    fun clearData() {
-        val searchContext = SearchContext.Builder(TEST_DB).build()
-        FutureAppSearchSessionImpl(appSearchManager, testExecutor, searchContext).use {
-            val setSchemaRequest = SetSchemaRequest.Builder().setForceOverride(true).build()
-            it.setSchema(setSchemaRequest).get()
-        }
-    }
+    private val packageManager = context.packageManager
 
     @Test
     fun getPackageToFunctionIdMap() {
-        val searchContext: SearchContext = SearchContext.Builder(TEST_DB).build()
+        val searchSession = FakeSearchSession()
         val functionRuntimeMetadata =
-            AppFunctionRuntimeMetadata.Builder(TEST_TARGET_PKG_NAME, "testFunctionId", "").build()
-        val setSchemaRequest =
-            SetSchemaRequest.Builder()
-                .addSchemas(AppFunctionRuntimeMetadata.createParentAppFunctionRuntimeSchema())
-                .addSchemas(
-                    AppFunctionRuntimeMetadata.createAppFunctionRuntimeSchema(TEST_TARGET_PKG_NAME)
-                )
-                .build()
+            AppFunctionRuntimeMetadata.Builder(TEST_TARGET_PKG_NAME, "testFunctionId").build()
         val putDocumentsRequest: PutDocumentsRequest =
             PutDocumentsRequest.Builder().addGenericDocuments(functionRuntimeMetadata).build()
-        FutureAppSearchSessionImpl(appSearchManager, testExecutor, searchContext).use {
-            val setSchemaResponse = it.setSchema(setSchemaRequest).get()
-            assertThat(setSchemaResponse).isNotNull()
-            val appSearchBatchResult = it.put(putDocumentsRequest).get()
-            assertThat(appSearchBatchResult.isSuccess).isTrue()
-        }
+        searchSession.put(putDocumentsRequest).get()
 
-        val metadataSyncAdapter =
-            MetadataSyncAdapter(
-                testExecutor,
-                FutureAppSearchSessionImpl(appSearchManager, testExecutor, searchContext),
-            )
         val packageToFunctionIdMap =
-            metadataSyncAdapter.getPackageToFunctionIdMap(
-                AppFunctionRuntimeMetadata.RUNTIME_SCHEMA_TYPE,
+            MetadataSyncAdapter.getPackageToFunctionIdMap(
+                searchSession,
+                "fakeSchema",
                 AppFunctionRuntimeMetadata.PROPERTY_FUNCTION_ID,
                 AppFunctionRuntimeMetadata.PROPERTY_PACKAGE_NAME,
             )
@@ -86,22 +72,15 @@
 
     @Test
     fun getPackageToFunctionIdMap_multipleDocuments() {
-        val searchContext: SearchContext = SearchContext.Builder(TEST_DB).build()
+        val searchSession = FakeSearchSession()
         val functionRuntimeMetadata =
-            AppFunctionRuntimeMetadata.Builder(TEST_TARGET_PKG_NAME, "testFunctionId", "").build()
+            AppFunctionRuntimeMetadata.Builder(TEST_TARGET_PKG_NAME, "testFunctionId").build()
         val functionRuntimeMetadata1 =
-            AppFunctionRuntimeMetadata.Builder(TEST_TARGET_PKG_NAME, "testFunctionId1", "").build()
+            AppFunctionRuntimeMetadata.Builder(TEST_TARGET_PKG_NAME, "testFunctionId1").build()
         val functionRuntimeMetadata2 =
-            AppFunctionRuntimeMetadata.Builder(TEST_TARGET_PKG_NAME, "testFunctionId2", "").build()
+            AppFunctionRuntimeMetadata.Builder(TEST_TARGET_PKG_NAME, "testFunctionId2").build()
         val functionRuntimeMetadata3 =
-            AppFunctionRuntimeMetadata.Builder(TEST_TARGET_PKG_NAME, "testFunctionId3", "").build()
-        val setSchemaRequest =
-            SetSchemaRequest.Builder()
-                .addSchemas(AppFunctionRuntimeMetadata.createParentAppFunctionRuntimeSchema())
-                .addSchemas(
-                    AppFunctionRuntimeMetadata.createAppFunctionRuntimeSchema(TEST_TARGET_PKG_NAME)
-                )
-                .build()
+            AppFunctionRuntimeMetadata.Builder(TEST_TARGET_PKG_NAME, "testFunctionId3").build()
         val putDocumentsRequest: PutDocumentsRequest =
             PutDocumentsRequest.Builder()
                 .addGenericDocuments(
@@ -111,20 +90,11 @@
                     functionRuntimeMetadata3,
                 )
                 .build()
-        FutureAppSearchSessionImpl(appSearchManager, testExecutor, searchContext).use {
-            val setSchemaResponse = it.setSchema(setSchemaRequest).get()
-            assertThat(setSchemaResponse).isNotNull()
-            val appSearchBatchResult = it.put(putDocumentsRequest).get()
-            assertThat(appSearchBatchResult.isSuccess).isTrue()
-        }
+        searchSession.put(putDocumentsRequest).get()
 
-        val metadataSyncAdapter =
-            MetadataSyncAdapter(
-                testExecutor,
-                FutureAppSearchSessionImpl(appSearchManager, testExecutor, searchContext),
-            )
         val packageToFunctionIdMap =
-            metadataSyncAdapter.getPackageToFunctionIdMap(
+            MetadataSyncAdapter.getPackageToFunctionIdMap(
+                searchSession,
                 AppFunctionRuntimeMetadata.RUNTIME_SCHEMA_TYPE,
                 AppFunctionRuntimeMetadata.PROPERTY_FUNCTION_ID,
                 AppFunctionRuntimeMetadata.PROPERTY_PACKAGE_NAME,
@@ -159,6 +129,29 @@
     }
 
     @Test
+    fun syncMetadata_noDiff() {
+        val runtimeSearchSession = FakeSearchSession()
+        val staticSearchSession = FakeSearchSession()
+        val functionRuntimeMetadata =
+            AppFunctionRuntimeMetadata.Builder(TEST_TARGET_PKG_NAME, "testFunctionId").build()
+        val putDocumentsRequest: PutDocumentsRequest =
+            PutDocumentsRequest.Builder().addGenericDocuments(functionRuntimeMetadata).build()
+        runtimeSearchSession.put(putDocumentsRequest).get()
+        staticSearchSession.put(putDocumentsRequest).get()
+        val metadataSyncAdapter =
+            MetadataSyncAdapter(
+                testExecutor,
+                runtimeSearchSession,
+                staticSearchSession,
+                packageManager,
+            )
+
+        val submitSyncRequest = metadataSyncAdapter.submitSyncRequest()
+
+        assertThat(submitSyncRequest.get()).isTrue()
+    }
+
+    @Test
     fun getAddedFunctionsDiffMap_addedFunction() {
         val staticPackageToFunctionMap: ArrayMap<String, ArraySet<String>> = ArrayMap()
         staticPackageToFunctionMap.putAll(
@@ -180,6 +173,28 @@
     }
 
     @Test
+    fun syncMetadata_addedFunction() {
+        val runtimeSearchSession = FakeSearchSession()
+        val staticSearchSession = FakeSearchSession()
+        val functionRuntimeMetadata =
+            AppFunctionRuntimeMetadata.Builder(TEST_TARGET_PKG_NAME, "testFunctionId").build()
+        val putDocumentsRequest: PutDocumentsRequest =
+            PutDocumentsRequest.Builder().addGenericDocuments(functionRuntimeMetadata).build()
+        staticSearchSession.put(putDocumentsRequest).get()
+        val metadataSyncAdapter =
+            MetadataSyncAdapter(
+                testExecutor,
+                runtimeSearchSession,
+                staticSearchSession,
+                packageManager,
+            )
+
+        val submitSyncRequest = metadataSyncAdapter.submitSyncRequest()
+
+        assertThat(submitSyncRequest.get()).isTrue()
+    }
+
+    @Test
     fun getAddedFunctionsDiffMap_addedFunctionNewPackage() {
         val staticPackageToFunctionMap: ArrayMap<String, ArraySet<String>> = ArrayMap()
         staticPackageToFunctionMap.putAll(
@@ -215,6 +230,28 @@
     }
 
     @Test
+    fun syncMetadata_removedFunction() {
+        val runtimeSearchSession = FakeSearchSession()
+        val staticSearchSession = FakeSearchSession()
+        val functionRuntimeMetadata =
+            AppFunctionRuntimeMetadata.Builder(TEST_TARGET_PKG_NAME, "testFunctionId").build()
+        val putDocumentsRequest: PutDocumentsRequest =
+            PutDocumentsRequest.Builder().addGenericDocuments(functionRuntimeMetadata).build()
+        runtimeSearchSession.put(putDocumentsRequest).get()
+        val metadataSyncAdapter =
+            MetadataSyncAdapter(
+                testExecutor,
+                runtimeSearchSession,
+                staticSearchSession,
+                packageManager,
+            )
+
+        val submitSyncRequest = metadataSyncAdapter.submitSyncRequest()
+
+        assertThat(submitSyncRequest.get()).isTrue()
+    }
+
+    @Test
     fun getRemovedFunctionsDiffMap_noDiff() {
         val staticPackageToFunctionMap: ArrayMap<String, ArraySet<String>> = ArrayMap()
         staticPackageToFunctionMap.putAll(
@@ -271,4 +308,100 @@
         const val TEST_DB: String = "test_db"
         const val TEST_TARGET_PKG_NAME = "com.android.frameworks.appfunctionstests"
     }
+
+    class FakeSearchSession : FutureAppSearchSession {
+        private val schemas: MutableSet<AppSearchSchema> = mutableSetOf()
+        private val genericDocumentMutableMap: MutableMap<String, GenericDocument> = mutableMapOf()
+
+        override fun close() {
+            Log.d("FakeRuntimeMetadataSearchSession", "Closing session")
+        }
+
+        override fun setSchema(
+            setSchemaRequest: SetSchemaRequest
+        ): AndroidFuture<SetSchemaResponse> {
+            schemas.addAll(setSchemaRequest.schemas)
+            return AndroidFuture.completedFuture(SetSchemaResponse.Builder().build())
+        }
+
+        override fun getSchema(): AndroidFuture<GetSchemaResponse> {
+            val resultBuilder = GetSchemaResponse.Builder()
+            for (schema in schemas) {
+                resultBuilder.addSchema(schema)
+            }
+            return AndroidFuture.completedFuture(resultBuilder.build())
+        }
+
+        override fun put(
+            putDocumentsRequest: PutDocumentsRequest
+        ): AndroidFuture<AppSearchBatchResult<String, Void>> {
+            for (document in putDocumentsRequest.genericDocuments) {
+                genericDocumentMutableMap[document.id] = document
+            }
+            val batchResultBuilder = AppSearchBatchResult.Builder<String, Void>()
+            for (document in putDocumentsRequest.genericDocuments) {
+                batchResultBuilder.setResult(document.id, AppSearchResult.newSuccessfulResult(null))
+            }
+            return AndroidFuture.completedFuture(batchResultBuilder.build())
+        }
+
+        override fun remove(
+            removeRequest: RemoveByDocumentIdRequest
+        ): AndroidFuture<AppSearchBatchResult<String, Void>> {
+            for (documentId in removeRequest.ids) {
+                if (!genericDocumentMutableMap.keys.contains(documentId)) {
+                    throw IllegalStateException("Document $documentId does not exist")
+                }
+            }
+            val batchResultBuilder = AppSearchBatchResult.Builder<String, Void>()
+            for (id in removeRequest.ids) {
+                batchResultBuilder.setResult(id, AppSearchResult.newSuccessfulResult(null))
+            }
+            return AndroidFuture.completedFuture(batchResultBuilder.build())
+        }
+
+        override fun getByDocumentId(
+            getRequest: GetByDocumentIdRequest
+        ): AndroidFuture<AppSearchBatchResult<String, GenericDocument>> {
+            val batchResultBuilder = AppSearchBatchResult.Builder<String, GenericDocument>()
+            for (documentId in getRequest.ids) {
+                if (!genericDocumentMutableMap.keys.contains(documentId)) {
+                    throw IllegalStateException("Document $documentId does not exist")
+                }
+                batchResultBuilder.setResult(
+                    documentId,
+                    AppSearchResult.newSuccessfulResult(genericDocumentMutableMap[documentId]),
+                )
+            }
+            return AndroidFuture.completedFuture(batchResultBuilder.build())
+        }
+
+        override fun search(
+            queryExpression: String,
+            searchSpec: SearchSpec,
+        ): AndroidFuture<FutureSearchResults> {
+            val futureSearchResults =
+                object : FutureSearchResults {
+                    val hasNextPage = AtomicBoolean(false)
+
+                    override fun getNextPage(): AndroidFuture<MutableList<SearchResult>> {
+                        val searchResultMutableList: MutableList<SearchResult> =
+                            genericDocumentMutableMap.values
+                                .map {
+                                    SearchResult.Builder(TEST_TARGET_PKG_NAME, TEST_DB)
+                                        .setGenericDocument(it)
+                                        .build()
+                                }
+                                .toMutableList()
+                        if (!hasNextPage.get()) {
+                            hasNextPage.set(true)
+                            return AndroidFuture.completedFuture(searchResultMutableList)
+                        } else {
+                            return AndroidFuture.completedFuture(mutableListOf())
+                        }
+                    }
+                }
+            return AndroidFuture.completedFuture(futureSearchResults)
+        }
+    }
 }
diff --git a/services/tests/displayservicetests/Android.bp b/services/tests/displayservicetests/Android.bp
index fe73025..36ea241 100644
--- a/services/tests/displayservicetests/Android.bp
+++ b/services/tests/displayservicetests/Android.bp
@@ -22,6 +22,7 @@
     static_libs: [
         "androidx.test.ext.junit",
         "androidx.test.rules",
+        "compatibility-device-util-axt",
         "flag-junit",
         "frameworks-base-testutils",
         "junit",
@@ -48,6 +49,10 @@
         "automotive-tests",
     ],
 
+    data: [
+        ":DisplayManagerTestApp",
+    ],
+
     certificate: "platform",
 
     dxflags: ["--multi-dex"],
diff --git a/services/tests/displayservicetests/AndroidManifest.xml b/services/tests/displayservicetests/AndroidManifest.xml
index 74260cd..37a34ee 100644
--- a/services/tests/displayservicetests/AndroidManifest.xml
+++ b/services/tests/displayservicetests/AndroidManifest.xml
@@ -39,6 +39,10 @@
                  android:testOnly="true">
         <uses-library android:name="android.test.mock" android:required="true" />
         <uses-library android:name="android.test.runner" />
+        <activity android:name="com.android.server.display.SimpleActivity"
+                  android:exported="true" />
+        <activity android:name="com.android.server.display.SimpleActivity2"
+                  android:exported="true" />
     </application>
 
     <instrumentation
diff --git a/services/tests/displayservicetests/AndroidTest.xml b/services/tests/displayservicetests/AndroidTest.xml
index 2985f98..f3697bb 100644
--- a/services/tests/displayservicetests/AndroidTest.xml
+++ b/services/tests/displayservicetests/AndroidTest.xml
@@ -23,6 +23,13 @@
         <option name="test-file-name" value="DisplayServiceTests.apk" />
     </target_preparer>
 
+    <!-- Load additional APKs onto device -->
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="install-arg" value="-t" />
+        <option name="test-file-name" value="DisplayManagerTestApp.apk" />
+    </target_preparer>
+
     <option name="test-tag" value="DisplayServiceTests" />
     <test class="com.android.tradefed.testtype.AndroidJUnitTest">
         <option name="package" value="com.android.frameworks.displayservicetests" />
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayEventDeliveryTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayEventDeliveryTest.java
new file mode 100644
index 0000000..90f6257
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayEventDeliveryTest.java
@@ -0,0 +1,441 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
+import static android.util.DisplayMetrics.DENSITY_HIGH;
+import static android.util.DisplayMetrics.DENSITY_MEDIUM;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+import android.app.ActivityManager;
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.Intent;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.platform.test.annotations.AppModeSdkSandbox;
+import android.util.Log;
+import android.util.SparseArray;
+
+import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests that applications can receive display events correctly.
+ */
+@RunWith(Parameterized.class)
+@AppModeSdkSandbox(reason = "Allow test in the SDK sandbox (does not prevent other modes).")
+public class DisplayEventDeliveryTest {
+    private static final String TAG = "DisplayEventDeliveryTest";
+
+    private static final String NAME = TAG;
+    private static final int WIDTH = 720;
+    private static final int HEIGHT = 480;
+
+    private static final int MESSAGE_LAUNCHED = 1;
+    private static final int MESSAGE_CALLBACK = 2;
+
+    private static final int DISPLAY_ADDED = 1;
+    private static final int DISPLAY_CHANGED = 2;
+    private static final int DISPLAY_REMOVED = 3;
+
+    private static final long DISPLAY_EVENT_TIMEOUT_MSEC = 100;
+    private static final long TEST_FAILURE_TIMEOUT_MSEC = 10000;
+
+    private static final String TEST_PACKAGE =
+            "com.android.servicestests.apps.displaymanagertestapp";
+    private static final String TEST_ACTIVITY = TEST_PACKAGE + ".DisplayEventActivity";
+    private static final String TEST_DISPLAYS = "DISPLAYS";
+    private static final String TEST_MESSENGER = "MESSENGER";
+
+    private final Object mLock = new Object();
+
+    private Instrumentation mInstrumentation;
+    private Context mContext;
+    private DisplayManager mDisplayManager;
+    private ActivityManager mActivityManager;
+    private ActivityManager.OnUidImportanceListener mUidImportanceListener;
+    private CountDownLatch mLatchActivityLaunch;
+    private CountDownLatch mLatchActivityCached;
+    private HandlerThread mHandlerThread;
+    private Handler mHandler;
+    private Messenger mMessenger;
+    private int mPid;
+    private int mUid;
+
+    /**
+     * Array of DisplayBundle. The test handler uses it to check if certain display events have
+     * been sent to DisplayEventActivity.
+     * Key: displayId of each new VirtualDisplay created by this test
+     * Value: DisplayBundle, storing the VirtualDisplay and its expected display events
+     *
+     * NOTE: The lock is required when adding and removing virtual displays. Otherwise it's not
+     * necessary to lock mDisplayBundles when accessing it from the test function.
+     */
+    @GuardedBy("mLock")
+    private SparseArray<DisplayBundle> mDisplayBundles;
+
+    /**
+     * Helper class to store VirtualDisplay and its corresponding display events expected to be
+     * sent to DisplayEventActivity.
+     */
+    private static final class DisplayBundle {
+        private VirtualDisplay mVirtualDisplay;
+        private final int mDisplayId;
+
+        // Display events we expect to receive before timeout
+        private final LinkedBlockingQueue<Integer> mExpectations;
+
+        DisplayBundle(VirtualDisplay display) {
+            mVirtualDisplay = display;
+            mDisplayId = display.getDisplay().getDisplayId();
+            mExpectations = new LinkedBlockingQueue<>();
+        }
+
+        public void releaseDisplay() {
+            if (mVirtualDisplay != null) {
+                mVirtualDisplay.release();
+            }
+            mVirtualDisplay = null;
+        }
+
+        /**
+         * Add the received display event from the test activity to the queue
+         *
+         * @param event The corresponding display event
+         */
+        public void addDisplayEvent(int event) {
+            Log.d(TAG, "Received " + mDisplayId + " " + event);
+            mExpectations.offer(event);
+        }
+
+
+        /**
+         * Assert that there isn't any unexpected display event from the test activity
+         */
+        public void assertNoDisplayEvents() {
+            try {
+                assertNull(mExpectations.poll(DISPLAY_EVENT_TIMEOUT_MSEC, TimeUnit.MILLISECONDS));
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        /**
+         * Wait for the expected display event from the test activity
+         *
+         * @param expect The expected display event
+         */
+        public void waitDisplayEvent(int expect) {
+            while (true) {
+                try {
+                    final Integer event;
+                    event = mExpectations.poll(TEST_FAILURE_TIMEOUT_MSEC, TimeUnit.MILLISECONDS);
+                    assertNotNull(event);
+                    if (expect == event) {
+                        Log.d(TAG, "Found    " + mDisplayId + " " + event);
+                        return;
+                    }
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        }
+    }
+
+    /**
+     * How many virtual displays to create during the test
+     */
+    @Parameter(0)
+    public int mDisplayCount;
+
+    /**
+     * True if running the test activity in cached mode
+     * False if running it in non-cached mode
+     */
+    @Parameter(1)
+    public boolean mCached;
+
+    @Parameters(name = "#{index}: {0} {1}")
+    public static Iterable<? extends Object> data() {
+        return Arrays.asList(new Object[][]{
+                {1, false}, {2, false}, {3, false}, {10, false},
+                {1, true}, {2, true}, {3, true}, {10, true}
+        });
+    }
+
+    private class TestHandler extends Handler {
+        TestHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(@NonNull Message msg) {
+            switch (msg.what) {
+                case MESSAGE_LAUNCHED:
+                    mPid = msg.arg1;
+                    mUid = msg.arg2;
+                    Log.d(TAG, "Launched " + mPid + " " + mUid);
+                    mLatchActivityLaunch.countDown();
+                    break;
+                case MESSAGE_CALLBACK:
+                    Log.d(TAG, "Callback " + msg.arg1 + " " + msg.arg2);
+                    synchronized (mLock) {
+                        // arg1: displayId
+                        DisplayBundle bundle = mDisplayBundles.get(msg.arg1);
+                        if (bundle != null) {
+                            // arg2: display event
+                            bundle.addDisplayEvent(msg.arg2);
+                        }
+                    }
+                    break;
+                default:
+                    fail("Unexpected value: " + msg.what);
+                    break;
+            }
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mContext = mInstrumentation.getContext();
+        mDisplayManager = mContext.getSystemService(DisplayManager.class);
+        mLatchActivityLaunch = new CountDownLatch(1);
+        mLatchActivityCached = new CountDownLatch(1);
+        mActivityManager = mContext.getSystemService(ActivityManager.class);
+        mUidImportanceListener = (uid, importance) -> {
+            if (uid == mUid && importance == IMPORTANCE_CACHED) {
+                Log.d(TAG, "Listener " + uid + " becomes " + importance);
+                mLatchActivityCached.countDown();
+            }
+        };
+        SystemUtil.runWithShellPermissionIdentity(() ->
+                mActivityManager.addOnUidImportanceListener(mUidImportanceListener,
+                        IMPORTANCE_CACHED));
+        // The lock is not functionally necessary but eliminates lint error messages.
+        synchronized (mLock) {
+            mDisplayBundles = new SparseArray<>();
+        }
+        mHandlerThread = new HandlerThread("handler");
+        mHandlerThread.start();
+        mHandler = new TestHandler(mHandlerThread.getLooper());
+        mMessenger = new Messenger(mHandler);
+        mPid = 0;
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mActivityManager.removeOnUidImportanceListener(mUidImportanceListener);
+        mHandlerThread.quitSafely();
+        synchronized (mLock) {
+            for (int i = 0; i < mDisplayBundles.size(); i++) {
+                DisplayBundle bundle = mDisplayBundles.valueAt(i);
+                // Clean up unreleased virtual display
+                bundle.releaseDisplay();
+            }
+            mDisplayBundles.clear();
+        }
+        SystemUtil.runShellCommand(mInstrumentation, "am force-stop " + TEST_PACKAGE);
+    }
+
+    /**
+     * Return a display bundle at the stated index.  The bundle is retrieved under lock.
+     */
+    private DisplayBundle displayBundleAt(int i) {
+        synchronized (mLock) {
+            return mDisplayBundles.valueAt(i);
+        }
+    }
+
+    /**
+     * Create virtual displays, change their configurations and release them
+     * mDisplays: the amount of virtual displays to be created
+     * mCached: true to run the test activity in cached mode; false in non-cached mode
+     */
+    @Test
+    public void testDisplayEvents() {
+        Log.d(TAG, "Start test testDisplayEvents " + mDisplayCount + " " + mCached);
+        // Launch DisplayEventActivity and start listening to display events
+        launchTestActivity();
+
+        if (mCached) {
+            // The test activity in cached mode won't receive the pending display events
+            makeTestActivityCached();
+        }
+
+        // Create new virtual displays
+        for (int i = 0; i < mDisplayCount; i++) {
+            // Lock is needed here to ensure the handler can query the displays
+            synchronized (mLock) {
+                VirtualDisplay display = createVirtualDisplay(NAME + i);
+                DisplayBundle bundle = new DisplayBundle(display);
+                mDisplayBundles.put(bundle.mDisplayId, bundle);
+            }
+        }
+
+        for (int i = 0; i < mDisplayCount; i++) {
+            if (mCached) {
+                // DISPLAY_ADDED should be deferred for cached process
+                displayBundleAt(i).assertNoDisplayEvents();
+            } else {
+                // DISPLAY_ADDED should arrive immediately for non-cached process
+                displayBundleAt(i).waitDisplayEvent(DISPLAY_ADDED);
+            }
+        }
+
+        // Change the virtual displays
+        for (int i = 0; i < mDisplayCount; i++) {
+            DisplayBundle bundle = displayBundleAt(i);
+            bundle.mVirtualDisplay.resize(WIDTH, HEIGHT, DENSITY_HIGH);
+        }
+
+        for (int i = 0; i < mDisplayCount; i++) {
+            if (mCached) {
+                // DISPLAY_CHANGED should be deferred for cached process
+                displayBundleAt(i).assertNoDisplayEvents();
+            } else {
+                // DISPLAY_CHANGED should arrive immediately for non-cached process
+                displayBundleAt(i).waitDisplayEvent(DISPLAY_CHANGED);
+            }
+        }
+
+        if (mCached) {
+            // The test activity becomes non-cached and should receive the pending display events
+            bringTestActivityTop();
+
+            for (int i = 0; i < mDisplayCount; i++) {
+                // The pending DISPLAY_ADDED & DISPLAY_CHANGED should arrive now
+                displayBundleAt(i).waitDisplayEvent(DISPLAY_ADDED);
+                displayBundleAt(i).waitDisplayEvent(DISPLAY_CHANGED);
+            }
+        }
+
+        // Release the virtual displays
+        for (int i = 0; i < mDisplayCount; i++) {
+            displayBundleAt(i).releaseDisplay();
+        }
+
+        // DISPLAY_REMOVED should arrive now
+        for (int i = 0; i < mDisplayCount; i++) {
+            displayBundleAt(i).waitDisplayEvent(DISPLAY_REMOVED);
+        }
+    }
+
+    /**
+     * Launch the test activity that would listen to display events
+     */
+    private void launchTestActivity() {
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.setClassName(TEST_PACKAGE, TEST_ACTIVITY);
+        intent.putExtra(TEST_MESSENGER, mMessenger);
+        intent.putExtra(TEST_DISPLAYS, mDisplayCount);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> {
+                    mContext.startActivity(intent);
+                },
+                android.Manifest.permission.START_ACTIVITIES_FROM_SDK_SANDBOX);
+        waitLatch(mLatchActivityLaunch);
+    }
+
+    /**
+     * Bring the test activity back to top
+     */
+    private void bringTestActivityTop() {
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.setClassName(TEST_PACKAGE, TEST_ACTIVITY);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> {
+                    mContext.startActivity(intent);
+                },
+                android.Manifest.permission.START_ACTIVITIES_FROM_SDK_SANDBOX);
+    }
+
+    /**
+     * Bring the test activity into cached mode by launching another 2 apps
+     */
+    private void makeTestActivityCached() {
+        // Launch another activity to bring the test activity into background
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.setClass(mContext, SimpleActivity.class);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+
+        // Launch another activity to bring the test activity into cached mode
+        Intent intent2 = new Intent(Intent.ACTION_MAIN);
+        intent2.setClass(mContext, SimpleActivity2.class);
+        intent2.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> {
+                    mInstrumentation.startActivitySync(intent);
+                    mInstrumentation.startActivitySync(intent2);
+                },
+                android.Manifest.permission.START_ACTIVITIES_FROM_SDK_SANDBOX);
+        waitLatch(mLatchActivityCached);
+    }
+
+    /**
+     * Create a virtual display
+     *
+     * @param name The name of the new virtual display
+     * @return The new virtual display
+     */
+    private VirtualDisplay createVirtualDisplay(String name) {
+        return mDisplayManager.createVirtualDisplay(name, WIDTH, HEIGHT, DENSITY_MEDIUM,
+                null /* surface: as we don't actually draw anything, null is enough */,
+                VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
+                /* flags: a public virtual display that another app can access */);
+    }
+
+    /**
+     * Wait for CountDownLatch with timeout
+     */
+    private void waitLatch(CountDownLatch latch) {
+        try {
+            latch.await(TEST_FAILURE_TIMEOUT_MSEC, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index d0aec3b..bf5a692 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -75,6 +75,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.internal.app.IBatteryStats;
 import com.android.modules.utils.testing.ExtendedMockitoRule;
 import com.android.server.LocalServices;
 import com.android.server.am.BatteryStatsService;
@@ -158,6 +159,8 @@
     private DisplayManagerFlags mDisplayManagerFlagsMock;
     @Mock
     private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession;
+    @Mock
+    private IBatteryStats mMockBatteryStats;
     @Captor
     private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor;
 
@@ -204,7 +207,8 @@
 
         doAnswer((Answer<Void>) invocationOnMock -> null).when(() ->
                 SystemProperties.set(anyString(), any()));
-        doAnswer((Answer<Void>) invocationOnMock -> null).when(BatteryStatsService::getService);
+        doAnswer((Answer<IBatteryStats>) invocationOnMock -> mMockBatteryStats)
+                .when(BatteryStatsService::getService);
         doAnswer((Answer<Boolean>) invocationOnMock -> false)
                 .when(ActivityManager::isLowRamDeviceStatic);
 
@@ -2227,6 +2231,52 @@
         verify(mHolder.brightnessSetting).saveIfNeeded();
     }
 
+    @Test
+    public void testBatteryStatNotes_enabledOnDefaultDisplayWhenDisabledOnOthers()
+            throws Exception {
+        when(mDisplayManagerFlagsMock.isBatteryStatsEnabledForAllDisplays()).thenReturn(false);
+
+        verifyNoteScreenState(Display.DEFAULT_DISPLAY, /* expectNote= */ true);
+    }
+
+    @Test
+    public void testBatteryStatNotes_enabledOnDefaultDisplayWhenEnabledOnOthers() throws Exception {
+        when(mDisplayManagerFlagsMock.isBatteryStatsEnabledForAllDisplays()).thenReturn(true);
+
+        verifyNoteScreenState(Display.DEFAULT_DISPLAY, /* expectNote= */ true);
+    }
+
+    @Test
+    public void testBatteryStatNotes_flagGuardedOnNonDefaultDisplays() throws Exception {
+        when(mDisplayManagerFlagsMock.isBatteryStatsEnabledForAllDisplays()).thenReturn(false);
+
+        verifyNoteScreenState(/* displayId= */ 2, /* expectNote= */ false);
+
+        when(mDisplayManagerFlagsMock.isBatteryStatsEnabledForAllDisplays()).thenReturn(true);
+
+        verifyNoteScreenState(/* displayId= */ 2, /* expectNote= */ true);
+    }
+
+    private void verifyNoteScreenState(int displayId, boolean expectNote) throws Exception {
+        mHolder = createDisplayPowerController(displayId, UNIQUE_ID);
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        if (expectNote) {
+            verify(mMockBatteryStats)
+                    .noteScreenState(
+                            displayId, Display.STATE_ON, Display.STATE_REASON_DEFAULT_POLICY);
+            verify(mMockBatteryStats).noteScreenBrightness(eq(displayId), anyInt());
+        } else {
+            verify(mMockBatteryStats, never()).noteScreenState(anyInt(), anyInt(), anyInt());
+            verify(mMockBatteryStats, never()).noteScreenBrightness(anyInt(), anyInt());
+        }
+    }
+
     /**
      * Creates a mock and registers it to {@link LocalServices}.
      */
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToNotificationsShadeTransition.kt b/services/tests/displayservicetests/src/com/android/server/display/SimpleActivity.java
similarity index 69%
copy from packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToNotificationsShadeTransition.kt
copy to services/tests/displayservicetests/src/com/android/server/display/SimpleActivity.java
index 48ec198..c4ebbd9 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToNotificationsShadeTransition.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/SimpleActivity.java
@@ -14,12 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.scene.ui.composable.transitions
+package com.android.server.display;
 
-import com.android.compose.animation.scene.TransitionBuilder
+import android.app.Activity;
 
-fun TransitionBuilder.goneToNotificationsShadeTransition(
-    durationScale: Double = 1.0,
-) {
-    toNotificationsShadeTransition(durationScale)
-}
+/**
+ * An activity doing nothing
+ */
+public final class SimpleActivity extends Activity { }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToNotificationsShadeTransition.kt b/services/tests/displayservicetests/src/com/android/server/display/SimpleActivity2.java
similarity index 69%
copy from packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToNotificationsShadeTransition.kt
copy to services/tests/displayservicetests/src/com/android/server/display/SimpleActivity2.java
index 48ec198..a719a57 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToNotificationsShadeTransition.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/SimpleActivity2.java
@@ -14,12 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.scene.ui.composable.transitions
+package com.android.server.display;
 
-import com.android.compose.animation.scene.TransitionBuilder
+import android.app.Activity;
 
-fun TransitionBuilder.goneToNotificationsShadeTransition(
-    durationScale: Double = 1.0,
-) {
-    toNotificationsShadeTransition(durationScale)
-}
+/**
+ * Another activity doing nothing
+ */
+public final class SimpleActivity2 extends Activity { }
diff --git a/services/tests/dreamservicetests/TEST_MAPPING b/services/tests/dreamservicetests/TEST_MAPPING
index a644ea6..38d7000 100644
--- a/services/tests/dreamservicetests/TEST_MAPPING
+++ b/services/tests/dreamservicetests/TEST_MAPPING
@@ -1,21 +1,12 @@
 {
   "presubmit": [
     {
-      "name": "DreamServiceTests",
-      "options": [
-        {"include-filter": "com.android.server.dreams"},
-        {"exclude-annotation": "androidx.test.filters.FlakyTest"},
-        {"exclude-annotation": "org.junit.Ignore"}
-      ]
+      "name": "DreamServiceTests_server_dreams"
     }
   ],
   "postsubmit": [
     {
-      "name": "DreamServiceTests",
-      "options": [
-        {"include-filter": "com.android.server.dreams"},
-        {"exclude-annotation": "org.junit.Ignore"}
-      ]
+      "name": "DreamServiceTests_server_dreams"
     }
   ]
 }
diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamOverlayServiceTest.java b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamOverlayServiceTest.java
index 1abc557..1128f52 100644
--- a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamOverlayServiceTest.java
+++ b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamOverlayServiceTest.java
@@ -39,7 +39,6 @@
 import android.view.WindowManager;
 
 import androidx.annotation.NonNull;
-import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -106,6 +105,12 @@
             mMonitor.onEndDream();
             super.onEndDream();
         }
+
+        @Override
+        public void onWakeUp() {
+            mMonitor.onWakeUp();
+            super.onWakeUp();
+        }
     }
 
     /**
@@ -128,7 +133,6 @@
      * Verifies that callbacks for subclasses are run on the provided executor.
      */
     @Test
-    @FlakyTest(bugId = 293108088)
     public void testCallbacksRunOnExecutor() throws RemoteException {
         final TestDreamOverlayService.Monitor monitor = Mockito.mock(
                 TestDreamOverlayService.Monitor.class);
@@ -153,6 +157,8 @@
         // Callback is run.
         verify(monitor).onStartDream();
 
+        clearInvocations(mExecutor);
+
         // Verify onWakeUp is run on the executor.
         client.wakeUp();
         verify(monitor, never()).onWakeUp();
@@ -161,6 +167,8 @@
         mRunnableCaptor.getValue().run();
         verify(monitor).onWakeUp();
 
+        clearInvocations(mExecutor);
+
         // Verify onEndDream is run on the executor.
         client.endDream();
         verify(monitor, never()).onEndDream();
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpuset_2/background/cpus b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpuset_2/background/cpus
new file mode 100644
index 0000000..8b0fab8
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpuset_2/background/cpus
@@ -0,0 +1 @@
+0-1
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpuset_2/top-app/cpus b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpuset_2/top-app/cpus
new file mode 100644
index 0000000..40c7bb2
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpuset_2/top-app/cpus
@@ -0,0 +1 @@
+0-3
diff --git a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java
index 2fbe8aa..3fe038a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java
@@ -26,6 +26,7 @@
 
 import android.content.Context;
 import android.content.res.AssetManager;
+import android.util.IntArray;
 import android.util.Log;
 import android.util.SparseArray;
 
@@ -48,6 +49,7 @@
     private static final String TAG = CpuInfoReaderTest.class.getSimpleName();
     private static final String ROOT_DIR_NAME = "CpuInfoReaderTest";
     private static final String VALID_CPUSET_DIR = "valid_cpuset";
+    private static final String VALID_CPUSET_2_DIR = "valid_cpuset_2";
     private static final String VALID_CPUSET_WITH_EMPTY_CPUS = "valid_cpuset_with_empty_cpus";
     private static final String VALID_CPUFREQ_WITH_EMPTY_AFFECTED_CPUS =
             "valid_cpufreq_with_empty_affected_cpus";
@@ -88,54 +90,95 @@
     }
 
     @Test
+    public void testReadCpuInfoWithUpdatedCpuset() throws Exception {
+        CpuInfoReader cpuInfoReader = newCpuInfoReader(getCacheFile(VALID_CPUSET_DIR),
+                getCacheFile(VALID_CPUFREQ_WITH_TIME_IN_STATE_DIR), getCacheFile(VALID_PROC_STAT));
+
+        SparseArray<CpuInfoReader.CpuInfo> actualCpuInfos = cpuInfoReader.readCpuInfos();
+        SparseArray<CpuInfoReader.CpuInfo> expectedCpuInfos =
+                getFirstCpuInfosWithTimeInStateSnapshot();
+
+        compareCpuInfos("CPU infos first snapshot", expectedCpuInfos, actualCpuInfos);
+
+        cpuInfoReader.setCpusetDir(getCacheFile(VALID_CPUSET_2_DIR));
+        cpuInfoReader.setCpuFreqDir(getCacheFile(VALID_CPUFREQ_WITH_TIME_IN_STATE_2_DIR));
+        cpuInfoReader.setProcStatFile(getCacheFile(VALID_PROC_STAT_2));
+
+        actualCpuInfos = cpuInfoReader.readCpuInfos();
+
+        IntArray cpusetCategories = new IntArray();
+        cpusetCategories.add(FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND);
+        cpusetCategories.add(FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND);
+        cpusetCategories.add(FLAG_CPUSET_CATEGORY_TOP_APP);
+        cpusetCategories.add(FLAG_CPUSET_CATEGORY_TOP_APP);
+        expectedCpuInfos = getSecondCpuInfosWithTimeInStateSnapshot(cpusetCategories);
+
+        compareCpuInfos("CPU infos second snapshot", expectedCpuInfos, actualCpuInfos);
+    }
+
+    @Test
+    public void testReadCpuInfoWithUpdatedCpusetBeforeStopSignal() throws Exception {
+        CpuInfoReader cpuInfoReader = newCpuInfoReader(getCacheFile(VALID_CPUSET_DIR),
+                getCacheFile(VALID_CPUFREQ_WITH_TIME_IN_STATE_DIR), getCacheFile(VALID_PROC_STAT));
+
+        SparseArray<CpuInfoReader.CpuInfo> actualCpuInfos = cpuInfoReader.readCpuInfos();
+        SparseArray<CpuInfoReader.CpuInfo> expectedCpuInfos =
+                getFirstCpuInfosWithTimeInStateSnapshot();
+
+        compareCpuInfos("CPU infos first snapshot", expectedCpuInfos, actualCpuInfos);
+
+        cpuInfoReader.setCpusetDir(getCacheFile(VALID_CPUSET_2_DIR));
+        // When stopping the periodic cpuset reading, the reader will create a new snapshot.
+        cpuInfoReader.stopPeriodicCpusetReading();
+        // Any cpuset update after the stop signal should be ignored by the reader.
+        cpuInfoReader.setCpusetDir(getCacheFile(VALID_CPUSET_DIR));
+        cpuInfoReader.setCpuFreqDir(getCacheFile(VALID_CPUFREQ_WITH_TIME_IN_STATE_2_DIR));
+        cpuInfoReader.setProcStatFile(getCacheFile(VALID_PROC_STAT_2));
+
+        actualCpuInfos = cpuInfoReader.readCpuInfos();
+
+        IntArray cpusetCategories = new IntArray();
+        cpusetCategories.add(FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND);
+        cpusetCategories.add(FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND);
+        cpusetCategories.add(FLAG_CPUSET_CATEGORY_TOP_APP);
+        cpusetCategories.add(FLAG_CPUSET_CATEGORY_TOP_APP);
+        expectedCpuInfos = getSecondCpuInfosWithTimeInStateSnapshot(cpusetCategories);
+
+        compareCpuInfos("CPU infos second snapshot", expectedCpuInfos, actualCpuInfos);
+    }
+
+
+    @Test
+    public void testReadCpuInfoWithUpdatedCpusetAfterStopSignal() throws Exception {
+        CpuInfoReader cpuInfoReader = newCpuInfoReader(getCacheFile(VALID_CPUSET_DIR),
+                getCacheFile(VALID_CPUFREQ_WITH_TIME_IN_STATE_DIR), getCacheFile(VALID_PROC_STAT));
+
+        SparseArray<CpuInfoReader.CpuInfo> actualCpuInfos = cpuInfoReader.readCpuInfos();
+        SparseArray<CpuInfoReader.CpuInfo> expectedCpuInfos =
+                getFirstCpuInfosWithTimeInStateSnapshot();
+
+        compareCpuInfos("CPU infos first snapshot", expectedCpuInfos, actualCpuInfos);
+
+        cpuInfoReader.stopPeriodicCpusetReading();
+        // Any cpuset update after the stop signal should be ignored by the reader.
+        cpuInfoReader.setCpusetDir(getCacheFile(VALID_CPUSET_2_DIR));
+        cpuInfoReader.setCpuFreqDir(getCacheFile(VALID_CPUFREQ_WITH_TIME_IN_STATE_2_DIR));
+        cpuInfoReader.setProcStatFile(getCacheFile(VALID_PROC_STAT_2));
+
+        actualCpuInfos = cpuInfoReader.readCpuInfos();
+
+        expectedCpuInfos = getSecondCpuInfosWithTimeInStateSnapshot();
+        compareCpuInfos("CPU infos second snapshot", expectedCpuInfos, actualCpuInfos);
+    }
+
+    @Test
     public void testReadCpuInfoWithTimeInState() throws Exception {
         CpuInfoReader cpuInfoReader = newCpuInfoReader(getCacheFile(VALID_CPUSET_DIR),
                 getCacheFile(VALID_CPUFREQ_WITH_TIME_IN_STATE_DIR), getCacheFile(VALID_PROC_STAT));
 
         SparseArray<CpuInfoReader.CpuInfo> actualCpuInfos = cpuInfoReader.readCpuInfos();
-        SparseArray<CpuInfoReader.CpuInfo> expectedCpuInfos = new SparseArray<>();
-        expectedCpuInfos.append(0, new CpuInfoReader.CpuInfo(/* cpuCore= */ 0,
-                FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_230_000,
-                /* maxCpuFreqKHz= */ 2_500_000, /* avgTimeInStateCpuFreqKHz= */ 488_095,
-                /* normalizedAvailableCpuFreqKHz= */ 2_402_267,
-                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 32_249_610,
-                        /* niceTimeMillis= */ 7_950_930, /* systemTimeMillis= */ 52_227_050,
-                        /* idleTimeMillis= */ 409_036_950, /* iowaitTimeMillis= */ 1_322_810,
-                        /* irqTimeMillis= */ 8_146_740, /* softirqTimeMillis= */ 428_970,
-                        /* stealTimeMillis= */ 81_950, /* guestTimeMillis= */ 0,
-                        /* guestNiceTimeMillis= */ 0)));
-        expectedCpuInfos.append(1, new CpuInfoReader.CpuInfo(/* cpuCore= */ 1,
-                FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_450_000,
-                /* maxCpuFreqKHz= */ 2_800_000, /* avgTimeInStateCpuFreqKHz= */ 502_380,
-                /* normalizedAvailableCpuFreqKHz= */ 2_693_525,
-                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_949_280,
-                        /* niceTimeMillis= */ 7_799_450, /* systemTimeMillis= */ 54_004_020,
-                        /* idleTimeMillis= */ 402_707_120, /* iowaitTimeMillis= */ 1_186_960,
-                        /* irqTimeMillis= */ 14_786_940, /* softirqTimeMillis= */ 1_498_130,
-                        /* stealTimeMillis= */ 78_780, /* guestTimeMillis= */ 0,
-                        /* guestNiceTimeMillis= */ 0)));
-        expectedCpuInfos.append(2, new CpuInfoReader.CpuInfo(/* cpuCore= */ 2,
-                FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND,
-                /* isOnline= */ true, /* curCpuFreqKHz= */ 1_000_000,
-                /* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ 464_285,
-                /* normalizedAvailableCpuFreqKHz= */ 1_901_608,
-                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_959_280,
-                        /* niceTimeMillis= */ 7_789_450, /* systemTimeMillis= */ 54_014_020,
-                        /* idleTimeMillis= */ 402_717_120, /* iowaitTimeMillis= */ 1_166_960,
-                        /* irqTimeMillis= */ 14_796_940, /* softirqTimeMillis= */ 1_478_130,
-                        /* stealTimeMillis= */ 88_780, /* guestTimeMillis= */ 0,
-                        /* guestNiceTimeMillis= */ 0)));
-        expectedCpuInfos.append(3, new CpuInfoReader.CpuInfo(/* cpuCore= */ 3,
-                FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND,
-                /* isOnline= */ true, /* curCpuFreqKHz= */ 1_000_000,
-                /* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ 464_285,
-                /* normalizedAvailableCpuFreqKHz= */ 1_907_125,
-                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 32_349_610,
-                        /* niceTimeMillis= */ 7_850_930, /* systemTimeMillis= */ 52_127_050,
-                        /* idleTimeMillis= */ 409_136_950, /* iowaitTimeMillis= */ 1_332_810,
-                        /* irqTimeMillis= */ 8_136_740, /* softirqTimeMillis= */ 438_970,
-                        /* stealTimeMillis= */ 71_950, /* guestTimeMillis= */ 0,
-                        /* guestNiceTimeMillis= */ 0)));
+        SparseArray<CpuInfoReader.CpuInfo> expectedCpuInfos =
+                getFirstCpuInfosWithTimeInStateSnapshot();
 
         compareCpuInfos("CPU infos first snapshot", expectedCpuInfos, actualCpuInfos);
 
@@ -144,49 +187,7 @@
 
         actualCpuInfos = cpuInfoReader.readCpuInfos();
 
-        expectedCpuInfos.clear();
-        expectedCpuInfos.append(0, new CpuInfoReader.CpuInfo(/* cpuCore= */ 0,
-                FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_000_000,
-                /* maxCpuFreqKHz= */ 2_600_000, /* avgTimeInStateCpuFreqKHz= */ 419_354,
-                /* normalizedAvailableCpuFreqKHz= */ 2_525_919,
-                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 10_000_000,
-                        /* niceTimeMillis= */ 1_000_000, /* systemTimeMillis= */ 10_000_000,
-                        /* idleTimeMillis= */ 110_000_000, /* iowaitTimeMillis= */ 1_100_000,
-                        /* irqTimeMillis= */ 1_400_000, /* softirqTimeMillis= */ 80_000,
-                        /* stealTimeMillis= */ 21_000, /* guestTimeMillis= */ 0,
-                        /* guestNiceTimeMillis= */ 0)));
-        expectedCpuInfos.append(1, new CpuInfoReader.CpuInfo(/* cpuCore= */ 1,
-                FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 2_800_000,
-                /* maxCpuFreqKHz= */ 2_900_000, /* avgTimeInStateCpuFreqKHz= */ 429_032,
-                /* normalizedAvailableCpuFreqKHz= */ 2_503_009,
-                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 900_000,
-                        /* niceTimeMillis= */ 1_000_000, /* systemTimeMillis= */ 10_000_000,
-                        /* idleTimeMillis= */ 1_000_000, /* iowaitTimeMillis= */ 90_000,
-                        /* irqTimeMillis= */ 200_000, /* softirqTimeMillis= */ 100_000,
-                        /* stealTimeMillis= */ 100_000, /* guestTimeMillis= */ 0,
-                        /* guestNiceTimeMillis= */ 0)));
-        expectedCpuInfos.append(2, new CpuInfoReader.CpuInfo(/* cpuCore= */ 2,
-                FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND,
-                /* isOnline= */ true, /* curCpuFreqKHz= */ 2_000_000,
-                /* maxCpuFreqKHz= */ 2_100_000, /* avgTimeInStateCpuFreqKHz= */ 403_225,
-                /* normalizedAvailableCpuFreqKHz= */ 1_788_209,
-                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 10_000_000,
-                        /* niceTimeMillis= */ 2_000_000, /* systemTimeMillis= */ 0,
-                        /* idleTimeMillis= */ 10_000_000, /* iowaitTimeMillis= */ 1_000_000,
-                        /* irqTimeMillis= */ 20_000_000, /* softirqTimeMillis= */ 1_000_000,
-                        /* stealTimeMillis= */ 100_000, /* guestTimeMillis= */ 0,
-                        /* guestNiceTimeMillis= */ 0)));
-        expectedCpuInfos.append(3, new CpuInfoReader.CpuInfo(/* cpuCore= */ 3,
-                FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND,
-                /* isOnline= */ false, /* curCpuFreqKHz= */ MISSING_FREQUENCY,
-                /* maxCpuFreqKHz= */ 2_100_000, /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
-                /* normalizedAvailableCpuFreqKHz= */ MISSING_FREQUENCY,
-                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 2_000_000,
-                        /* niceTimeMillis= */ 1_000_000, /* systemTimeMillis= */ 1_000_000,
-                        /* idleTimeMillis= */ 100_000, /* iowaitTimeMillis= */ 100_000,
-                        /* irqTimeMillis= */ 100_000, /* softirqTimeMillis= */ 1_000_000,
-                        /* stealTimeMillis= */ 1_000, /* guestTimeMillis= */ 0,
-                        /* guestNiceTimeMillis= */ 0)));
+        expectedCpuInfos = getSecondCpuInfosWithTimeInStateSnapshot();
 
         compareCpuInfos("CPU infos second snapshot", expectedCpuInfos, actualCpuInfos);
     }
@@ -592,4 +593,108 @@
         }
         return rootDir.delete();
     }
+
+    private SparseArray<CpuInfoReader.CpuInfo> getFirstCpuInfosWithTimeInStateSnapshot() {
+        SparseArray<CpuInfoReader.CpuInfo> cpuInfos = new SparseArray<>();
+        cpuInfos.append(0, new CpuInfoReader.CpuInfo(/* cpuCore= */ 0, FLAG_CPUSET_CATEGORY_TOP_APP,
+                /* isOnline= */ true, /* curCpuFreqKHz= */ 1_230_000,
+                /* maxCpuFreqKHz= */ 2_500_000, /* avgTimeInStateCpuFreqKHz= */ 488_095,
+                /* normalizedAvailableCpuFreqKHz= */ 2_402_267,
+                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 32_249_610,
+                        /* niceTimeMillis= */ 7_950_930, /* systemTimeMillis= */ 52_227_050,
+                        /* idleTimeMillis= */ 409_036_950, /* iowaitTimeMillis= */ 1_322_810,
+                        /* irqTimeMillis= */ 8_146_740, /* softirqTimeMillis= */ 428_970,
+                        /* stealTimeMillis= */ 81_950, /* guestTimeMillis= */ 0,
+                        /* guestNiceTimeMillis= */ 0)));
+        cpuInfos.append(1, new CpuInfoReader.CpuInfo(/* cpuCore= */ 1, FLAG_CPUSET_CATEGORY_TOP_APP,
+                /* isOnline= */ true, /* curCpuFreqKHz= */ 1_450_000,
+                /* maxCpuFreqKHz= */ 2_800_000, /* avgTimeInStateCpuFreqKHz= */ 502_380,
+                /* normalizedAvailableCpuFreqKHz= */ 2_693_525,
+                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_949_280,
+                        /* niceTimeMillis= */ 7_799_450, /* systemTimeMillis= */ 54_004_020,
+                        /* idleTimeMillis= */ 402_707_120, /* iowaitTimeMillis= */ 1_186_960,
+                        /* irqTimeMillis= */ 14_786_940, /* softirqTimeMillis= */ 1_498_130,
+                        /* stealTimeMillis= */ 78_780, /* guestTimeMillis= */ 0,
+                        /* guestNiceTimeMillis= */ 0)));
+        cpuInfos.append(2, new CpuInfoReader.CpuInfo(/* cpuCore= */ 2,
+                FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND,
+                /* isOnline= */ true, /* curCpuFreqKHz= */ 1_000_000,
+                /* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ 464_285,
+                /* normalizedAvailableCpuFreqKHz= */ 1_901_608,
+                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_959_280,
+                        /* niceTimeMillis= */ 7_789_450, /* systemTimeMillis= */ 54_014_020,
+                        /* idleTimeMillis= */ 402_717_120, /* iowaitTimeMillis= */ 1_166_960,
+                        /* irqTimeMillis= */ 14_796_940, /* softirqTimeMillis= */ 1_478_130,
+                        /* stealTimeMillis= */ 88_780, /* guestTimeMillis= */ 0,
+                        /* guestNiceTimeMillis= */ 0)));
+        cpuInfos.append(3, new CpuInfoReader.CpuInfo(/* cpuCore= */ 3,
+                FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND,
+                /* isOnline= */ true, /* curCpuFreqKHz= */ 1_000_000,
+                /* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ 464_285,
+                /* normalizedAvailableCpuFreqKHz= */ 1_907_125,
+                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 32_349_610,
+                        /* niceTimeMillis= */ 7_850_930, /* systemTimeMillis= */ 52_127_050,
+                        /* idleTimeMillis= */ 409_136_950, /* iowaitTimeMillis= */ 1_332_810,
+                        /* irqTimeMillis= */ 8_136_740, /* softirqTimeMillis= */ 438_970,
+                        /* stealTimeMillis= */ 71_950, /* guestTimeMillis= */ 0,
+                        /* guestNiceTimeMillis= */ 0)));
+        return cpuInfos;
+    }
+
+    private SparseArray<CpuInfoReader.CpuInfo> getSecondCpuInfosWithTimeInStateSnapshot() {
+        IntArray cpusetCategories = new IntArray();
+        cpusetCategories.add(FLAG_CPUSET_CATEGORY_TOP_APP);
+        cpusetCategories.add(FLAG_CPUSET_CATEGORY_TOP_APP);
+        cpusetCategories.add(FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND);
+        cpusetCategories.add(FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND);
+        return getSecondCpuInfosWithTimeInStateSnapshot(cpusetCategories);
+    }
+
+    private SparseArray<CpuInfoReader.CpuInfo> getSecondCpuInfosWithTimeInStateSnapshot(
+        IntArray cpusetCategories) {
+        SparseArray<CpuInfoReader.CpuInfo> cpuInfos = new SparseArray<>();
+        cpuInfos.append(0, new CpuInfoReader.CpuInfo(/* cpuCore= */ 0, cpusetCategories.get(0),
+                /* isOnline= */ true, /* curCpuFreqKHz= */ 1_000_000,
+                /* maxCpuFreqKHz= */ 2_600_000, /* avgTimeInStateCpuFreqKHz= */ 419_354,
+                /* normalizedAvailableCpuFreqKHz= */ 2_525_919,
+                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 10_000_000,
+                        /* niceTimeMillis= */ 1_000_000, /* systemTimeMillis= */ 10_000_000,
+                        /* idleTimeMillis= */ 110_000_000, /* iowaitTimeMillis= */ 1_100_000,
+                        /* irqTimeMillis= */ 1_400_000, /* softirqTimeMillis= */ 80_000,
+                        /* stealTimeMillis= */ 21_000, /* guestTimeMillis= */ 0,
+                        /* guestNiceTimeMillis= */ 0)));
+        cpuInfos.append(1, new CpuInfoReader.CpuInfo(/* cpuCore= */ 1, cpusetCategories.get(1),
+                /* isOnline= */ true, /* curCpuFreqKHz= */ 2_800_000,
+                /* maxCpuFreqKHz= */ 2_900_000, /* avgTimeInStateCpuFreqKHz= */ 429_032,
+                /* normalizedAvailableCpuFreqKHz= */ 2_503_009,
+                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 900_000,
+                        /* niceTimeMillis= */ 1_000_000, /* systemTimeMillis= */ 10_000_000,
+                        /* idleTimeMillis= */ 1_000_000, /* iowaitTimeMillis= */ 90_000,
+                        /* irqTimeMillis= */ 200_000, /* softirqTimeMillis= */ 100_000,
+                        /* stealTimeMillis= */ 100_000, /* guestTimeMillis= */ 0,
+                        /* guestNiceTimeMillis= */ 0)));
+        cpuInfos.append(2, new CpuInfoReader.CpuInfo(/* cpuCore= */ 2, cpusetCategories.get(2),
+                /* isOnline= */ true, /* curCpuFreqKHz= */ 2_000_000,
+                /* maxCpuFreqKHz= */ 2_100_000, /* avgTimeInStateCpuFreqKHz= */ 403_225,
+                /* normalizedAvailableCpuFreqKHz= */ 1_788_209,
+                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 10_000_000,
+                        /* niceTimeMillis= */ 2_000_000, /* systemTimeMillis= */ 0,
+                        /* idleTimeMillis= */ 10_000_000, /* iowaitTimeMillis= */ 1_000_000,
+                        /* irqTimeMillis= */ 20_000_000, /* softirqTimeMillis= */ 1_000_000,
+                        /* stealTimeMillis= */ 100_000, /* guestTimeMillis= */ 0,
+                        /* guestNiceTimeMillis= */ 0)));
+        cpuInfos.append(3, new CpuInfoReader.CpuInfo(/* cpuCore= */ 3, cpusetCategories.get(3),
+                /* isOnline= */ false,
+                /* curCpuFreqKHz= */ MISSING_FREQUENCY, /* maxCpuFreqKHz= */ 2_100_000,
+                /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
+                /* normalizedAvailableCpuFreqKHz= */ MISSING_FREQUENCY,
+                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 2_000_000,
+                        /* niceTimeMillis= */ 1_000_000, /* systemTimeMillis= */ 1_000_000,
+                        /* idleTimeMillis= */ 100_000, /* iowaitTimeMillis= */ 100_000,
+                        /* irqTimeMillis= */ 100_000, /* softirqTimeMillis= */ 1_000_000,
+                        /* stealTimeMillis= */ 1_000, /* guestTimeMillis= */ 0,
+                        /* guestNiceTimeMillis= */ 0)));
+        return cpuInfos;
+    }
+
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java
index 994313f..d9e09d8 100644
--- a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java
@@ -26,6 +26,7 @@
 import static com.android.server.cpu.CpuInfoReader.FLAG_CPUSET_CATEGORY_BACKGROUND;
 import static com.android.server.cpu.CpuInfoReader.FLAG_CPUSET_CATEGORY_TOP_APP;
 import static com.android.server.cpu.CpuMonitorService.DEFAULT_MONITORING_INTERVAL_MILLISECONDS;
+import static com.android.server.SystemService.PHASE_BOOT_COMPLETED;
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
@@ -75,6 +76,7 @@
     private static final long TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS = 100;
     private static final long TEST_DEBUG_MONITORING_INTERVAL_MILLISECONDS = 150;
     private static final long TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS = 300;
+    private static final long TEST_STOP_PERIODIC_CPUSET_READING_DELAY_MILLISECONDS = 0;
     private static final CpuAvailabilityMonitoringConfig TEST_MONITORING_CONFIG_ALL_CPUSET =
             new CpuAvailabilityMonitoringConfig.Builder(CPUSET_ALL)
                     .addThreshold(30).addThreshold(70).build();
@@ -119,7 +121,8 @@
         mService = new CpuMonitorService(mMockContext, mMockCpuInfoReader, mServiceHandlerThread,
                 /* shouldDebugMonitor= */ true, TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS,
                 TEST_DEBUG_MONITORING_INTERVAL_MILLISECONDS,
-                TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS);
+                TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS,
+                TEST_STOP_PERIODIC_CPUSET_READING_DELAY_MILLISECONDS);
 
         doNothing().when(() -> ServiceManager.addService(eq("cpu_monitor"), any(Binder.class),
                 anyBoolean(), anyInt()));
@@ -535,6 +538,18 @@
     }
 
     @Test
+    public void testBootCompleted() throws Exception {
+        mService.onBootPhase(PHASE_BOOT_COMPLETED);
+
+        // Message to stop periodic cpuset reading is posted on the service handler thread. Sync
+        // with this thread before proceeding.
+        syncWithHandler(mServiceHandler, /* delayMillis= */ 0);
+
+        verify(mMockCpuInfoReader, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS))
+                .stopPeriodicCpusetReading();
+    }
+
+    @Test
     public void testHeavyCpuLoadMonitoring() throws Exception {
         // TODO(b/267500110): Once heavy CPU load detection logic is added, add unittest.
     }
@@ -567,7 +582,8 @@
                 mServiceHandlerThread, /* shouldDebugMonitor= */ false,
                 TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS,
                 TEST_DEBUG_MONITORING_INTERVAL_MILLISECONDS,
-                TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS);
+                TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS,
+                TEST_STOP_PERIODIC_CPUSET_READING_DELAY_MILLISECONDS);
 
         startService();
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp b/services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp
index 127d3e8..7ac7aca 100644
--- a/services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp
+++ b/services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp
@@ -39,9 +39,9 @@
     ],
 
     libs: [
-        "android.test.mock",
-        "android.test.base",
-        "android.test.runner",
+        "android.test.mock.stubs.system",
+        "android.test.base.stubs.system",
+        "android.test.runner.stubs.system",
     ],
 
     jni_libs: [
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING b/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING
index 4ac4484..ef2d605 100644
--- a/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING
@@ -1,12 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "RollbackPackageHealthObserverTests",
-      "options": [
-        {
-          "include-filter": "com.android.server.rollback"
-        }
-      ]
+      "name": "RollbackPackageHealthObserverTests_server_rollback"
     }
   ]
 }
\ No newline at end of file
diff --git a/services/tests/ondeviceintelligencetests/Android.bp b/services/tests/ondeviceintelligencetests/Android.bp
index aa85942..a31a3fb 100644
--- a/services/tests/ondeviceintelligencetests/Android.bp
+++ b/services/tests/ondeviceintelligencetests/Android.bp
@@ -47,9 +47,9 @@
     ],
 
     libs: [
-        "android.test.mock",
-        "android.test.base",
-        "android.test.runner",
+        "android.test.mock.stubs.system",
+        "android.test.base.stubs.system",
+        "android.test.runner.stubs.system",
     ],
 
     certificate: "platform",
diff --git a/services/tests/performancehinttests/Android.bp b/services/tests/performancehinttests/Android.bp
index 1692921c..c8121fc 100644
--- a/services/tests/performancehinttests/Android.bp
+++ b/services/tests/performancehinttests/Android.bp
@@ -19,7 +19,7 @@
         "truth",
     ],
     libs: [
-        "android.test.base",
+        "android.test.base.stubs.system",
     ],
     test_suites: [
         "automotive-tests",
diff --git a/services/tests/powerstatstests/TEST_MAPPING b/services/tests/powerstatstests/TEST_MAPPING
index 1e8d2de..d3d3cf6 100644
--- a/services/tests/powerstatstests/TEST_MAPPING
+++ b/services/tests/powerstatstests/TEST_MAPPING
@@ -1,12 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "PowerStatsTests",
-      "options": [
-        {"include-filter": "com.android.server.power.stats"},
-        {"exclude-annotation": "androidx.test.filters.FlakyTest"},
-        {"exclude-annotation": "org.junit.Ignore"}
-      ]
+      "name": "PowerStatsTests_power_stats"
     }
   ],
   "ravenwood-presubmit": [
@@ -20,11 +15,7 @@
   ],
   "postsubmit": [
     {
-      "name": "PowerStatsTests",
-      "options": [
-        {"include-filter": "com.android.server.power.stats"},
-        {"exclude-annotation": "org.junit.Ignore"}
-      ]
+      "name": "PowerStatsTests_power_stats"
     }
   ]
 }
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/AmbientDisplayPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/AmbientDisplayPowerCalculatorTest.java
index f74cfae..c0be865 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/AmbientDisplayPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/AmbientDisplayPowerCalculatorTest.java
@@ -56,14 +56,14 @@
         stats.updateDisplayEnergyConsumerStatsLocked(new long[]{300_000_000},
                 new int[]{Display.STATE_ON}, 0);
 
-        stats.noteScreenStateLocked(0, Display.STATE_DOZE, 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS,
-                30 * MINUTE_IN_MS);
+        stats.noteScreenStateLocked(0, Display.STATE_DOZE, Display.STATE_REASON_DEFAULT_POLICY,
+                30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS);
 
         stats.updateDisplayEnergyConsumerStatsLocked(new long[]{200_000_000},
                 new int[]{Display.STATE_DOZE}, 30 * MINUTE_IN_MS);
 
-        stats.noteScreenStateLocked(0, Display.STATE_OFF, 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS,
-                120 * MINUTE_IN_MS);
+        stats.noteScreenStateLocked(0, Display.STATE_OFF, Display.STATE_REASON_DEFAULT_POLICY,
+                120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS);
 
         stats.updateDisplayEnergyConsumerStatsLocked(new long[]{100_000_000},
                 new int[]{Display.STATE_OFF}, 120 * MINUTE_IN_MS);
@@ -93,37 +93,37 @@
 
         final int[] screenStates = new int[] {Display.STATE_OFF, Display.STATE_OFF};
 
-        stats.noteScreenStateLocked(0, screenStates[0], 0, 0, 0);
-        stats.noteScreenStateLocked(1, screenStates[1], 0, 0, 0);
+        stats.noteScreenStateLocked(0, screenStates[0], Display.STATE_REASON_UNKNOWN, 0, 0, 0);
+        stats.noteScreenStateLocked(1, screenStates[1], Display.STATE_REASON_UNKNOWN, 0, 0, 0);
         stats.updateDisplayEnergyConsumerStatsLocked(new long[]{300, 400}, screenStates, 0);
 
         // Switch display0 to doze
         screenStates[0] = Display.STATE_DOZE;
-        stats.noteScreenStateLocked(0, screenStates[0], 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS,
-                30 * MINUTE_IN_MS);
+        stats.noteScreenStateLocked(0, screenStates[0], Display.STATE_REASON_UNKNOWN,
+                30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS);
         stats.updateDisplayEnergyConsumerStatsLocked(new long[]{200, 300},
                 screenStates, 30 * MINUTE_IN_MS);
 
         // Switch display1 to doze
         screenStates[1] = Display.STATE_DOZE;
-        stats.noteScreenStateLocked(1, Display.STATE_DOZE, 90 * MINUTE_IN_MS, 90 * MINUTE_IN_MS,
-                90 * MINUTE_IN_MS);
+        stats.noteScreenStateLocked(1, Display.STATE_DOZE, Display.STATE_REASON_UNKNOWN,
+                90 * MINUTE_IN_MS, 90 * MINUTE_IN_MS, 90 * MINUTE_IN_MS);
         // 100,000,000 uC should be attributed to display 0 doze here.
         stats.updateDisplayEnergyConsumerStatsLocked(new long[]{100_000_000, 700_000_000},
                 screenStates, 90 * MINUTE_IN_MS);
 
         // Switch display0 to off
         screenStates[0] = Display.STATE_OFF;
-        stats.noteScreenStateLocked(0, screenStates[0], 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS,
-                120 * MINUTE_IN_MS);
+        stats.noteScreenStateLocked(0, screenStates[0], Display.STATE_REASON_UNKNOWN,
+                120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS);
         // 40,000,000 and 70,000,000 uC should be attributed to display 0 and 1 doze here.
         stats.updateDisplayEnergyConsumerStatsLocked(new long[]{40_000_000, 70_000_000},
                 screenStates, 120 * MINUTE_IN_MS);
 
         // Switch display1 to off
         screenStates[1] = Display.STATE_OFF;
-        stats.noteScreenStateLocked(1, screenStates[1], 150 * MINUTE_IN_MS, 150 * MINUTE_IN_MS,
-                150 * MINUTE_IN_MS);
+        stats.noteScreenStateLocked(1, screenStates[1], Display.STATE_REASON_UNKNOWN,
+                150 * MINUTE_IN_MS, 150 * MINUTE_IN_MS, 150 * MINUTE_IN_MS);
         stats.updateDisplayEnergyConsumerStatsLocked(new long[]{100, 90_000_000}, screenStates,
                 150 * MINUTE_IN_MS);
         // 90,000,000 uC should be attributed to display 1 doze here.
@@ -148,10 +148,10 @@
     public void testPowerProfileBasedModel() {
         BatteryStatsImpl stats = mStatsRule.getBatteryStats();
 
-        stats.noteScreenStateLocked(0, Display.STATE_DOZE, 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS,
-                30 * MINUTE_IN_MS);
-        stats.noteScreenStateLocked(0, Display.STATE_OFF, 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS,
-                120 * MINUTE_IN_MS);
+        stats.noteScreenStateLocked(0, Display.STATE_DOZE, Display.STATE_REASON_UNKNOWN,
+                30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS);
+        stats.noteScreenStateLocked(0, Display.STATE_OFF, Display.STATE_REASON_UNKNOWN,
+                120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS);
 
         AmbientDisplayPowerCalculator calculator =
                 new AmbientDisplayPowerCalculator(mStatsRule.getPowerProfile());
@@ -174,15 +174,15 @@
 
         BatteryStatsImpl stats = mStatsRule.getBatteryStats();
 
-        stats.noteScreenStateLocked(1, Display.STATE_OFF, 0, 0, 0);
-        stats.noteScreenStateLocked(0, Display.STATE_DOZE, 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS,
-                30 * MINUTE_IN_MS);
-        stats.noteScreenStateLocked(1, Display.STATE_DOZE, 90 * MINUTE_IN_MS, 90 * MINUTE_IN_MS,
-                90 * MINUTE_IN_MS);
-        stats.noteScreenStateLocked(0, Display.STATE_OFF, 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS,
-                120 * MINUTE_IN_MS);
-        stats.noteScreenStateLocked(1, Display.STATE_OFF, 150 * MINUTE_IN_MS, 150 * MINUTE_IN_MS,
-                150 * MINUTE_IN_MS);
+        stats.noteScreenStateLocked(1, Display.STATE_OFF, Display.STATE_REASON_UNKNOWN, 0, 0, 0);
+        stats.noteScreenStateLocked(0, Display.STATE_DOZE, Display.STATE_REASON_UNKNOWN,
+                30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS);
+        stats.noteScreenStateLocked(1, Display.STATE_DOZE, Display.STATE_REASON_UNKNOWN,
+                90 * MINUTE_IN_MS, 90 * MINUTE_IN_MS, 90 * MINUTE_IN_MS);
+        stats.noteScreenStateLocked(0, Display.STATE_OFF, Display.STATE_REASON_UNKNOWN,
+                120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS);
+        stats.noteScreenStateLocked(1, Display.STATE_OFF, Display.STATE_REASON_UNKNOWN,
+                150 * MINUTE_IN_MS, 150 * MINUTE_IN_MS, 150 * MINUTE_IN_MS);
 
         AmbientDisplayPowerCalculator calculator =
                 new AmbientDisplayPowerCalculator(mStatsRule.getPowerProfile());
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
index afbe9159..2ccb642 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
@@ -44,6 +44,10 @@
 import android.os.Process;
 import android.os.UserHandle;
 import android.os.WorkSource;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.DisabledOnRavenwood;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.platform.test.ravenwood.RavenwoodRule;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.ActivityStatsTechSpecificInfo;
@@ -65,6 +69,7 @@
 import com.android.internal.os.MonotonicClock;
 import com.android.internal.os.PowerProfile;
 import com.android.internal.power.EnergyConsumerStats;
+import com.android.server.power.optimization.Flags;
 import com.android.server.power.stats.BatteryStatsImpl.DualTimer;
 
 import org.junit.Rule;
@@ -90,6 +95,8 @@
             .setProvideMainThread(true)
             .build();
 
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     private static final String TAG = BatteryStatsNoteTest.class.getSimpleName();
 
     private static final int UID = 10500;
@@ -104,6 +111,54 @@
     @Mock
     NetworkStatsManager mNetworkStatsManager;
 
+    @DisabledOnRavenwood
+    @EnableFlags(Flags.FLAG_BATTERY_STATS_SCREEN_STATE_EVENT)
+    @Test
+    public void testScreenStateEvent_screenStateEventFlagOn_eventsRecorded() throws Exception {
+        MockBatteryStatsImpl bi = new MockBatteryStatsImpl(new MockClock());
+        bi.forceRecordAllHistory();
+
+        bi.noteScreenStateLocked(0, Display.STATE_ON, Display.STATE_REASON_DEFAULT_POLICY,
+                0, 0, 0);
+        bi.noteScreenStateLocked(2, Display.STATE_DOZE_SUSPEND, Display.STATE_REASON_DRAW_WAKE_LOCK,
+                1, 1, 1);
+
+        BatteryStatsHistoryIterator iterator =
+                bi.iterateBatteryStatsHistory(0, MonotonicClock.UNDEFINED);
+        BatteryStats.HistoryItem item =
+                iterateAndFind(iterator, HistoryItem.EVENT_DISPLAY_STATE_CHANGED);
+        assertThat(item).isNotNull();
+        assertThat(item.eventTag).isNotNull();
+        assertThat(item.eventTag.string).isEqualTo("display=0 state=ON reason=DEFAULT_POLICY");
+        assertThat(item.eventTag.uid).isEqualTo(Process.INVALID_UID);
+
+        item = iterateAndFind(iterator, HistoryItem.EVENT_DISPLAY_STATE_CHANGED);
+        assertThat(item).isNotNull();
+        assertThat(item.eventTag).isNotNull();
+        assertThat(item.eventTag.string)
+                .isEqualTo("display=2 state=DOZE_SUSPEND reason=DRAW_WAKE_LOCK");
+        assertThat(item.eventTag.uid).isEqualTo(Process.INVALID_UID);
+
+        // Last check to make sure that we did not record any extra event.
+        assertThat(iterateAndFind(iterator, HistoryItem.EVENT_DISPLAY_STATE_CHANGED)).isNull();
+    }
+
+    @DisableFlags(Flags.FLAG_BATTERY_STATS_SCREEN_STATE_EVENT)
+    @Test
+    public void testScreenStateEvent_screenStateEventFlagOff_eventsNotRecorded() throws Exception {
+        MockBatteryStatsImpl bi = new MockBatteryStatsImpl(new MockClock());
+        bi.forceRecordAllHistory();
+
+        bi.noteScreenStateLocked(0, Display.STATE_ON, Display.STATE_REASON_DEFAULT_POLICY,
+                0, 0, 0);
+        bi.noteScreenStateLocked(2, Display.STATE_DOZE_SUSPEND, Display.STATE_REASON_DRAW_WAKE_LOCK,
+                1, 1, 1);
+
+        BatteryStatsHistoryIterator iterator =
+                bi.iterateBatteryStatsHistory(0, MonotonicClock.UNDEFINED);
+        assertThat(iterateAndFind(iterator, HistoryItem.EVENT_DISPLAY_STATE_CHANGED)).isNull();
+    }
+
     /**
      * Test BatteryStatsImpl.Uid.noteBluetoothScanResultLocked.
      */
@@ -285,20 +340,15 @@
         final BatteryStatsHistoryIterator iterator =
                 bi.iterateBatteryStatsHistory(0, MonotonicClock.UNDEFINED);
 
-        BatteryStats.HistoryItem item;
+        BatteryStats.HistoryItem item =
+                iterateAndFind(iterator, HistoryItem.EVENT_LONG_WAKE_LOCK_START);
 
-        while ((item = iterator.next()) != null) {
-            if (item.eventCode == HistoryItem.EVENT_LONG_WAKE_LOCK_START) break;
-        }
-        assertThat(item.eventCode).isEqualTo(HistoryItem.EVENT_LONG_WAKE_LOCK_START);
+        assertThat(item).isNotNull();
         assertThat(item.eventTag).isNotNull();
         assertThat(item.eventTag.string).isEqualTo(historyName);
         assertThat(item.eventTag.uid).isEqualTo(UID);
 
-        while ((item = iterator.next()) != null) {
-            if (item.eventCode == HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH) break;
-        }
-        assertThat(item.eventCode).isEqualTo(HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH);
+        item = iterateAndFind(iterator, HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH);
         assertThat(item.eventTag).isNotNull();
         assertThat(item.eventTag.string).isEqualTo(historyName);
         assertThat(item.eventTag.uid).isEqualTo(UID);
@@ -343,20 +393,15 @@
         final BatteryStatsHistoryIterator iterator =
                 bi.iterateBatteryStatsHistory(0, MonotonicClock.UNDEFINED);
 
-        BatteryStats.HistoryItem item;
-
-        while ((item = iterator.next()) != null) {
-            if (item.eventCode == HistoryItem.EVENT_LONG_WAKE_LOCK_START) break;
-        }
-        assertThat(item.eventCode).isEqualTo(HistoryItem.EVENT_LONG_WAKE_LOCK_START);
+        BatteryStats.HistoryItem item =
+                iterateAndFind(iterator, HistoryItem.EVENT_LONG_WAKE_LOCK_START);
+        assertThat(item).isNotNull();
         assertThat(item.eventTag).isNotNull();
         assertThat(item.eventTag.string).isEqualTo(historyName);
         assertThat(item.eventTag.uid).isEqualTo(UID);
 
-        while ((item = iterator.next()) != null) {
-            if (item.eventCode == HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH) break;
-        }
-        assertThat(item.eventCode).isEqualTo(HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH);
+        item = iterateAndFind(iterator, HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH);
+        assertThat(item).isNotNull();
         assertThat(item.eventTag).isNotNull();
         assertThat(item.eventTag.string).isEqualTo(historyName);
         assertThat(item.eventTag.uid).isEqualTo(UID);
@@ -2562,4 +2607,18 @@
                     currentTimeMs, currentTimeMs, mNetworkStatsManager);
         }
     }
+
+    /**
+     * Moves a given {@link BatteryStatsHistoryIterator} until a history item with the given
+     * {@code eventCode} is found and returns the history item. Returns {@code null} if no such item
+     * is found.
+     */
+    private static BatteryStats.HistoryItem iterateAndFind(
+                BatteryStatsHistoryIterator iterator, int eventCode) {
+        BatteryStats.HistoryItem item;
+        while ((item = iterator.next()) != null) {
+            if (item.eventCode == eventCode) return item;
+        }
+        return null;
+    }
 }
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BstatsCpuTimesValidationTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BstatsCpuTimesValidationTest.java
index e4ab227..38fc6a9 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BstatsCpuTimesValidationTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BstatsCpuTimesValidationTest.java
@@ -39,11 +39,11 @@
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
+import android.hardware.display.DisplayManager;
 import android.os.BatteryManager;
 import android.os.BatteryStats;
 import android.os.Bundle;
 import android.os.IBinder;
-import android.os.PowerManager;
 import android.os.Process;
 import android.os.SystemClock;
 import android.platform.test.ravenwood.RavenwoodRule;
@@ -52,10 +52,10 @@
 import android.util.DebugUtils;
 import android.util.KeyValueListParser;
 import android.util.Log;
+import android.view.Display;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
 import androidx.test.uiautomator.UiDevice;
 
 import com.android.frameworks.coretests.aidl.ICmdCallback;
@@ -66,7 +66,6 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TestName;
-import org.junit.runner.RunWith;
 
 import java.util.Arrays;
 import java.util.concurrent.CountDownLatch;
@@ -103,6 +102,7 @@
 
     private static final int GENERAL_TIMEOUT_MS = 4000;
     private static final int GENERAL_INTERVAL_MS = 200;
+    private static final int SCREEN_STATE_CHANGE_TIMEOUT_MS = 10000;
 
     private static final int WORK_DURATION_MS = 2000;
 
@@ -110,6 +110,7 @@
     private static String sOriginalBatteryStatsConsts;
 
     private static Context sContext;
+    private static Display sDisplay;
     private static UiDevice sUiDevice;
     private static int sTestPkgUid;
     private static boolean sCpuFreqTimesAvailable;
@@ -131,6 +132,10 @@
         sTestPkgUid = sContext.getPackageManager().getPackageUid(TEST_PKG, 0);
         executeCmd("cmd deviceidle whitelist +" + TEST_PKG);
         checkCpuTimesAvailability();
+        DisplayManager displayManager = sContext.getSystemService(DisplayManager.class);
+        if (displayManager != null) {
+            sDisplay = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
+        }
     }
 
     @AfterClass
@@ -833,12 +838,12 @@
         executeCmd("input keyevent KEYCODE_WAKEUP");
         executeCmd("wm dismiss-keyguard");
         assertKeyguardUnLocked();
-        assertScreenInteractive(true);
+        assertScreenState(true);
     }
 
     private void screenoff() throws Exception {
         executeCmd("input keyevent KEYCODE_SLEEP");
-        assertScreenInteractive(false);
+        assertScreenState(false);
     }
 
     private void forceStop() throws Exception {
@@ -854,12 +859,15 @@
         );
     }
 
-    private void assertScreenInteractive(boolean interactive) throws Exception {
-        final PowerManager powerManager =
-                (PowerManager) sContext.getSystemService(Context.POWER_SERVICE);
-        assertDelayedCondition("Unexpected screen interactive state", () ->
-                interactive == powerManager.isInteractive() ? null : "expected=" + interactive
-        );
+    private void assertScreenState(boolean expectedOn) throws Exception {
+        if (sDisplay == null) {
+            return;
+        }
+
+        assertDelayedCondition("Unexpected screen-on state",
+                () -> expectedOn == Display.isOnState(sDisplay.getState())
+                        ? null : "expected=" + expectedOn,
+                SCREEN_STATE_CHANGE_TIMEOUT_MS, GENERAL_INTERVAL_MS);
     }
 
     private void assertDelayedCondition(String errMsgPrefix, ExpectedCondition condition)
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerCalculatorTest.java
index 88d4ea7..2da98e8 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerCalculatorTest.java
@@ -61,7 +61,8 @@
         mStatsRule.initMeasuredEnergyStatsLocked();
         BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
 
-        batteryStats.noteScreenStateLocked(0, Display.STATE_ON, 0, 0, 0);
+        batteryStats.noteScreenStateLocked(0, Display.STATE_ON, Display.STATE_REASON_UNKNOWN,
+                0, 0, 0);
         batteryStats.updateDisplayEnergyConsumerStatsLocked(new long[]{0},
                 new int[]{Display.STATE_ON}, 0);
         setProcState(APP_UID1, ActivityManager.PROCESS_STATE_TOP, true,
@@ -79,7 +80,7 @@
         batteryStats.updateDisplayEnergyConsumerStatsLocked(new long[]{300_000_000},
                 new int[]{Display.STATE_ON}, 60 * MINUTE_IN_MS);
 
-        batteryStats.noteScreenStateLocked(0, Display.STATE_OFF,
+        batteryStats.noteScreenStateLocked(0, Display.STATE_OFF, Display.STATE_REASON_UNKNOWN,
                 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
         setProcState(APP_UID2, ActivityManager.PROCESS_STATE_TOP_SLEEPING, false,
                 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
@@ -150,8 +151,10 @@
 
         final int[] screenStates = new int[]{Display.STATE_ON, Display.STATE_OFF};
 
-        batteryStats.noteScreenStateLocked(0, screenStates[0], 0, 0, 0);
-        batteryStats.noteScreenStateLocked(1, screenStates[1], 0, 0, 0);
+        batteryStats.noteScreenStateLocked(0, screenStates[0], Display.STATE_REASON_UNKNOWN,
+                0, 0, 0);
+        batteryStats.noteScreenStateLocked(1, screenStates[1], Display.STATE_REASON_UNKNOWN,
+                0, 0, 0);
         batteryStats.noteScreenBrightnessLocked(0, 255, 0, 0);
         setProcState(APP_UID1, ActivityManager.PROCESS_STATE_TOP, true, 0, 0);
         batteryStats.updateDisplayEnergyConsumerStatsLocked(new long[]{300, 400}, screenStates, 0);
@@ -166,10 +169,10 @@
 
         screenStates[0] = Display.STATE_OFF;
         screenStates[1] = Display.STATE_ON;
-        batteryStats.noteScreenStateLocked(0, screenStates[0],
+        batteryStats.noteScreenStateLocked(0, screenStates[0], Display.STATE_REASON_UNKNOWN,
                 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
-        batteryStats.noteScreenStateLocked(1, screenStates[1], 80 * MINUTE_IN_MS,
-                80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
+        batteryStats.noteScreenStateLocked(1, screenStates[1], Display.STATE_REASON_UNKNOWN,
+                80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
         batteryStats.updateDisplayEnergyConsumerStatsLocked(new long[]{600_000_000, 500},
                 screenStates, 80 * MINUTE_IN_MS);
 
@@ -178,8 +181,8 @@
         batteryStats.noteScreenBrightnessLocked(1, 75, 98 * MINUTE_IN_MS, 98 * MINUTE_IN_MS);
 
         screenStates[1] = Display.STATE_OFF;
-        batteryStats.noteScreenStateLocked(1, screenStates[1], 110 * MINUTE_IN_MS,
-                110 * MINUTE_IN_MS, 110 * MINUTE_IN_MS);
+        batteryStats.noteScreenStateLocked(1, screenStates[1], Display.STATE_REASON_UNKNOWN,
+                110 * MINUTE_IN_MS, 110 * MINUTE_IN_MS, 110 * MINUTE_IN_MS);
         batteryStats.updateDisplayEnergyConsumerStatsLocked(new long[]{700, 800_000_000},
                 screenStates, 110 * MINUTE_IN_MS);
 
@@ -240,7 +243,8 @@
     public void testPowerProfileBasedModel() {
         BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
 
-        batteryStats.noteScreenStateLocked(0, Display.STATE_ON, 0, 0, 0);
+        batteryStats.noteScreenStateLocked(0, Display.STATE_ON, Display.STATE_REASON_UNKNOWN,
+                0, 0, 0);
         batteryStats.noteScreenBrightnessLocked(0, 255, 0, 0);
         setProcState(APP_UID1, ActivityManager.PROCESS_STATE_TOP, true,
                 0, 0);
@@ -253,7 +257,7 @@
         setProcState(APP_UID2, ActivityManager.PROCESS_STATE_TOP, true,
                 20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS);
 
-        batteryStats.noteScreenStateLocked(0, Display.STATE_OFF,
+        batteryStats.noteScreenStateLocked(0, Display.STATE_OFF, Display.STATE_REASON_UNKNOWN,
                 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
         setProcState(APP_UID2, ActivityManager.PROCESS_STATE_TOP_SLEEPING, false,
                 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
@@ -313,8 +317,10 @@
 
         BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
 
-        batteryStats.noteScreenStateLocked(0, Display.STATE_ON, 0, 0, 0);
-        batteryStats.noteScreenStateLocked(1, Display.STATE_OFF, 0, 0, 0);
+        batteryStats.noteScreenStateLocked(0, Display.STATE_ON, Display.STATE_REASON_UNKNOWN,
+                0, 0, 0);
+        batteryStats.noteScreenStateLocked(1, Display.STATE_OFF, Display.STATE_REASON_UNKNOWN,
+                0, 0, 0);
         batteryStats.noteScreenBrightnessLocked(0, 255, 0, 0);
         setProcState(APP_UID1, ActivityManager.PROCESS_STATE_TOP, true,
                 0, 0);
@@ -327,16 +333,16 @@
         setProcState(APP_UID2, ActivityManager.PROCESS_STATE_TOP, true,
                 20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS);
 
-        batteryStats.noteScreenStateLocked(0, Display.STATE_OFF,
+        batteryStats.noteScreenStateLocked(0, Display.STATE_OFF, Display.STATE_REASON_UNKNOWN,
                 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
-        batteryStats.noteScreenStateLocked(1, Display.STATE_ON, 80 * MINUTE_IN_MS,
-                80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
+        batteryStats.noteScreenStateLocked(1, Display.STATE_ON, Display.STATE_REASON_UNKNOWN,
+                80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
         batteryStats.noteScreenBrightnessLocked(1, 20, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
 
         batteryStats.noteScreenBrightnessLocked(1, 250, 86 * MINUTE_IN_MS, 86 * MINUTE_IN_MS);
         batteryStats.noteScreenBrightnessLocked(1, 75, 98 * MINUTE_IN_MS, 98 * MINUTE_IN_MS);
-        batteryStats.noteScreenStateLocked(1, Display.STATE_OFF, 110 * MINUTE_IN_MS,
-                110 * MINUTE_IN_MS, 110 * MINUTE_IN_MS);
+        batteryStats.noteScreenStateLocked(1, Display.STATE_OFF, Display.STATE_REASON_UNKNOWN,
+                110 * MINUTE_IN_MS, 110 * MINUTE_IN_MS, 110 * MINUTE_IN_MS);
 
         setProcState(APP_UID2, ActivityManager.PROCESS_STATE_TOP_SLEEPING, false,
                 110 * MINUTE_IN_MS, 110 * MINUTE_IN_MS);
diff --git a/services/tests/selinux/Android.bp b/services/tests/selinux/Android.bp
index 12a7038..048978a 100644
--- a/services/tests/selinux/Android.bp
+++ b/services/tests/selinux/Android.bp
@@ -42,9 +42,9 @@
         "mockito_extended",
     ],
     libs: [
-        "android.test.base",
-        "android.test.mock",
-        "android.test.runner",
+        "android.test.base.stubs.system",
+        "android.test.mock.stubs.system",
+        "android.test.runner.stubs.system",
         "servicestests-core-utils",
     ],
     static_libs: [
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index e55e0f2..bbe0755 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -30,6 +30,7 @@
         "src/**/*.kt",
 
         "test-apps/SuspendTestApp/src/**/*.java",
+        "test-apps/DisplayManagerTestApp/src/**/*.java",
     ],
 
     kotlincflags: [
@@ -90,6 +91,7 @@
         "net_flags_lib",
         "CtsVirtualDeviceCommonLib",
         "com_android_server_accessibility_flags_lib",
+        "locksettings_flags_lib",
     ],
 
     libs: [
@@ -134,6 +136,7 @@
     },
 
     data: [
+        ":DisplayManagerTestApp",
         ":SimpleServiceTestApp1",
         ":SimpleServiceTestApp2",
         ":SimpleServiceTestApp3",
diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml
index b56af87..5298251 100644
--- a/services/tests/servicestests/AndroidTest.xml
+++ b/services/tests/servicestests/AndroidTest.xml
@@ -35,6 +35,7 @@
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="install-arg" value="-t" />
+        <option name="test-file-name" value="DisplayManagerTestApp.apk" />
         <option name="test-file-name" value="FrameworksServicesTests.apk" />
         <option name="test-file-name" value="SuspendTestApp.apk" />
         <option name="test-file-name" value="SimpleServiceTestApp1.apk" />
diff --git a/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpPersistenceTest.java b/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpPersistenceTest.java
index bc3a5ca..2ff0c62 100644
--- a/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpPersistenceTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpPersistenceTest.java
@@ -86,7 +86,8 @@
         int attributionChainId = AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE;
 
         mDiscreteRegistry.recordDiscreteAccess(uid, packageName, deviceId, op, null, opFlags,
-                uidState, accessTime, duration, attributionFlags, attributionChainId);
+                uidState, accessTime, duration, attributionFlags, attributionChainId,
+                DiscreteRegistry.ACCESS_TYPE_FINISH_OP);
 
         // Verify in-memory object is correct
         fetchDiscreteOpsAndValidate(uid, packageName, op, deviceId, null, accessTime,
@@ -117,7 +118,8 @@
         int attributionChainId = 10;
 
         mDiscreteRegistry.recordDiscreteAccess(uid, packageName, deviceId, op, null, opFlags,
-                uidState, accessTime, duration, attributionFlags, attributionChainId);
+                uidState, accessTime, duration, attributionFlags, attributionChainId,
+                DiscreteRegistry.ACCESS_TYPE_START_OP);
 
         fetchDiscreteOpsAndValidate(uid, packageName, op, deviceId, null, accessTime,
                 duration, uidState, opFlags, attributionFlags, attributionChainId);
diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/location/contexthub/TEST_MAPPING
index 58f5bb3..9b23b49 100644
--- a/services/tests/servicestests/src/com/android/server/location/contexthub/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/location/contexthub/TEST_MAPPING
@@ -6,23 +6,7 @@
   ],
   "postsubmit": [
     {
-      "name": "FrameworksServicesTests",
-      "options": [
-        {
-          "include-filter": "com.android.server.location.contexthub."
-        },
-        {
-          // I believe this include annotation is preventing tests from being run
-          // as there are no matching tests with the Postsubmit annotation.
-          "include-annotation": "android.platform.test.annotations.Postsubmit"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        }
-      ]
+      "name": "FrameworksServicesTests_com_android_server_location_contexthub"
     }
   ]
 }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
index 2ba3969..87c9db2 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -92,6 +92,7 @@
 
     MockLockSettingsContext mContext;
     LockSettingsStorageTestable mStorage;
+    LockSettingsStrongAuth mStrongAuth;
 
     Resources mResources;
     FakeGateKeeperService mGateKeeperService;
@@ -135,6 +136,7 @@
         mFingerprintManager = mock(FingerprintManager.class);
         mFaceManager = mock(FaceManager.class);
         mPackageManager = mock(PackageManager.class);
+        mStrongAuth = mock(LockSettingsStrongAuth.class);
 
         LocalServices.removeServiceForTest(LockSettingsInternal.class);
         LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
@@ -162,7 +164,7 @@
         mInjector =
                 new LockSettingsServiceTestable.MockInjector(
                         mContext,
-                        mStorage,
+                        mStorage, mStrongAuth,
                         mActivityManager,
                         setUpStorageManagerMock(),
                         mSpManager,
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
index 93fc071a..abd39b0 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
@@ -50,6 +50,7 @@
     public static class MockInjector extends LockSettingsService.Injector {
 
         private LockSettingsStorage mLockSettingsStorage;
+        private final LockSettingsStrongAuth mStrongAuth;
         private IActivityManager mActivityManager;
         private IStorageManager mStorageManager;
         private SyntheticPasswordManager mSpManager;
@@ -62,12 +63,14 @@
         public boolean mIsMainUserPermanentAdmin = false;
 
         public MockInjector(Context context, LockSettingsStorage storage,
+                LockSettingsStrongAuth strongAuth,
                 IActivityManager activityManager, IStorageManager storageManager,
                 SyntheticPasswordManager spManager, FakeGsiService gsiService,
                 RecoverableKeyStoreManager recoverableKeyStoreManager,
                 UserManagerInternal userManagerInternal, DeviceStateCache deviceStateCache) {
             super(context);
             mLockSettingsStorage = storage;
+            mStrongAuth = strongAuth;
             mActivityManager = activityManager;
             mStorageManager = storageManager;
             mSpManager = spManager;
@@ -89,7 +92,7 @@
 
         @Override
         public LockSettingsStrongAuth getStrongAuth() {
-            return mock(LockSettingsStrongAuth.class);
+            return mStrongAuth;
         }
 
         @Override
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
index 601a016..2868e55 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
@@ -17,6 +17,7 @@
 package com.android.server.locksettings;
 
 import static android.Manifest.permission.CONFIGURE_FACTORY_RESET_PROTECTION;
+import static android.security.Flags.FLAG_CLEAR_STRONG_AUTH_ON_ADD_PRIMARY_CREDENTIAL;
 import static android.security.Flags.FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS;
 
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
@@ -46,6 +47,10 @@
 import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.service.gatekeeper.GateKeeperResponse;
 import android.text.TextUtils;
@@ -71,6 +76,8 @@
 @RunWith(AndroidJUnit4.class)
 public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
     @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
 
     @Before
     public void setUp() {
@@ -258,6 +265,34 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(FLAG_CLEAR_STRONG_AUTH_ON_ADD_PRIMARY_CREDENTIAL)
+    public void setLockCredential_forPrimaryUser_clearsStrongAuthWhenFlagIsOn()
+            throws Exception {
+        setCredential(PRIMARY_USER_ID, newPassword("password"));
+
+        verify(mStrongAuth).reportUnlock(PRIMARY_USER_ID);
+    }
+
+    @Test
+    @RequiresFlagsDisabled(FLAG_CLEAR_STRONG_AUTH_ON_ADD_PRIMARY_CREDENTIAL)
+    public void setLockCredential_forPrimaryUser_leavesStrongAuthWhenFlagIsOff()
+            throws Exception {
+        setCredential(PRIMARY_USER_ID, newPassword("password"));
+
+        verify(mStrongAuth, never()).reportUnlock(anyInt());
+    }
+
+    @Test
+    public void setLockCredential_forPrimaryUserWithCredential_leavesStrongAuth() throws Exception {
+        setCredential(PRIMARY_USER_ID, newPassword("password"));
+        reset(mStrongAuth);
+
+        setCredential(PRIMARY_USER_ID, newPassword("password2"), newPassword("password"));
+
+        verify(mStrongAuth, never()).reportUnlock(anyInt());
+    }
+
+    @Test
     public void testSetLockCredential_forProfileWithSeparateChallenge_sendsCredentials()
             throws Exception {
         setCredential(MANAGED_PROFILE_USER_ID, newPattern("12345"));
@@ -278,6 +313,28 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(FLAG_CLEAR_STRONG_AUTH_ON_ADD_PRIMARY_CREDENTIAL)
+    public void setLockCredential_profileWithNewSeparateChallenge_clearsStrongAuthWhenFlagIsOn()
+            throws Exception {
+        mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, true, null);
+
+        setCredential(MANAGED_PROFILE_USER_ID, newPattern("12345"));
+
+        verify(mStrongAuth).reportUnlock(MANAGED_PROFILE_USER_ID);
+    }
+
+    @Test
+    @RequiresFlagsDisabled(FLAG_CLEAR_STRONG_AUTH_ON_ADD_PRIMARY_CREDENTIAL)
+    public void setLockCredential_profileWithNewSeparateChallenge_leavesStrongAuthWhenFlagIsOff()
+            throws Exception {
+        mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, true, null);
+
+        setCredential(MANAGED_PROFILE_USER_ID, newPattern("12345"));
+
+        verify(mStrongAuth, never()).reportUnlock(anyInt());
+    }
+
+    @Test
     public void testSetLockCredential_forProfileWithUnifiedChallenge_doesNotSendRandomCredential()
             throws Exception {
         mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
@@ -305,6 +362,67 @@
                         MANAGED_PROFILE_USER_ID);
     }
 
+
+    @Test
+    public void setLockCredential_primaryWithUnifiedProfileAndCredential_leavesStrongAuthForBoth()
+            throws Exception {
+        final LockscreenCredential oldCredential = newPassword("oldPassword");
+        final LockscreenCredential newCredential = newPassword("newPassword");
+        setCredential(PRIMARY_USER_ID, oldCredential);
+        mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
+        reset(mStrongAuth);
+
+        setCredential(PRIMARY_USER_ID, newCredential, oldCredential);
+
+        verify(mStrongAuth, never()).reportUnlock(anyInt());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_CLEAR_STRONG_AUTH_ON_ADD_PRIMARY_CREDENTIAL)
+    public void setLockCredential_primaryWithUnifiedProfile_clearsStrongAuthForBothWhenFlagIsOn()
+            throws Exception {
+        final LockscreenCredential credential = newPassword("oldPassword");
+        setCredential(PRIMARY_USER_ID, credential);
+        mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
+        clearCredential(PRIMARY_USER_ID, credential);
+        reset(mStrongAuth);
+
+        setCredential(PRIMARY_USER_ID, credential);
+
+        verify(mStrongAuth).reportUnlock(PRIMARY_USER_ID);
+        verify(mStrongAuth).reportUnlock(MANAGED_PROFILE_USER_ID);
+    }
+
+    @Test
+    @RequiresFlagsDisabled(FLAG_CLEAR_STRONG_AUTH_ON_ADD_PRIMARY_CREDENTIAL)
+    public void setLockCredential_primaryWithUnifiedProfile_leavesStrongAuthForBothWhenFlagIsOff()
+            throws Exception {
+        final LockscreenCredential credential = newPassword("oldPassword");
+        setCredential(PRIMARY_USER_ID, credential);
+        mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
+        clearCredential(PRIMARY_USER_ID, credential);
+        reset(mStrongAuth);
+
+        setCredential(PRIMARY_USER_ID, credential);
+
+        verify(mStrongAuth, never()).reportUnlock(anyInt());
+    }
+
+
+    @Test
+    public void setLockCredential_primaryWithUnifiedProfileWithCredential_leavesStrongAuthForBoth()
+            throws Exception {
+        final LockscreenCredential oldCredential = newPassword("oldPassword");
+        final LockscreenCredential newCredential = newPassword("newPassword");
+        setCredential(PRIMARY_USER_ID, oldCredential);
+        mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
+        reset(mStrongAuth);
+
+        setCredential(PRIMARY_USER_ID, newCredential, oldCredential);
+
+        verify(mStrongAuth, never()).reportUnlock(anyInt());
+    }
+
     @Test
     public void
             testSetLockCredential_forPrimaryUserWithUnifiedChallengeProfile_removesBothCredentials()
@@ -343,6 +461,18 @@
     }
 
     @Test
+    public void clearLockCredential_primaryWithUnifiedProfile_leavesStrongAuthForBoth()
+            throws Exception {
+        setCredential(PRIMARY_USER_ID, newPassword("password"));
+        mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
+        reset(mStrongAuth);
+
+        clearCredential(PRIMARY_USER_ID, newPassword("password"));
+
+        verify(mStrongAuth, never()).reportUnlock(anyInt());
+    }
+
+    @Test
     public void testSetLockCredential_forUnifiedToSeparateChallengeProfile_sendsNewCredentials()
             throws Exception {
         final LockscreenCredential parentPassword = newPassword("parentPassword");
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
index d6f7e21..d071c15 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
@@ -60,6 +60,9 @@
 import android.os.ServiceSpecificException;
 import android.os.UserManager;
 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 androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -70,6 +73,7 @@
 import com.android.server.pm.UserManagerInternal;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -108,6 +112,9 @@
             0x26, 0x52, 0x72, 0x63, 0x63, 0x61, 0x78, 0x23,
     };
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     private Context mContext;
     private UserManager mUserManager;
     private UserManagerInternal mUserManagerInternal;
@@ -145,7 +152,6 @@
         private RebootEscrowProviderInterface mRebootEscrowProviderInUse;
         private ConnectivityManager.NetworkCallback mNetworkCallback;
         private Consumer<ConnectivityManager.NetworkCallback> mNetworkConsumer;
-        private boolean mWaitForInternet;
 
         MockInjector(
                 Context context,
@@ -159,7 +165,6 @@
             super(context, storage, userManagerInternal);
             mRebootEscrow = rebootEscrow;
             mServerBased = false;
-            mWaitForInternet = false;
             RebootEscrowProviderHalImpl.Injector halInjector =
                     new RebootEscrowProviderHalImpl.Injector() {
                         @Override
@@ -185,7 +190,6 @@
             super(context, storage, userManagerInternal);
             mRebootEscrow = null;
             mServerBased = true;
-            mWaitForInternet = false;
             RebootEscrowProviderServerBasedImpl.Injector injector =
                     new RebootEscrowProviderServerBasedImpl.Injector(serviceConnection) {
                         @Override
@@ -227,15 +231,6 @@
         }
 
         @Override
-        public boolean waitForInternet() {
-            return mWaitForInternet;
-        }
-
-        public void setWaitForNetwork(boolean waitForNetworkEnabled) {
-            mWaitForInternet = waitForNetworkEnabled;
-        }
-
-        @Override
         public boolean isNetworkConnected() {
             return false;
         }
@@ -934,10 +929,10 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_WAIT_FOR_INTERNET_ROR)
     public void loadRebootEscrowDataIfAvailable_serverBasedWaitForInternet_success()
             throws Exception {
         setServerBasedRebootEscrowProvider();
-        mMockInjector.setWaitForNetwork(true);
 
         when(mInjected.getBootCount()).thenReturn(0);
         RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
@@ -987,10 +982,10 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_WAIT_FOR_INTERNET_ROR)
     public void loadRebootEscrowDataIfAvailable_serverBasedWaitForInternetRemoteException_Failure()
             throws Exception {
         setServerBasedRebootEscrowProvider();
-        mMockInjector.setWaitForNetwork(true);
 
         when(mInjected.getBootCount()).thenReturn(0);
         RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
@@ -1042,10 +1037,10 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_WAIT_FOR_INTERNET_ROR)
     public void loadRebootEscrowDataIfAvailable_waitForInternet_networkUnavailable()
             throws Exception {
         setServerBasedRebootEscrowProvider();
-        mMockInjector.setWaitForNetwork(true);
 
         when(mInjected.getBootCount()).thenReturn(0);
         RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
@@ -1090,9 +1085,9 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_WAIT_FOR_INTERNET_ROR)
     public void loadRebootEscrowDataIfAvailable_waitForInternet_networkLost() throws Exception {
         setServerBasedRebootEscrowProvider();
-        mMockInjector.setWaitForNetwork(true);
 
         when(mInjected.getBootCount()).thenReturn(0);
         RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
@@ -1145,10 +1140,10 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_WAIT_FOR_INTERNET_ROR)
     public void loadRebootEscrowDataIfAvailable_waitForInternet_networkAvailableWithDelay()
             throws Exception {
         setServerBasedRebootEscrowProvider();
-        mMockInjector.setWaitForNetwork(true);
 
         when(mInjected.getBootCount()).thenReturn(0);
         RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
@@ -1204,10 +1199,10 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_WAIT_FOR_INTERNET_ROR)
     public void loadRebootEscrowDataIfAvailable_waitForInternet_timeoutExhausted()
             throws Exception {
         setServerBasedRebootEscrowProvider();
-        mMockInjector.setWaitForNetwork(true);
 
         when(mInjected.getBootCount()).thenReturn(0);
         RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
@@ -1264,10 +1259,10 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_WAIT_FOR_INTERNET_ROR)
     public void loadRebootEscrowDataIfAvailable_serverBasedWaitForNetwork_retryCountExhausted()
             throws Exception {
         setServerBasedRebootEscrowProvider();
-        mMockInjector.setWaitForNetwork(true);
 
         when(mInjected.getBootCount()).thenReturn(0);
         RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
@@ -1320,10 +1315,10 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_WAIT_FOR_INTERNET_ROR)
     public void loadRebootEscrowDataIfAvailable_ServerBasedWaitForInternet_RetrySuccess()
             throws Exception {
         setServerBasedRebootEscrowProvider();
-        mMockInjector.setWaitForNetwork(true);
 
         when(mInjected.getBootCount()).thenReturn(0);
         RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
diff --git a/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING
index 944c1df..dc3b144 100644
--- a/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING
@@ -4,12 +4,7 @@
       "name": "FrameworksServicesTests_om"
     },
     {
-      "name": "PackageManagerServiceHostTests",
-      "options": [
-        {
-          "include-filter": "com.android.server.pm.test.OverlayActorVisibilityTest"
-        }
-      ]
+      "name": "PackageManagerServiceHostTests_test_overlayactorvisibilitytest"
     }
   ]
 }
diff --git a/services/tests/servicestests/test-apps/DisplayManagerTestApp/Android.bp b/services/tests/servicestests/test-apps/DisplayManagerTestApp/Android.bp
new file mode 100644
index 0000000..962ae9b
--- /dev/null
+++ b/services/tests/servicestests/test-apps/DisplayManagerTestApp/Android.bp
@@ -0,0 +1,37 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test_helper_app {
+    name: "DisplayManagerTestApp",
+
+    sdk_version: "current",
+
+    srcs: ["**/*.java"],
+
+    dex_preopt: {
+        enabled: false,
+    },
+    optimize: {
+        enabled: false,
+    },
+}
diff --git a/services/tests/servicestests/test-apps/DisplayManagerTestApp/AndroidManifest.xml b/services/tests/servicestests/test-apps/DisplayManagerTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..c0d9d6f
--- /dev/null
+++ b/services/tests/servicestests/test-apps/DisplayManagerTestApp/AndroidManifest.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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.servicestests.apps.displaymanagertestapp">
+
+    <application android:label="DisplayEventTestApp">
+        <activity android:name=".DisplayEventActivity"
+                  android:exported="true" />
+    </application>
+
+</manifest>
diff --git a/services/tests/servicestests/test-apps/DisplayManagerTestApp/OWNERS b/services/tests/servicestests/test-apps/DisplayManagerTestApp/OWNERS
new file mode 100644
index 0000000..e9557f8
--- /dev/null
+++ b/services/tests/servicestests/test-apps/DisplayManagerTestApp/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 345010
+
+include /services/core/java/com/android/server/display/OWNERS
diff --git a/services/tests/servicestests/test-apps/DisplayManagerTestApp/src/com/android/servicestests/apps/displaymanagertestapp/DisplayEventActivity.java b/services/tests/servicestests/test-apps/DisplayManagerTestApp/src/com/android/servicestests/apps/displaymanagertestapp/DisplayEventActivity.java
new file mode 100644
index 0000000..07754b2
--- /dev/null
+++ b/services/tests/servicestests/test-apps/DisplayManagerTestApp/src/com/android/servicestests/apps/displaymanagertestapp/DisplayEventActivity.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.servicestests.apps.displaymanagertestapp;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.hardware.display.DisplayManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * A simple activity manipulating displays and listening to corresponding display events
+ */
+public class DisplayEventActivity extends Activity {
+    private static final String TAG = DisplayEventActivity.class.getSimpleName();
+
+    private static final String TEST_DISPLAYS = "DISPLAYS";
+    private static final String TEST_MESSENGER = "MESSENGER";
+
+    private static final int MESSAGE_LAUNCHED = 1;
+    private static final int MESSAGE_CALLBACK = 2;
+
+    private static final int DISPLAY_ADDED = 1;
+    private static final int DISPLAY_CHANGED = 2;
+    private static final int DISPLAY_REMOVED = 3;
+
+    private int mExpectedDisplayCount;
+    private int mSeenDisplayCount;
+    private Messenger mMessenger;
+    private DisplayManager mDisplayManager;
+    private DisplayManager.DisplayListener mDisplayListener;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Intent intent = getIntent();
+        mExpectedDisplayCount = 0;
+        mSeenDisplayCount = intent.getIntExtra(TEST_DISPLAYS, 0);
+        mMessenger = intent.getParcelableExtra(TEST_MESSENGER, Messenger.class);
+        mDisplayManager = getApplicationContext().getSystemService(DisplayManager.class);
+        mDisplayListener = new DisplayManager.DisplayListener() {
+            @Override
+            public void onDisplayAdded(int displayId) {
+                callback(displayId, DISPLAY_ADDED);
+            }
+
+            @Override
+            public void onDisplayRemoved(int displayId) {
+                callback(displayId, DISPLAY_REMOVED);
+            }
+
+            @Override
+            public void onDisplayChanged(int displayId) {
+                callback(displayId, DISPLAY_CHANGED);
+            }
+        };
+        Handler handler = new Handler(Looper.getMainLooper());
+        mDisplayManager.registerDisplayListener(mDisplayListener, handler);
+        launched();
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        mDisplayManager.unregisterDisplayListener(mDisplayListener);
+    }
+
+    private void launched() {
+        try {
+            Message msg = Message.obtain();
+            msg.what = MESSAGE_LAUNCHED;
+            msg.arg1 = Process.myPid();
+            msg.arg2 = Process.myUid();
+            Log.d(TAG, "Launched " + mSeenDisplayCount);
+            mMessenger.send(msg);
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+    }
+
+    private void callback(int displayId, int event) {
+        try {
+            Message msg = Message.obtain();
+            msg.what = MESSAGE_CALLBACK;
+            msg.arg1 = displayId;
+            msg.arg2 = event;
+            Log.d(TAG, "Msg " + msg.arg1 + " " + msg.arg2);
+            mMessenger.send(msg);
+            if (event == DISPLAY_REMOVED) {
+                mExpectedDisplayCount++;
+                if (mExpectedDisplayCount >= mSeenDisplayCount) {
+                    finish();
+                }
+            }
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+    }
+}
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 196bc47..96ddf80 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -99,6 +99,7 @@
 import static android.service.notification.Condition.SOURCE_CONTEXT;
 import static android.service.notification.Condition.SOURCE_USER_ACTION;
 import static android.service.notification.Condition.STATE_TRUE;
+import static android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION;
 import static android.service.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUPING;
 import static android.service.notification.Flags.FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS;
 import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
@@ -4412,7 +4413,7 @@
                 eq(mTestNotificationChannel.getId()), anyBoolean()))
                 .thenReturn(mTestNotificationChannel);
         when(mPreferencesHelper.deleteNotificationChannel(eq(mPkg), anyInt(),
-                eq(mTestNotificationChannel.getId()),  anyInt(), anyBoolean())).thenReturn(true);
+                eq(mTestNotificationChannel.getId()), anyInt(), anyBoolean())).thenReturn(true);
         reset(mListeners);
         mBinderService.deleteNotificationChannel(mPkg, mTestNotificationChannel.getId());
         verify(mListeners, times(1)).notifyNotificationChannelChanged(eq(mPkg),
@@ -4421,6 +4422,24 @@
     }
 
     @Test
+    @EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION)
+    public void testAppsCannotDeleteBundleChannel() throws Exception {
+        when(mCompanionMgr.getAssociations(mPkg, mUserId))
+                .thenReturn(singletonList(mock(AssociationInfo.class)));
+        mService.setPreferencesHelper(mPreferencesHelper);
+        when(mPreferencesHelper.getNotificationChannel(eq(mPkg), anyInt(),
+                eq(NEWS_ID), anyBoolean()))
+                .thenReturn(mTestNotificationChannel);
+        when(mPreferencesHelper.deleteNotificationChannel(eq(mPkg), anyInt(),
+                eq(NEWS_ID), anyInt(), anyBoolean())).thenReturn(true);
+        reset(mListeners);
+        mBinderService.deleteNotificationChannel(mPkg, NEWS_ID);
+        verify(mListeners, never()).notifyNotificationChannelChanged(eq(mPkg),
+                eq(Process.myUserHandle()), any(),
+                eq(NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_DELETED));
+    }
+
+    @Test
     public void testDeleteChannelOnlyDoExtraWorkIfExisted() throws Exception {
         when(mCompanionMgr.getAssociations(mPkg, mUserId))
                 .thenReturn(singletonList(mock(AssociationInfo.class)));
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 559c324..1905ae4 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -6222,6 +6222,47 @@
                 .isEqualTo(IMPORTANCE_LOW);
     }
 
+    @Test
+    @EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION)
+    public void testNotificationBundles_appsCannotUpdate() {
+        // do something that triggers settings creation for an app
+        mHelper.setShowBadge(PKG_O, UID_O, true);
+
+        NotificationChannel fromApp =
+                new NotificationChannel(NEWS_ID, "The best channel", IMPORTANCE_HIGH);
+        mHelper.createNotificationChannel(PKG_O, UID_O, fromApp, true, false, UID_O, false);
+
+        assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, NEWS_ID, false).getImportance())
+                .isEqualTo(IMPORTANCE_LOW);
+    }
+
+    @Test
+    @EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION)
+    public void testNotificationBundles_osCanAllowToBypassDnd() {
+        // do something that triggers settings creation for an app
+        mHelper.setShowBadge(PKG_O, UID_O, true);
+
+        NotificationChannel fromApp =
+                new NotificationChannel(NEWS_ID, "The best channel", IMPORTANCE_HIGH);
+        mHelper.createNotificationChannel(PKG_O, UID_O, fromApp, true, false, UID_O, false);
+    }
+
+    @Test
+    @EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION)
+    public void testUnDeleteBundleChannelsOnLoadIfNotUserChange() throws Exception {
+        mHelper.setShowBadge(PKG_P, UID_P, true);
+        // the public create/update methods should prevent this, so take advantage of the fact that
+        // the object is in the same process
+        mHelper.getNotificationChannel(PKG_P, UID_P, SOCIAL_MEDIA_ID, true).setDeleted(true);
+
+        ByteArrayOutputStream baos = writeXmlAndPurge(PKG_N_MR1, UID_N_MR1, false,
+                UserHandle.USER_ALL, SOCIAL_MEDIA_ID);
+
+        loadStreamXml(baos, false, UserHandle.USER_ALL);
+
+        assertThat(mXmlHelper.getNotificationChannel(PKG_P, UID_P, SOCIAL_MEDIA_ID, true).
+                isDeleted()).isFalse();
+    }
 
     @Test
     public void testRestoredWithoutUid_threadSafety() throws Exception {
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 f8ff1f4..efcf027 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
@@ -756,7 +756,7 @@
         assertEquals("a", fromXml.getPkg());
 
         fromXml.condition = new Condition(Uri.EMPTY, "", Condition.STATE_TRUE);
-        assertTrue(fromXml.isAutomaticActive());
+        assertTrue(fromXml.isActive());
     }
 
     @Test
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 baa633f..39a9d30 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -5788,7 +5788,7 @@
 
         // ... but it is NOT active
         ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(newRuleId);
-        assertThat(storedRule.isAutomaticActive()).isFalse();
+        assertThat(storedRule.isActive()).isFalse();
         assertThat(storedRule.isTrueOrUnknown()).isFalse();
         assertThat(storedRule.condition).isNull();
         assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
@@ -5841,7 +5841,7 @@
 
         // ... but it is NEITHER active NOR snoozed.
         ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(newRuleId);
-        assertThat(storedRule.isAutomaticActive()).isFalse();
+        assertThat(storedRule.isActive()).isFalse();
         assertThat(storedRule.isTrueOrUnknown()).isFalse();
         assertThat(storedRule.condition).isNull();
         assertThat(storedRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE);
@@ -6619,7 +6619,7 @@
                 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.isActive()).isTrue();
         assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_ACTIVATE);
         assertThat(zenRule.condition).isNull();
 
@@ -6627,14 +6627,14 @@
                 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.isActive()).isFalse();
         assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE);
         assertThat(zenRule.condition).isNull();
 
         // Bonus check: app has resumed control over the rule and can now turn it on.
         mZenModeHelper.setAutomaticZenRuleState(ruleId, autoOn,  ORIGIN_APP, CUSTOM_PKG_UID);
         zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
-        assertThat(zenRule.isAutomaticActive()).isTrue();
+        assertThat(zenRule.isActive()).isTrue();
         assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE);
         assertThat(zenRule.condition).isEqualTo(autoOn);
     }
@@ -6655,7 +6655,7 @@
 
         mZenModeHelper.setAutomaticZenRuleState(ruleId, autoOn,  ORIGIN_APP, CUSTOM_PKG_UID);
         zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
-        assertThat(zenRule.isAutomaticActive()).isTrue();
+        assertThat(zenRule.isActive()).isTrue();
         assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE);
         assertThat(zenRule.condition).isEqualTo(autoOn);
 
@@ -6663,7 +6663,7 @@
                 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.isActive()).isFalse();
         assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_DEACTIVATE);
         assertThat(zenRule.condition).isEqualTo(autoOn);
 
@@ -6671,14 +6671,14 @@
                 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.isActive()).isTrue();
         assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE);
         assertThat(zenRule.condition).isEqualTo(autoOn);
 
         // Bonus check: app has resumed control over the rule and can now turn it off.
         mZenModeHelper.setAutomaticZenRuleState(ruleId, autoOff,  ORIGIN_APP, CUSTOM_PKG_UID);
         zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
-        assertThat(zenRule.isAutomaticActive()).isFalse();
+        assertThat(zenRule.isActive()).isFalse();
         assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE);
         assertThat(zenRule.condition).isEqualTo(autoOff);
     }
@@ -6696,7 +6696,7 @@
                 new Condition(rule.getConditionId(), "auto-on", STATE_TRUE, SOURCE_CONTEXT),
                 ORIGIN_APP, CUSTOM_PKG_UID);
         ZenRule zenRuleOn = mZenModeHelper.mConfig.automaticRules.get(ruleId);
-        assertThat(zenRuleOn.isAutomaticActive()).isTrue();
+        assertThat(zenRuleOn.isActive()).isTrue();
         assertThat(zenRuleOn.getConditionOverride()).isEqualTo(OVERRIDE_NONE);
         assertThat(zenRuleOn.condition).isNotNull();
 
@@ -6704,7 +6704,7 @@
                 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.isActive()).isFalse();
         assertThat(zenRuleOff.getConditionOverride()).isEqualTo(OVERRIDE_DEACTIVATE);
         assertThat(zenRuleOff.condition).isNotNull();
     }
@@ -6723,27 +6723,27 @@
                 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.isActive()).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.isActive()).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.isActive()).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();
+        assertThat(zenRule.isActive()).isFalse();
     }
 
     @Test
@@ -6760,35 +6760,35 @@
                 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.isActive()).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.isActive()).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.isActive()).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.isActive()).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.isActive()).isTrue();
         assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE);
     }
 
@@ -6805,14 +6805,14 @@
         mZenModeHelper.setAutomaticZenRuleState(ruleId,
                 new Condition(rule.getConditionId(), "manual-on-from-sysui", STATE_TRUE,
                         SOURCE_USER_ACTION), ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
-        assertThat(getZenRule(ruleId).isAutomaticActive()).isTrue();
+        assertThat(getZenRule(ruleId).isActive()).isTrue();
         assertThat(getZenRule(ruleId).getConditionOverride()).isEqualTo(OVERRIDE_ACTIVATE);
 
         // ... and they can turn it off manually from inside the app.
         mZenModeHelper.setAutomaticZenRuleState(ruleId,
                 new Condition(rule.getConditionId(), "manual-off-from-app", STATE_FALSE,
                         SOURCE_USER_ACTION), ORIGIN_USER_IN_APP, CUSTOM_PKG_UID);
-        assertThat(getZenRule(ruleId).isAutomaticActive()).isFalse();
+        assertThat(getZenRule(ruleId).isActive()).isFalse();
         assertThat(getZenRule(ruleId).getConditionOverride()).isEqualTo(OVERRIDE_NONE);
     }
 
@@ -6829,21 +6829,21 @@
         mZenModeHelper.setAutomaticZenRuleState(ruleId,
                 new Condition(rule.getConditionId(), "auto-on-from-app", STATE_TRUE,
                         SOURCE_SCHEDULE), ORIGIN_APP, CUSTOM_PKG_UID);
-        assertThat(getZenRule(ruleId).isAutomaticActive()).isTrue();
+        assertThat(getZenRule(ruleId).isActive()).isTrue();
         assertThat(getZenRule(ruleId).getConditionOverride()).isEqualTo(OVERRIDE_NONE);
 
         // User manually turns off rule from SysUI / Settings...
         mZenModeHelper.setAutomaticZenRuleState(ruleId,
                 new Condition(rule.getConditionId(), "manual-off-from-sysui", STATE_FALSE,
                         SOURCE_USER_ACTION), ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
-        assertThat(getZenRule(ruleId).isAutomaticActive()).isFalse();
+        assertThat(getZenRule(ruleId).isActive()).isFalse();
         assertThat(getZenRule(ruleId).getConditionOverride()).isEqualTo(OVERRIDE_DEACTIVATE);
 
         // ... and they can turn it on manually from inside the app.
         mZenModeHelper.setAutomaticZenRuleState(ruleId,
                 new Condition(rule.getConditionId(), "manual-on-from-app", STATE_TRUE,
                         SOURCE_USER_ACTION), ORIGIN_USER_IN_APP, CUSTOM_PKG_UID);
-        assertThat(getZenRule(ruleId).isAutomaticActive()).isTrue();
+        assertThat(getZenRule(ruleId).isActive()).isTrue();
         assertThat(getZenRule(ruleId).getConditionOverride()).isEqualTo(OVERRIDE_NONE);
     }
 
@@ -6861,14 +6861,14 @@
         mZenModeHelper.setAutomaticZenRuleState(ruleId,
                 new Condition(rule.getConditionId(), "manual-on-from-app", STATE_TRUE,
                         SOURCE_USER_ACTION), ORIGIN_USER_IN_APP, CUSTOM_PKG_UID);
-        assertThat(getZenRule(ruleId).isAutomaticActive()).isTrue();
+        assertThat(getZenRule(ruleId).isActive()).isTrue();
         assertThat(getZenRule(ruleId).getConditionOverride()).isEqualTo(OVERRIDE_NONE);
 
         // ... so the app can turn it off when its schedule is over.
         mZenModeHelper.setAutomaticZenRuleState(ruleId,
                 new Condition(rule.getConditionId(), "auto-off-from-app", STATE_FALSE,
                         SOURCE_SCHEDULE), ORIGIN_APP, CUSTOM_PKG_UID);
-        assertThat(getZenRule(ruleId).isAutomaticActive()).isFalse();
+        assertThat(getZenRule(ruleId).isActive()).isFalse();
         assertThat(getZenRule(ruleId).getConditionOverride()).isEqualTo(OVERRIDE_NONE);
     }
 
diff --git a/services/tests/vibrator/TEST_MAPPING b/services/tests/vibrator/TEST_MAPPING
index 39bd238..b17b96a 100644
--- a/services/tests/vibrator/TEST_MAPPING
+++ b/services/tests/vibrator/TEST_MAPPING
@@ -1,12 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "FrameworksVibratorServicesTests",
-      "options": [
-        {"exclude-annotation": "androidx.test.filters.FlakyTest"},
-        {"exclude-annotation": "androidx.test.filters.LargeTest"},
-        {"exclude-annotation": "org.junit.Ignore"}
-      ]
+      "name": "FrameworksVibratorServicesTests"
     }
   ],
   "postsubmit": [
diff --git a/services/tests/voiceinteractiontests/TEST_MAPPING b/services/tests/voiceinteractiontests/TEST_MAPPING
index 6cbc49a..466ba54 100644
--- a/services/tests/voiceinteractiontests/TEST_MAPPING
+++ b/services/tests/voiceinteractiontests/TEST_MAPPING
@@ -1,12 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "FrameworksVoiceInteractionTests",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "FrameworksVoiceInteractionTests"
     }
   ],
   "postsubmit": [
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index e2e76d6..577b02a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -39,6 +39,8 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.pm.ApplicationInfo.CATEGORY_SOCIAL;
+import static android.content.pm.ApplicationInfo.CATEGORY_GAME;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.content.res.Configuration.UI_MODE_TYPE_DESK;
@@ -137,6 +139,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.provider.DeviceConfig;
 import android.util.MutableBoolean;
 import android.view.DisplayInfo;
@@ -2655,21 +2658,43 @@
 
     @Test
     public void testSetOrientation() {
+        assertSetOrientation(Build.VERSION_CODES.VANILLA_ICE_CREAM, CATEGORY_SOCIAL, true);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_UNIVERSAL_RESIZABLE_BY_DEFAULT)
+    public void testSetOrientation_restrictedByTargetSdk() {
+        assertSetOrientation(Build.VERSION_CODES.CUR_DEVELOPMENT, CATEGORY_SOCIAL, false);
+        assertSetOrientation(Build.VERSION_CODES.CUR_DEVELOPMENT, CATEGORY_GAME, true);
+
+        // Blanket application, also ignoring Target SDK
+        mWm.mConstants.mIgnoreActivityOrientationRequest = true;
+        assertSetOrientation(Build.VERSION_CODES.VANILLA_ICE_CREAM, CATEGORY_SOCIAL, false);
+    }
+
+    private void assertSetOrientation(int targetSdk, int category, boolean expectRotate) {
         final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        activity.mTargetSdk = targetSdk;
+        activity.info.applicationInfo.category = category;
+
         activity.setVisible(true);
 
         // Assert orientation is unspecified to start.
         assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, activity.getOrientation());
 
+        // Request orientation and see if it can be applied.
         activity.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
-        assertEquals(SCREEN_ORIENTATION_LANDSCAPE, activity.getOrientation());
+        if (expectRotate) {
+            assertEquals("targetSdk=" + targetSdk + " should be able to enter landscape",
+                    SCREEN_ORIENTATION_LANDSCAPE, activity.getOrientation());
+        } else {
+            assertEquals("targetSdk=" + targetSdk + " should not be able to enter landscape",
+                    SCREEN_ORIENTATION_UNSPECIFIED, activity.getOrientation());
+        }
 
         mDisplayContent.removeAppToken(activity.token);
         // Assert orientation is unset to after container is removed.
         assertEquals(SCREEN_ORIENTATION_UNSET, activity.getOrientation());
-
-        // Reset display frozen state
-        mWm.mDisplayFrozen = false;
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index eca4d21..85cb1bc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -369,8 +369,10 @@
                 "startingWin");
         startingWin.setHasSurface(true);
         assertTrue(startingWin.canBeImeTarget());
+        final WindowContainer imeSurfaceParentWindow = mock(WindowContainer.class);
         final SurfaceControl imeSurfaceParent = mock(SurfaceControl.class);
-        doReturn(imeSurfaceParent).when(mDisplayContent).computeImeParent();
+        doReturn(imeSurfaceParent).when(imeSurfaceParentWindow).getSurfaceControl();
+        doReturn(imeSurfaceParentWindow).when(mDisplayContent).computeImeParent();
         spyOn(imeContainer);
 
         mDisplayContent.setImeInputTarget(startingWin);
@@ -406,8 +408,11 @@
                 "startingWin");
         startingWin.setHasSurface(true);
         assertTrue(startingWin.canBeImeTarget());
+        final WindowContainer imeSurfaceParentWindow = mock(WindowContainer.class);
         final SurfaceControl imeSurfaceParent = mock(SurfaceControl.class);
-        doReturn(imeSurfaceParent).when(mDisplayContent).computeImeParent();
+        doReturn(imeSurfaceParent).when(imeSurfaceParentWindow).getSurfaceControl();
+        doReturn(imeSurfaceParentWindow).when(mDisplayContent).computeImeParent();
+
 
         // Main precondition for this test: organize the ImeContainer.
         final IDisplayAreaOrganizer mockImeOrganizer = mock(IDisplayAreaOrganizer.class);
@@ -639,10 +644,11 @@
                 ws.matchesDisplayAreaBounds());
 
         assertTrue("IME shouldn't be attached to app",
-                dc.computeImeParent() != dc.getImeTarget(IME_TARGET_LAYERING).getWindow()
-                        .mActivityRecord.getSurfaceControl());
+                dc.computeImeParent().getSurfaceControl() != dc.getImeTarget(
+                        IME_TARGET_LAYERING).getWindow().mActivityRecord.getSurfaceControl());
         assertEquals("IME should be attached to display",
-                dc.getImeContainer().getParent().getSurfaceControl(), dc.computeImeParent());
+                dc.getImeContainer().getParent().getSurfaceControl(),
+                dc.computeImeParent().getSurfaceControl());
     }
 
     private WindowState[] createNotDrawnWindowsOn(DisplayContent displayContent, int... types) {
@@ -1191,8 +1197,9 @@
         final DisplayContent dc = createNewDisplay();
         dc.setImeLayeringTarget(createWindow(null, TYPE_BASE_APPLICATION, "app"));
         dc.setImeInputTarget(dc.getImeTarget(IME_TARGET_LAYERING).getWindow());
-        assertEquals(dc.getImeTarget(IME_TARGET_LAYERING).getWindow()
-                        .mActivityRecord.getSurfaceControl(), dc.computeImeParent());
+        assertEquals(dc.getImeTarget(
+                        IME_TARGET_LAYERING).getWindow().mActivityRecord.getSurfaceControl(),
+                dc.computeImeParent().getSurfaceControl());
     }
 
     @Test
@@ -1202,7 +1209,8 @@
         dc.getImeTarget(IME_TARGET_LAYERING).getWindow().setWindowingMode(
                 WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW);
         dc.setImeInputTarget(dc.getImeTarget(IME_TARGET_LAYERING).getWindow());
-        assertEquals(dc.getImeContainer().getParentSurfaceControl(), dc.computeImeParent());
+        assertEquals(dc.getImeContainer().getParentSurfaceControl(),
+                dc.computeImeParent().getSurfaceControl());
     }
 
     @SetupWindows(addWindows = W_ACTIVITY)
@@ -1213,7 +1221,7 @@
         mDisplayContent.setImeLayeringTarget(mAppWindow);
         // The surface parent of IME should be the display instead of app window.
         assertEquals(mDisplayContent.getImeContainer().getParentSurfaceControl(),
-                mDisplayContent.computeImeParent());
+                mDisplayContent.computeImeParent().getSurfaceControl());
     }
 
     @Test
@@ -1221,7 +1229,8 @@
         final DisplayContent dc = createNewDisplay();
         dc.setImeLayeringTarget(createWindow(null, TYPE_STATUS_BAR, "statusBar"));
         dc.setImeInputTarget(dc.getImeTarget(IME_TARGET_LAYERING).getWindow());
-        assertEquals(dc.getImeContainer().getParentSurfaceControl(), dc.computeImeParent());
+        assertEquals(dc.getImeContainer().getParentSurfaceControl(),
+                dc.computeImeParent().getSurfaceControl());
     }
 
     @SetupWindows(addWindows = W_ACTIVITY)
@@ -1232,7 +1241,8 @@
         doReturn(true).when(mDisplayContent).shouldImeAttachedToApp();
         mDisplayContent.setImeLayeringTarget(app1);
         mDisplayContent.setImeInputTarget(app1);
-        assertEquals(app1.mActivityRecord.getSurfaceControl(), mDisplayContent.computeImeParent());
+        assertEquals(app1.mActivityRecord.getSurfaceControl(),
+                mDisplayContent.computeImeParent().getSurfaceControl());
         mDisplayContent.setImeLayeringTarget(app2);
         // Expect null means no change IME parent when the IME layering target not yet
         // request IME to be the input target.
@@ -1250,7 +1260,7 @@
         mDisplayContent.setImeInputTarget(app);
         assertFalse(mDisplayContent.shouldImeAttachedToApp());
         assertEquals(mDisplayContent.getImeContainer().getParentSurfaceControl(),
-                mDisplayContent.computeImeParent());
+                mDisplayContent.computeImeParent().getSurfaceControl());
     }
 
     @Test
@@ -1275,7 +1285,8 @@
         assertEquals(dc.getImeTarget(IME_TARGET_LAYERING), dc.getImeInputTarget());
 
         // The ImeParent should be the display.
-        assertEquals(dc.getImeContainer().getParent().getSurfaceControl(), dc.computeImeParent());
+        assertEquals(dc.getImeContainer().getParent().getSurfaceControl(),
+                dc.computeImeParent().getSurfaceControl());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java
index 2f2b473..b7aa730 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java
@@ -45,6 +45,7 @@
 
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.server.wm.DisplayWindowSettings.SettingsProvider.SettingsEntry;
+import com.android.server.wm.TestDisplayWindowSettingsProvider.TestStorage;
 
 import org.junit.After;
 import org.junit.Before;
@@ -516,81 +517,4 @@
         }
         return fullyDeleted;
     }
-
-    /** In-memory storage implementation. */
-    public class TestStorage implements DisplayWindowSettingsProvider.WritableSettingsStorage {
-        private InputStream mReadStream;
-        private ByteArrayOutputStream mWriteStream;
-
-        private boolean mWasSuccessful;
-
-        /**
-         * Returns input stream for reading. By default tries forward the output stream if previous
-         * write was successful.
-         * @see #closeRead()
-         */
-        @Override
-        public InputStream openRead() throws FileNotFoundException {
-            if (mReadStream == null && mWasSuccessful) {
-                mReadStream = new ByteArrayInputStream(mWriteStream.toByteArray());
-            }
-            if (mReadStream == null) {
-                throw new FileNotFoundException();
-            }
-            if (mReadStream.markSupported()) {
-                mReadStream.mark(Integer.MAX_VALUE);
-            }
-            return mReadStream;
-        }
-
-        /** Must be called after each {@link #openRead} to reset the position in the stream. */
-        void closeRead() throws IOException {
-            if (mReadStream == null) {
-                throw new FileNotFoundException();
-            }
-            if (mReadStream.markSupported()) {
-                mReadStream.reset();
-            }
-            mReadStream = null;
-        }
-
-        /**
-         * Creates new or resets existing output stream for write. Automatically closes previous
-         * read stream, since following reads should happen based on this new write.
-         */
-        @Override
-        public OutputStream startWrite() throws IOException {
-            if (mWriteStream == null) {
-                mWriteStream = new ByteArrayOutputStream();
-            } else {
-                mWriteStream.reset();
-            }
-            if (mReadStream != null) {
-                closeRead();
-            }
-            return mWriteStream;
-        }
-
-        @Override
-        public void finishWrite(OutputStream os, boolean success) {
-            mWasSuccessful = success;
-            try {
-                os.close();
-            } catch (IOException e) {
-                // This method can't throw IOException since the super implementation doesn't, so
-                // we just wrap it in a RuntimeException so we end up crashing the test all the
-                // same.
-                throw new RuntimeException(e);
-            }
-        }
-
-        /** Overrides the read stream of the injector. By default it uses current write stream. */
-        private void setReadStream(InputStream is) {
-            mReadStream = is;
-        }
-
-        private boolean wasWriteSuccessful() {
-            return mWasSuccessful;
-        }
-    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index 71cfbfd..08622e6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -133,6 +133,7 @@
     private final ArrayList<DeviceConfig.OnPropertiesChangedListener> mDeviceConfigListeners =
             new ArrayList<>();
 
+    private AppCompatConfiguration mAppCompat;
     private Description mDescription;
     private Context mContext;
     private StaticMockitoSession mMockitoSession;
@@ -379,6 +380,11 @@
                 mock(ActivityManagerService.class, withSettings().stubOnly());
         mAtmService = new TestActivityTaskManagerService(mContext, amService);
         LocalServices.addService(ActivityTaskManagerInternal.class, mAtmService.getAtmInternal());
+
+        // AppCompatConfiguration
+        mAppCompat = new AppCompatConfiguration(
+                ActivityThread.currentActivityThread().getSystemUiContext());
+
         // Create a fake WindowProcessController for the system process.
         final WindowProcessController wpc =
                 addProcess("android", "system", 1485 /* pid */, 1000 /* uid */);
@@ -394,7 +400,7 @@
         mWmService = WindowManagerService.main(
                 mContext, mImService, false, wmPolicy, mAtmService,
                 testDisplayWindowSettingsProvider, StubTransaction::new,
-                MockSurfaceControlBuilder::new);
+                MockSurfaceControlBuilder::new, mAppCompat);
         spyOn(mWmService);
         spyOn(mWmService.mRoot);
         // Invoked during {@link ActivityStack} creation.
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index fd959b9..65a6a69 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -884,7 +884,7 @@
 
         // The ImeParent should be the display.
         assertEquals(mDisplayContent.getImeContainer().getParent().getSurfaceControl(),
-                mDisplayContent.computeImeParent());
+                mDisplayContent.computeImeParent().getSurfaceControl());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayWindowSettingsProvider.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayWindowSettingsProvider.java
index e11df98..877f65c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayWindowSettingsProvider.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayWindowSettingsProvider.java
@@ -22,6 +22,16 @@
 import java.util.HashMap;
 import java.util.Map;
 
+import com.android.server.wm.DisplayWindowSettingsProvider.WritableSettingsStorage;
+import com.android.server.wm.DisplayWindowSettings.SettingsProvider.SettingsEntry;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
 /**
  * In-memory DisplayWindowSettingsProvider used in tests. Ensures no settings are read from or
  * written to device-specific display settings files.
@@ -30,6 +40,10 @@
 
     private final Map<String, SettingsEntry> mOverrideSettingsMap = new HashMap<>();
 
+    public TestDisplayWindowSettingsProvider() {
+        super(new TestStorage(), new TestStorage());
+    }
+
     @Override
     @NonNull
     public SettingsEntry getSettings(@NonNull DisplayInfo info) {
@@ -76,4 +90,81 @@
     private static String getIdentifier(DisplayInfo displayInfo) {
         return displayInfo.uniqueId;
     }
+
+    /** In-memory storage implementation. */
+    public static class TestStorage implements WritableSettingsStorage {
+        private InputStream mReadStream;
+        private ByteArrayOutputStream mWriteStream;
+
+        private boolean mWasSuccessful;
+
+        /**
+         * Returns input stream for reading. By default tries forward the output stream if previous
+         * write was successful.
+         * @see #closeRead()
+         */
+        @Override
+        public InputStream openRead() throws FileNotFoundException {
+            if (mReadStream == null && mWasSuccessful) {
+                mReadStream = new ByteArrayInputStream(mWriteStream.toByteArray());
+            }
+            if (mReadStream == null) {
+                throw new FileNotFoundException();
+            }
+            if (mReadStream.markSupported()) {
+                mReadStream.mark(Integer.MAX_VALUE);
+            }
+            return mReadStream;
+        }
+
+        /** Must be called after each {@link #openRead} to reset the position in the stream. */
+        public void closeRead() throws IOException {
+            if (mReadStream == null) {
+                throw new FileNotFoundException();
+            }
+            if (mReadStream.markSupported()) {
+                mReadStream.reset();
+            }
+            mReadStream = null;
+        }
+
+        /**
+         * Creates new or resets existing output stream for write. Automatically closes previous
+         * read stream, since following reads should happen based on this new write.
+         */
+        @Override
+        public OutputStream startWrite() throws IOException {
+            if (mWriteStream == null) {
+                mWriteStream = new ByteArrayOutputStream();
+            } else {
+                mWriteStream.reset();
+            }
+            if (mReadStream != null) {
+                closeRead();
+            }
+            return mWriteStream;
+        }
+
+        @Override
+        public void finishWrite(OutputStream os, boolean success) {
+            mWasSuccessful = success;
+            try {
+                os.close();
+            } catch (IOException e) {
+                // This method can't throw IOException since the super implementation doesn't, so
+                // we just wrap it in a RuntimeException so we end up crashing the test all the
+                // same.
+                throw new RuntimeException(e);
+            }
+        }
+
+        /** Overrides the read stream of the injector. By default it uses current write stream. */
+        public void setReadStream(InputStream is) {
+            mReadStream = is;
+        }
+
+        public boolean wasWriteSuccessful() {
+            return mWasSuccessful;
+        }
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 5a54af1..2d5e5da 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -80,6 +80,7 @@
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.atMost;
 import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.when;
 
@@ -88,9 +89,12 @@
 import android.graphics.Matrix;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.graphics.Region;
 import android.os.IBinder;
 import android.os.InputConfig;
 import android.os.RemoteException;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
 import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.util.ArraySet;
@@ -116,6 +120,7 @@
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.testutils.StubTransaction;
 import com.android.server.wm.SensitiveContentPackages.PackageInfo;
+import com.android.window.flags.Flags;
 
 import org.junit.After;
 import org.junit.Test;
@@ -965,6 +970,88 @@
         assertTrue(testFlag(handle.inputConfig, InputConfig.NO_INPUT_CHANNEL));
     }
 
+    @DisableFlags(Flags.FLAG_SCROLLING_FROM_LETTERBOX)
+    @Test
+    public void testTouchRegionUsesLetterboxBoundsIfTransformedBoundsAndLetterboxScrolling() {
+        final WindowState win = createWindow(null, TYPE_APPLICATION, "win");
+
+        // Transformed bounds used for size of touchable region if letterbox inner bounds are empty.
+        final Rect transformedBounds = new Rect(0, 0, 300, 500);
+        doReturn(transformedBounds).when(win.mToken).getFixedRotationTransformDisplayBounds();
+
+        // Otherwise, touchable region should match letterbox inner bounds.
+        final Rect letterboxInnerBounds = new Rect(30, 0, 270, 500);
+        doAnswer(invocation -> {
+            Rect rect = invocation.getArgument(0);
+            rect.set(letterboxInnerBounds);
+            return null;
+        }).when(win.mActivityRecord).getLetterboxInnerBounds(any());
+
+        Region outRegion = new Region();
+        win.getSurfaceTouchableRegion(outRegion, win.mAttrs);
+
+        // Because scrollingFromLetterbox flag is disabled and letterboxInnerBounds is not empty,
+        // touchable region should match letterboxInnerBounds always.
+        assertEquals(letterboxInnerBounds, outRegion.getBounds());
+    }
+
+    @DisableFlags(Flags.FLAG_SCROLLING_FROM_LETTERBOX)
+    @Test
+    public void testTouchRegionUsesLetterboxBoundsIfNullTransformedBoundsAndLetterboxScrolling() {
+        final WindowState win = createWindow(null, TYPE_APPLICATION, "win");
+
+        // Fragment bounds used for size of touchable region if letterbox inner bounds are empty
+        // and Transform bounds are null.
+        doReturn(null).when(win.mToken).getFixedRotationTransformDisplayBounds();
+        final Rect fragmentBounds = new Rect(0, 0, 300, 500);
+        final TaskFragment taskFragment = win.mActivityRecord.getTaskFragment();
+        doAnswer(invocation -> {
+            Rect rect = invocation.getArgument(0);
+            rect.set(fragmentBounds);
+            return null;
+        }).when(taskFragment).getDimBounds(any());
+
+        // Otherwise, touchable region should match letterbox inner bounds.
+        final Rect letterboxInnerBounds = new Rect(30, 0, 270, 500);
+        doAnswer(invocation -> {
+            Rect rect = invocation.getArgument(0);
+            rect.set(letterboxInnerBounds);
+            return null;
+        }).when(win.mActivityRecord).getLetterboxInnerBounds(any());
+
+        Region outRegion = new Region();
+        win.getSurfaceTouchableRegion(outRegion, win.mAttrs);
+
+        // Because scrollingFromLetterbox flag is disabled and letterboxInnerBounds is not empty,
+        // touchable region should match letterboxInnerBounds always.
+        assertEquals(letterboxInnerBounds, outRegion.getBounds());
+    }
+
+    @EnableFlags(Flags.FLAG_SCROLLING_FROM_LETTERBOX)
+    @Test
+    public void testTouchRegionUsesTransformedBoundsIfLetterboxScrolling() {
+        final WindowState win = createWindow(null, TYPE_APPLICATION, "win");
+
+        // Transformed bounds used for size of touchable region if letterbox inner bounds are empty.
+        final Rect transformedBounds = new Rect(0, 0, 300, 500);
+        doReturn(transformedBounds).when(win.mToken).getFixedRotationTransformDisplayBounds();
+
+        // Otherwise, touchable region should match letterbox inner bounds.
+        final Rect letterboxInnerBounds = new Rect(30, 0, 270, 500);
+        doAnswer(invocation -> {
+            Rect rect = invocation.getArgument(0);
+            rect.set(letterboxInnerBounds);
+            return null;
+        }).when(win.mActivityRecord).getLetterboxInnerBounds(any());
+
+        Region outRegion = new Region();
+        win.getSurfaceTouchableRegion(outRegion, win.mAttrs);
+
+        // Because scrollingFromLetterbox flag is enabled and transformedBounds are non-null,
+        // touchable region should match transformedBounds.
+        assertEquals(transformedBounds, outRegion.getBounds());
+    }
+
     @Test
     public void testHasActiveVisibleWindow() {
         final int uid = ActivityBuilder.DEFAULT_FAKE_UID;
diff --git a/services/translation/java/com/android/server/translation/TEST_MAPPING b/services/translation/java/com/android/server/translation/TEST_MAPPING
index 4090b4a..0b97358 100644
--- a/services/translation/java/com/android/server/translation/TEST_MAPPING
+++ b/services/translation/java/com/android/server/translation/TEST_MAPPING
@@ -1,12 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "CtsTranslationTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsTranslationTestCases"
     }
   ]
 }
diff --git a/services/usage/java/com/android/server/usage/TEST_MAPPING b/services/usage/java/com/android/server/usage/TEST_MAPPING
index c878054..79b294c 100644
--- a/services/usage/java/com/android/server/usage/TEST_MAPPING
+++ b/services/usage/java/com/android/server/usage/TEST_MAPPING
@@ -7,33 +7,15 @@
       "name": "FrameworksServicesTests_android_server_usage"
     },
     {
-      "name": "CtsBRSTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        }
-      ]
+      "name": "CtsBRSTestCases"
     }
   ],
   "postsubmit": [
     {
-      "name": "CtsUsageStatsTestCases",
-      "options": [
-        {
-          "include-filter": "android.app.usage.cts.UsageStatsTest"
-        }
-      ]
+      "name": "CtsUsageStatsTestCases_cts_usagestatstest_ExcludeMediumAndLarge"
     },
     {
-      "name": "CtsShortcutManagerTestCases",
-      "options": [
-        {
-          "include-filter": "android.content.pm.cts.shortcutmanager.ShortcutManagerUsageTest"
-        }
-      ]
+      "name": "CtsShortcutManagerTestCases_shortcutmanager_shortcutmanagerusagetest"
     }
   ]
 }
diff --git a/services/voiceinteraction/TEST_MAPPING b/services/voiceinteraction/TEST_MAPPING
index e3d2549..3a68b33 100644
--- a/services/voiceinteraction/TEST_MAPPING
+++ b/services/voiceinteraction/TEST_MAPPING
@@ -12,44 +12,19 @@
       ]
     },
     {
-      "name": "CtsAssistTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsAssistTestCases"
     },
     {
-      "name": "CtsVoiceInteractionHostTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsVoiceInteractionHostTestCases"
     },
     {
-      "name": "CtsLocalVoiceInteraction",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsLocalVoiceInteraction"
     },
     {
-      "name": "FrameworksVoiceInteractionTests",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "FrameworksVoiceInteractionTests"
     },
     {
-      "name": "CtsSoundTriggerTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsSoundTriggerTestCases"
     }
   ]
 }
diff --git a/telecomm/TEST_MAPPING b/telecomm/TEST_MAPPING
index 775f1b8..4f6e558 100644
--- a/telecomm/TEST_MAPPING
+++ b/telecomm/TEST_MAPPING
@@ -1,70 +1,30 @@
 {
   "presubmit": [
     {
-      "name": "TeleServiceTests",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "TeleServiceTests"
     },
     {
-      "name": "TelecomUnitTests",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "TelecomUnitTests"
     },
     {
-      "name": "TelephonyProviderTests",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "TelephonyProviderTests"
     },
     {
-      "name": "CtsTelephony2TestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsTelephony2TestCases"
     },
     {
-      "name": "CtsTelephony3TestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsTelephony3TestCases"
     },
     {
-      "name": "CtsSimRestrictedApisTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsSimRestrictedApisTestCases"
     },
     {
-      "name": "CtsTelephonyProviderTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsTelephonyProviderTestCases"
     }
   ],
   "presubmit-large": [
     {
-      "name": "CtsTelecomTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsTelecomTestCases"
     }
   ]
 }
diff --git a/telephony/TEST_MAPPING b/telephony/TEST_MAPPING
index 73e3dcd..4a4bae3 100644
--- a/telephony/TEST_MAPPING
+++ b/telephony/TEST_MAPPING
@@ -1,60 +1,25 @@
 {
   "presubmit": [
     {
-      "name": "TeleServiceTests",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "TeleServiceTests"
     },
     {
-      "name": "TelecomUnitTests",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "TelecomUnitTests"
     },
     {
-      "name": "TelephonyProviderTests",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "TelephonyProviderTests"
     },
     {
-      "name": "CtsTelephony2TestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsTelephony2TestCases"
     },    
     {
-      "name": "CtsTelephony3TestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsTelephony3TestCases"
     },
     {
-      "name": "CtsSimRestrictedApisTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsSimRestrictedApisTestCases"
     },
     {
-      "name": "CtsTelephonyProviderTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsTelephonyProviderTestCases"
     }
   ]
 }
diff --git a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
index f31a87f..4224338 100644
--- a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
+++ b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
@@ -60,6 +60,7 @@
 public final class TelephonyUtils {
     private static final String LOG_TAG = "TelephonyUtils";
 
+    public static final boolean FORCE_VERBOSE_STATE_LOGGING = false; /* stopship if true */
     public static boolean IS_USER = "user".equals(android.os.Build.TYPE);
     public static boolean IS_DEBUGGABLE = SystemProperties.getInt("ro.debuggable", 0) == 1;
 
diff --git a/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl
index 579fda3..a0f01bd 100644
--- a/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl
+++ b/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl
@@ -50,4 +50,11 @@
      *                     Satellite location is based on magnetic north direction.
      */
     void onSatellitePositionChanged(in PointingInfo pointingInfo);
+
+    /**
+     * Called when framework receives a request to send a datagram.
+     *
+     * @param datagramType The type of the requested datagram.
+     */
+    void onSendDatagramRequested(int datagramType);
 }
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 284e2bd..90dae3b 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -1220,6 +1220,12 @@
                                         () -> callback.onReceiveDatagramStateChanged(
                                                 state, receivePendingCount, errorCode)));
                             }
+
+                            @Override
+                            public void onSendDatagramRequested(int datagramType) {
+                                executor.execute(() -> Binder.withCleanCallingIdentity(
+                                        () -> callback.onSendDatagramRequested(datagramType)));
+                            }
                         };
                 sSatelliteTransmissionUpdateCallbackMap.put(callback, internalCallback);
                 telephony.startSatelliteTransmissionUpdates(errorCallback, internalCallback);
diff --git a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
index d8bd662..046ae5f 100644
--- a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
@@ -75,4 +75,13 @@
     void onReceiveDatagramStateChanged(
             @SatelliteManager.SatelliteDatagramTransferState int state, int receivePendingCount,
             @SatelliteManager.SatelliteResult int errorCode);
+
+    /**
+     * Called when framework receives a request to send a datagram.
+     *
+     * @param datagramType The type of the requested datagram.
+     *
+     * @hide
+     */
+    default void onSendDatagramRequested(@SatelliteManager.DatagramType int datagramType) {}
 }
diff --git a/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt b/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt
index afb3593..4712d6b 100644
--- a/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt
+++ b/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt
@@ -4,6 +4,7 @@
 import android.content.Context
 import android.os.Bundle
 import android.security.attestationverification.AttestationVerificationManager.PARAM_CHALLENGE
+import android.security.attestationverification.AttestationVerificationManager.PARAM_MAX_PATCH_LEVEL_DIFF_MONTHS
 import android.security.attestationverification.AttestationVerificationManager.PARAM_PUBLIC_KEY
 import android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE
 import android.security.attestationverification.AttestationVerificationManager.RESULT_SUCCESS
@@ -162,6 +163,41 @@
     }
 
     @Test
+    fun verifyAttestation_returnsSuccessPatchDataWithinMaxPatchDiff() {
+        val verifier = AttestationVerificationPeerDeviceVerifier(
+            context, dumpLogger, trustAnchors, false, LocalDate.of(2023, 3, 1),
+            LocalDate.of(2023, 2, 1)
+        )
+        val challengeRequirements = Bundle()
+        challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray())
+        challengeRequirements.putInt(PARAM_MAX_PATCH_LEVEL_DIFF_MONTHS, 24)
+
+        val result = verifier.verifyAttestation(
+            TYPE_CHALLENGE, challengeRequirements,
+            TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
+        )
+        assertThat(result).isEqualTo(RESULT_SUCCESS)
+    }
+
+    @Test
+    fun verifyAttestation_returnsFailurePatchDataNotWithinMaxPatchDiff() {
+        val verifier = AttestationVerificationPeerDeviceVerifier(
+            context, dumpLogger, trustAnchors, false, LocalDate.of(2024, 10, 1),
+            LocalDate.of(2024, 9, 1)
+        )
+        val challengeRequirements = Bundle()
+        challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray())
+        challengeRequirements.putInt(PARAM_MAX_PATCH_LEVEL_DIFF_MONTHS, 24)
+
+        val result = verifier.verifyAttestation(
+            TYPE_CHALLENGE, challengeRequirements,
+            // The patch date of this file is early 2022
+            TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
+        )
+        assertThat(result).isEqualTo(RESULT_FAILURE)
+    }
+
+    @Test
     fun verifyAttestation_returnsFailureTrustedAnchorEmpty() {
         val verifier = AttestationVerificationPeerDeviceVerifier(
             context, dumpLogger, HashSet(), false, LocalDate.of(2022, 1, 1),
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt
index 095c819..3753b23 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt
@@ -23,7 +23,6 @@
 import android.tools.flicker.legacy.LegacyFlickerTest
 import android.tools.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.flicker.subject.region.RegionSubject
-import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
 import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
@@ -43,7 +42,6 @@
  *
  * To run this test: `atest FlickerTestsActivityEmbedding:OpenTrampolineActivityTest`
  */
-@FlakyTest(bugId = 341209752)
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
diff --git a/tests/FlickerTests/test-apps/flickerapp/Android.bp b/tests/FlickerTests/test-apps/flickerapp/Android.bp
index e3b23b9..a186679 100644
--- a/tests/FlickerTests/test-apps/flickerapp/Android.bp
+++ b/tests/FlickerTests/test-apps/flickerapp/Android.bp
@@ -19,6 +19,7 @@
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
     default_applicable_licenses: ["frameworks_base_license"],
+    default_team: "trendy_team_windowing_tools",
 }
 
 android_test {
diff --git a/tests/Input/AndroidManifest.xml b/tests/Input/AndroidManifest.xml
index a05d08c..914adc4 100644
--- a/tests/Input/AndroidManifest.xml
+++ b/tests/Input/AndroidManifest.xml
@@ -32,6 +32,14 @@
              android:process=":externalProcess">
         </activity>
 
+        <activity android:name="com.android.test.input.CaptureEventActivity"
+            android:label="Capture events"
+            android:configChanges="touchscreen|uiMode|orientation|screenSize|screenLayout|keyboardHidden|uiMode|navigation|keyboard|density|fontScale|layoutDirection|locale|mcc|mnc|smallestScreenSize"
+            android:enableOnBackInvokedCallback="false"
+            android:turnScreenOn="true"
+            android:exported="true">
+        </activity>
+
     </application>
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
                      android:targetPackage="com.android.test.input"
diff --git a/tests/Input/res/raw/google_pixel_tablet_touchscreen.evemu b/tests/Input/res/raw/google_pixel_tablet_touchscreen.evemu
new file mode 100644
index 0000000..1a9112b
--- /dev/null
+++ b/tests/Input/res/raw/google_pixel_tablet_touchscreen.evemu
@@ -0,0 +1,150 @@
+# EVEMU 1.2
+# One finger swipe gesture on the Google Pixel Tablet touchscreen
+N: NVTCapacitiveTouchScreen
+I: 001c 0603 7806 0100
+P: 02 00 00 00 00 00 00 00
+B: 00 0b 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 80 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 04 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 02 00 00 00 00 00 00 00 00
+B: 03 00 00 00 00 00 80 f3 46
+B: 04 00 00 00 00 00 00 00 00
+B: 05 00 00 00 00 00 00 00 00
+B: 11 00 00 00 00 00 00 00 00
+B: 12 00 00 00 00 00 00 00 00
+A: 2f 0 9 0 0 0
+A: 30 0 2559 0 0 11
+A: 31 0 1599 0 0 11
+A: 34 -4096 4096 0 0 0
+A: 35 0 1599 0 0 11
+A: 36 0 2559 0 0 11
+A: 37 0 2 0 0 0
+A: 39 0 65535 0 0 0
+A: 3a 0 256 0 0 0
+A: 3e 0 8 0 0 0
+E: 0.000001 0001 014a 0001
+E: 0.000001 0003 0039 0003
+E: 0.000001 0003 0035 0810
+E: 0.000001 0003 0036 1650
+E: 0.000001 0003 0030 0082
+E: 0.000001 0003 0031 0077
+E: 0.000001 0003 003a 0215
+E: 0.000001 0003 0034 3218
+E: 0.000001 0000 0000 0000
+E: 0.008818 0003 0035 0825
+E: 0.008818 0003 0036 1645
+E: 0.008818 0003 0034 3217
+E: 0.008818 0000 0000 0000
+E: 0.016306 0003 0035 0841
+E: 0.016306 0003 0036 1639
+E: 0.016306 0003 0034 3102
+E: 0.016306 0000 0000 0000
+E: 0.025653 0003 0035 0862
+E: 0.025653 0003 0036 1630
+E: 0.025653 0003 0034 3092
+E: 0.025653 0000 0000 0000
+E: 0.032936 0003 0035 0883
+E: 0.032936 0003 0036 1619
+E: 0.032936 0003 0034 3030
+E: 0.032936 0000 0000 0000
+E: 0.042072 0003 0035 0905
+E: 0.042072 0003 0036 1604
+E: 0.042072 0003 0034 2848
+E: 0.042072 0000 0000 0000
+E: 0.049569 0003 0035 0924
+E: 0.049569 0003 0036 1591
+E: 0.049569 0003 0034 2830
+E: 0.049569 0000 0000 0000
+E: 0.058706 0003 0035 0942
+E: 0.058706 0003 0036 1573
+E: 0.058706 0000 0000 0000
+E: 0.066207 0003 0035 0954
+E: 0.066207 0003 0036 1557
+E: 0.066207 0003 0034 2790
+E: 0.066207 0000 0000 0000
+E: 0.075337 0003 0035 0966
+E: 0.075337 0003 0036 1535
+E: 0.075337 0000 0000 0000
+E: 0.082841 0003 0035 0973
+E: 0.082841 0003 0036 1511
+E: 0.082841 0003 0034 2788
+E: 0.082841 0000 0000 0000
+E: 0.091972 0003 0035 0971
+E: 0.091972 0003 0036 1480
+E: 0.091972 0003 0034 2770
+E: 0.091972 0000 0000 0000
+E: 0.099474 0003 0035 0961
+E: 0.099474 0003 0036 1445
+E: 0.099474 0003 0034 2644
+E: 0.099474 0000 0000 0000
+E: 0.108631 0003 0035 0937
+E: 0.108631 0003 0036 1400
+E: 0.108631 0003 0030 0083
+E: 0.108631 0003 0034 2461
+E: 0.108631 0000 0000 0000
+E: 0.116109 0003 0035 0909
+E: 0.116109 0003 0036 1361
+E: 0.116109 0003 0034 2278
+E: 0.116109 0000 0000 0000
+E: 0.125263 0003 0035 0865
+E: 0.125263 0003 0036 1311
+E: 0.125263 0003 0034 2096
+E: 0.125263 0000 0000 0000
+E: 0.132741 0003 0035 0820
+E: 0.132741 0003 0036 1261
+E: 0.132741 0003 0034 2083
+E: 0.132741 0000 0000 0000
+E: 0.141876 0003 0035 0755
+E: 0.141876 0003 0036 1193
+E: 0.141876 0003 003a 0216
+E: 0.141876 0003 0034 2266
+E: 0.141876 0000 0000 0000
+E: 0.149376 0003 0035 0691
+E: 0.149376 0003 0036 1124
+E: 0.149376 0003 0034 2448
+E: 0.149376 0000 0000 0000
+E: 0.158510 0003 0035 0609
+E: 0.158510 0003 0036 1033
+E: 0.158510 0003 0034 2631
+E: 0.158510 0000 0000 0000
+E: 0.166011 0003 0035 0543
+E: 0.166011 0003 0036 0957
+E: 0.166011 0003 0034 2813
+E: 0.166011 0000 0000 0000
+E: 0.175182 0003 0035 0471
+E: 0.175182 0003 0036 0864
+E: 0.175182 0003 0031 0076
+E: 0.175182 0003 0034 2996
+E: 0.175182 0000 0000 0000
+E: 0.182683 0003 0035 0417
+E: 0.182683 0003 0036 0792
+E: 0.182683 0003 003a 0214
+E: 0.182683 0003 0034 3178
+E: 0.182683 0000 0000 0000
+E: 0.191777 0003 0035 0361
+E: 0.191777 0003 0036 0719
+E: 0.191777 0003 0031 0075
+E: 0.191777 0003 003a 0213
+E: 0.191777 0003 0034 2996
+E: 0.191777 0000 0000 0000
+E: 0.199431 0003 0035 0271
+E: 0.199431 0003 0036 0603
+E: 0.199431 0003 0030 0060
+E: 0.199431 0003 0031 0029
+E: 0.199431 0003 003a 0060
+E: 0.199431 0003 0034 2813
+E: 0.199431 0000 0000 0000
+E: 0.207943 0003 003a 0000
+E: 0.207943 0003 0039 -001
+E: 0.207943 0001 014a 0000
+E: 0.207943 0000 0000 0000
diff --git a/tests/Input/res/raw/google_pixel_tablet_touchscreen_events.json b/tests/Input/res/raw/google_pixel_tablet_touchscreen_events.json
new file mode 100644
index 0000000..df4f9fb
--- /dev/null
+++ b/tests/Input/res/raw/google_pixel_tablet_touchscreen_events.json
@@ -0,0 +1,34 @@
+[
+  {
+    "name": "One finger swipe",
+    "source": "TOUCHSCREEN",
+    "events": [
+      {"action":"DOWN","axes":{"AXIS_X":810,"AXIS_Y":1650,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03106682375073433,"AXIS_TOUCH_MAJOR":82,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":82,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":1.234087586402893},"buttonState":[]},
+      {"action":"MOVE","axes":{"AXIS_X":825,"AXIS_Y":1645,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03106682375073433,"AXIS_TOUCH_MAJOR":82,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":82,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":1.2337040901184082},"buttonState":[]},
+      {"action":"MOVE","axes":{"AXIS_X":841,"AXIS_Y":1639,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03106682375073433,"AXIS_TOUCH_MAJOR":82,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":82,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":1.1896021366119385},"buttonState":[]},
+      {"action":"MOVE","axes":{"AXIS_X":862,"AXIS_Y":1630,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03106682375073433,"AXIS_TOUCH_MAJOR":82,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":82,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":1.1857671737670898},"buttonState":[]},
+      {"action":"MOVE","axes":{"AXIS_X":883,"AXIS_Y":1619,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03106682375073433,"AXIS_TOUCH_MAJOR":82,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":82,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":1.1619905233383179},"buttonState":[]},
+      {"action":"MOVE","axes":{"AXIS_X":905,"AXIS_Y":1604,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03106682375073433,"AXIS_TOUCH_MAJOR":82,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":82,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":1.0921943187713623},"buttonState":[]},
+      {"action":"MOVE","axes":{"AXIS_X":924,"AXIS_Y":1591,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03106682375073433,"AXIS_TOUCH_MAJOR":82,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":82,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":1.0852913856506348},"buttonState":[]},
+      {"action":"MOVE","axes":{"AXIS_X":942,"AXIS_Y":1573,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03106682375073433,"AXIS_TOUCH_MAJOR":82,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":82,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":1.0852913856506348},"buttonState":[]},
+      {"action":"MOVE","axes":{"AXIS_X":954,"AXIS_Y":1557,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03106682375073433,"AXIS_TOUCH_MAJOR":82,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":82,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":1.0699516534805298},"buttonState":[]},
+      {"action":"MOVE","axes":{"AXIS_X":966,"AXIS_Y":1535,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03106682375073433,"AXIS_TOUCH_MAJOR":82,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":82,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":1.0699516534805298},"buttonState":[]},
+      {"action":"MOVE","axes":{"AXIS_X":973,"AXIS_Y":1511,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03106682375073433,"AXIS_TOUCH_MAJOR":82,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":82,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":1.06918466091156},"buttonState":[]},
+      {"action":"MOVE","axes":{"AXIS_X":971,"AXIS_Y":1480,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03106682375073433,"AXIS_TOUCH_MAJOR":82,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":82,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":1.0622817277908325},"buttonState":[]},
+      {"action":"MOVE","axes":{"AXIS_X":961,"AXIS_Y":1445,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03106682375073433,"AXIS_TOUCH_MAJOR":82,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":82,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":1.0139613151550293},"buttonState":[]},
+      {"action":"MOVE","axes":{"AXIS_X":937,"AXIS_Y":1400,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03126221150159836,"AXIS_TOUCH_MAJOR":83,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":83,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":0.9437817335128784},"buttonState":[]},
+      {"action":"MOVE","axes":{"AXIS_X":909,"AXIS_Y":1361,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03126221150159836,"AXIS_TOUCH_MAJOR":83,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":83,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":0.8736020922660828},"buttonState":[]},
+      {"action":"MOVE","axes":{"AXIS_X":865,"AXIS_Y":1311,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03126221150159836,"AXIS_TOUCH_MAJOR":83,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":83,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":0.803805947303772},"buttonState":[]},
+      {"action":"MOVE","axes":{"AXIS_X":820,"AXIS_Y":1261,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03126221150159836,"AXIS_TOUCH_MAJOR":83,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":83,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":0.7988204956054688},"buttonState":[]},
+      {"action":"MOVE","axes":{"AXIS_X":755,"AXIS_Y":1193,"AXIS_PRESSURE":0.84375,"AXIS_SIZE":0.03126221150159836,"AXIS_TOUCH_MAJOR":83,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":83,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":0.8690001368522644},"buttonState":[]},
+      {"action":"MOVE","axes":{"AXIS_X":691,"AXIS_Y":1124,"AXIS_PRESSURE":0.84375,"AXIS_SIZE":0.03126221150159836,"AXIS_TOUCH_MAJOR":83,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":83,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":0.9387962818145752},"buttonState":[]},
+      {"action":"MOVE","axes":{"AXIS_X":609,"AXIS_Y":1033,"AXIS_PRESSURE":0.84375,"AXIS_SIZE":0.03126221150159836,"AXIS_TOUCH_MAJOR":83,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":83,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":1.008975863456726},"buttonState":[]},
+      {"action":"MOVE","axes":{"AXIS_X":543,"AXIS_Y":957,"AXIS_PRESSURE":0.84375,"AXIS_SIZE":0.03126221150159836,"AXIS_TOUCH_MAJOR":83,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":83,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":1.0787720680236816},"buttonState":[]},
+      {"action":"MOVE","axes":{"AXIS_X":471,"AXIS_Y":864,"AXIS_PRESSURE":0.84375,"AXIS_SIZE":0.03106682375073433,"AXIS_TOUCH_MAJOR":83,"AXIS_TOUCH_MINOR":76,"AXIS_TOOL_MAJOR":83,"AXIS_TOOL_MINOR":76,"AXIS_ORIENTATION":1.1489516496658325},"buttonState":[]},
+      {"action":"MOVE","axes":{"AXIS_X":417,"AXIS_Y":792,"AXIS_PRESSURE":0.8359375,"AXIS_SIZE":0.03106682375073433,"AXIS_TOUCH_MAJOR":83,"AXIS_TOUCH_MINOR":76,"AXIS_TOOL_MAJOR":83,"AXIS_TOOL_MINOR":76,"AXIS_ORIENTATION":1.2187477350234985},"buttonState":[]},
+      {"action":"MOVE","axes":{"AXIS_X":361,"AXIS_Y":719,"AXIS_PRESSURE":0.83203125,"AXIS_SIZE":0.03087143413722515,"AXIS_TOUCH_MAJOR":83,"AXIS_TOUCH_MINOR":75,"AXIS_TOOL_MAJOR":83,"AXIS_TOOL_MINOR":75,"AXIS_ORIENTATION":1.1489516496658325},"buttonState":[]},
+      {"action":"MOVE","axes":{"AXIS_X":271,"AXIS_Y":603,"AXIS_PRESSURE":0.234375,"AXIS_SIZE":0.017389604821801186,"AXIS_TOUCH_MAJOR":60,"AXIS_TOUCH_MINOR":29,"AXIS_TOOL_MAJOR":60,"AXIS_TOOL_MINOR":29,"AXIS_ORIENTATION":1.0787720680236816},"buttonState":[]},
+      {"action":"UP","axes":{"AXIS_X":271,"AXIS_Y":603,"AXIS_PRESSURE":0.234375,"AXIS_SIZE":0.017389604821801186,"AXIS_TOUCH_MAJOR":60,"AXIS_TOUCH_MINOR":29,"AXIS_TOOL_MAJOR":60,"AXIS_TOOL_MINOR":29,"AXIS_ORIENTATION":1.0787720680236816},"buttonState":[]}
+    ]
+  }
+]
diff --git a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
index 99e04cc..0719686 100644
--- a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
+++ b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
@@ -85,8 +85,8 @@
         when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
 
         mTouchpadDebugView = new TouchpadDebugView(mTestableContext, TOUCHPAD_DEVICE_ID,
-                new TouchpadHardwareProperties.Builder(500f, 500f, 500f,
-                        500f, 0f, 0f, -5f, 5f, (short) 10, true,
+                new TouchpadHardwareProperties.Builder(0f, 0f, 500f,
+                        500f, 45f, 47f, -4f, 5f, (short) 10, true,
                         true).build());
 
         mTouchpadDebugView.measure(
diff --git a/tests/Input/src/com/android/test/input/AnrTest.kt b/tests/Input/src/com/android/test/input/AnrTest.kt
index d32cedb..cd6ab30 100644
--- a/tests/Input/src/com/android/test/input/AnrTest.kt
+++ b/tests/Input/src/com/android/test/input/AnrTest.kt
@@ -166,12 +166,12 @@
         val displayManager =
             instrumentation.context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
         val display = displayManager.getDisplay(obj.getDisplayId())
-        val touchScreen = UinputTouchScreen(instrumentation, display)
-
         val rect: Rect = obj.visibleBounds
-        val pointer = touchScreen.touchDown(rect.centerX(), rect.centerY())
-        pointer.lift()
-        touchScreen.close()
+        UinputTouchScreen(instrumentation, display).use { touchScreen ->
+            touchScreen
+                .touchDown(rect.centerX(), rect.centerY())
+                .lift()
+        }
     }
 
     private fun triggerAnr() {
diff --git a/tests/Input/src/com/android/test/input/CaptureEventActivity.kt b/tests/Input/src/com/android/test/input/CaptureEventActivity.kt
new file mode 100644
index 0000000..d54e3470
--- /dev/null
+++ b/tests/Input/src/com/android/test/input/CaptureEventActivity.kt
@@ -0,0 +1,85 @@
+/*
+ * 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.test.input
+
+import android.app.Activity
+import android.os.Bundle
+import android.view.InputEvent
+import android.view.KeyEvent
+import android.view.MotionEvent
+import java.util.concurrent.LinkedBlockingQueue
+import java.util.concurrent.TimeUnit
+import org.junit.Assert.assertNull
+
+class CaptureEventActivity : Activity() {
+    private val events = LinkedBlockingQueue<InputEvent>()
+    var shouldHandleKeyEvents = true
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        // Set the fixed orientation if requested
+        if (intent.hasExtra(EXTRA_FIXED_ORIENTATION)) {
+            val orientation = intent.getIntExtra(EXTRA_FIXED_ORIENTATION, 0)
+            setRequestedOrientation(orientation)
+        }
+
+        // Set the flag if requested
+        if (intent.hasExtra(EXTRA_WINDOW_FLAGS)) {
+            val flags = intent.getIntExtra(EXTRA_WINDOW_FLAGS, 0)
+            window.addFlags(flags)
+        }
+    }
+
+    override fun dispatchGenericMotionEvent(ev: MotionEvent?): Boolean {
+        events.add(MotionEvent.obtain(ev))
+        return true
+    }
+
+    override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
+        events.add(MotionEvent.obtain(ev))
+        return true
+    }
+
+    override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
+        events.add(KeyEvent(event))
+        return shouldHandleKeyEvents
+    }
+
+    override fun dispatchTrackballEvent(ev: MotionEvent?): Boolean {
+        events.add(MotionEvent.obtain(ev))
+        return true
+    }
+
+    fun getInputEvent(): InputEvent? {
+        return events.poll(5, TimeUnit.SECONDS)
+    }
+
+    fun hasReceivedEvents(): Boolean {
+        return !events.isEmpty()
+    }
+
+    fun assertNoEvents() {
+        val event = events.poll(100, TimeUnit.MILLISECONDS)
+        assertNull("Expected no events, but received $event", event)
+    }
+
+    companion object {
+        const val EXTRA_FIXED_ORIENTATION = "fixed_orientation"
+        const val EXTRA_WINDOW_FLAGS = "window_flags"
+    }
+}
diff --git a/tests/Input/src/com/android/test/input/UinputRecordingIntegrationTests.kt b/tests/Input/src/com/android/test/input/UinputRecordingIntegrationTests.kt
new file mode 100644
index 0000000..aa73c39
--- /dev/null
+++ b/tests/Input/src/com/android/test/input/UinputRecordingIntegrationTests.kt
@@ -0,0 +1,197 @@
+/*
+ * 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.test.input
+
+import android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY
+import android.app.Instrumentation
+import android.cts.input.EventVerifier
+import android.graphics.PointF
+import android.hardware.input.InputManager
+import android.os.ParcelFileDescriptor
+import android.util.Log
+import android.util.Size
+import android.view.InputEvent
+import android.view.MotionEvent
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.cts.input.BatchedEventSplitter
+import com.android.cts.input.InputJsonParser
+import com.android.cts.input.VirtualDisplayActivityScenario
+import com.android.cts.input.inputeventmatchers.isResampled
+import com.android.cts.input.inputeventmatchers.withButtonState
+import com.android.cts.input.inputeventmatchers.withHistorySize
+import com.android.cts.input.inputeventmatchers.withMotionAction
+import com.android.cts.input.inputeventmatchers.withPressure
+import com.android.cts.input.inputeventmatchers.withRawCoords
+import com.android.cts.input.inputeventmatchers.withSource
+import java.io.InputStream
+import junit.framework.Assert.fail
+import org.hamcrest.Matchers.allOf
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TestName
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Integration tests for the input pipeline that replays recording taken from physical input devices
+ * at the evdev interface level, and makes assertions on the events that are received by a test app.
+ *
+ * These tests associate the playback input device with a virtual display to make these tests
+ * agnostic to the device form factor.
+ *
+ * New recordings can be taken using the `evemu-record` shell command.
+ */
+@RunWith(Parameterized::class)
+class UinputRecordingIntegrationTests {
+
+    companion object {
+        /**
+         * Add new test cases by adding a new [TestData] to the following list.
+         */
+        @JvmStatic
+        @Parameterized.Parameters(name = "{0}")
+        fun data(): Iterable<Any> =
+            listOf(
+                TestData(
+                    "GooglePixelTabletTouchscreen", R.raw.google_pixel_tablet_touchscreen,
+                    R.raw.google_pixel_tablet_touchscreen_events, Size(1600, 2560),
+                    vendorId = 0x0603, productId = 0x7806
+                ),
+            )
+
+        /**
+         * Use the debug mode to see the JSON-encoded received events in logcat.
+         */
+        const val DEBUG_RECEIVED_EVENTS = false
+
+        const val INPUT_DEVICE_SOURCE_ALL = -1
+        val TAG = UinputRecordingIntegrationTests::class.java.simpleName
+    }
+
+    class TestData(
+        val name: String,
+        val uinputRecordingResource: Int,
+        val expectedEventsResource: Int,
+        val displaySize: Size,
+        val vendorId: Int,
+        val productId: Int,
+    ) {
+        override fun toString(): String = name
+    }
+
+    private lateinit var instrumentation: Instrumentation
+    private lateinit var parser: InputJsonParser
+
+    @get:Rule
+    val testName = TestName()
+
+    @Parameterized.Parameter(0)
+    lateinit var testData: TestData
+
+    @Before
+    fun setUp() {
+        instrumentation = InstrumentationRegistry.getInstrumentation()
+        parser = InputJsonParser(instrumentation.context)
+    }
+
+    @Test
+    fun testEvemuRecording() {
+        VirtualDisplayActivityScenario.AutoClose<CaptureEventActivity>(
+            testName,
+            size = testData.displaySize
+        ).use { scenario ->
+            scenario.activity.window.decorView.requestUnbufferedDispatch(INPUT_DEVICE_SOURCE_ALL)
+
+            try {
+                instrumentation.uiAutomation.adoptShellPermissionIdentity(
+                    ASSOCIATE_INPUT_DEVICE_TO_DISPLAY,
+                )
+
+                val inputPort = "uinput:1:${testData.vendorId}:${testData.productId}"
+                val inputManager =
+                    instrumentation.context.getSystemService(InputManager::class.java)!!
+                try {
+                    inputManager.addUniqueIdAssociationByPort(
+                        inputPort,
+                        scenario.virtualDisplay.display.uniqueId!!,
+                    )
+
+                    injectUinputEvents()
+
+                    if (DEBUG_RECEIVED_EVENTS) {
+                        printReceivedEventsToLogcat(scenario.activity)
+                        fail("Test cannot pass in debug mode!")
+                    }
+
+                    val verifier =
+                        EventVerifier(BatchedEventSplitter { scenario.activity.getInputEvent() })
+                    verifyEvents(verifier)
+                    scenario.activity.assertNoEvents()
+                } finally {
+                    inputManager.removeUniqueIdAssociationByPort(inputPort)
+                }
+            } finally {
+                instrumentation.uiAutomation.dropShellPermissionIdentity()
+            }
+        }
+    }
+
+    private fun printReceivedEventsToLogcat(activity: CaptureEventActivity) {
+        val getNextEvent = BatchedEventSplitter { activity.getInputEvent() }
+        var receivedEvent: InputEvent? = getNextEvent()
+        while (receivedEvent != null) {
+            Log.d(TAG,
+                parser.encodeEvent(receivedEvent)?.toString()
+                    ?: "(Failed to encode received event)"
+            )
+            receivedEvent = getNextEvent()
+        }
+    }
+
+    private fun injectUinputEvents() {
+        val fds = instrumentation.uiAutomation!!.executeShellCommandRw("uinput -")
+
+        ParcelFileDescriptor.AutoCloseOutputStream(fds[1]).use { stdIn ->
+            val inputStream: InputStream = instrumentation.context.resources.openRawResource(
+                testData.uinputRecordingResource,
+            )
+            stdIn.write(inputStream.readBytes())
+        }
+    }
+
+    private fun verifyEvents(verifier: EventVerifier) {
+        val uinputTestData = parser.getUinputTestData(testData.expectedEventsResource)
+        for (test in uinputTestData) {
+            for ((index, expectedEvent) in test.events.withIndex()) {
+                if (expectedEvent is MotionEvent) {
+                    verifier.assertReceivedMotion(
+                        allOf(
+                            withMotionAction(expectedEvent.action),
+                            withSource(expectedEvent.source),
+                            withButtonState(expectedEvent.buttonState),
+                            withRawCoords(PointF(expectedEvent.rawX, expectedEvent.rawY)),
+                            withPressure(expectedEvent.pressure),
+                            isResampled(false),
+                            withHistorySize(0),
+                        ),
+                        "${test.name}: Expected event at index $index",
+                    )
+                }
+            }
+        }
+    }
+}
diff --git a/tests/TrustTests/TEST_MAPPING b/tests/TrustTests/TEST_MAPPING
index 23923ee..b0dd551 100644
--- a/tests/TrustTests/TEST_MAPPING
+++ b/tests/TrustTests/TEST_MAPPING
@@ -1,28 +1,12 @@
 {
   "presubmit": [
     {
-      "name": "TrustTests",
-      "options": [
-        {
-          "include-filter": "android.trust.test"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "TrustTests_trust_test"
     }
   ],
   "trust-tablet": [
     {
-      "name": "TrustTests",
-      "options": [
-        {
-          "include-filter": "android.trust.test"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "TrustTests_trust_test"
     }
   ]
 }
\ No newline at end of file
diff --git a/tests/UsbManagerTests/Android.bp b/tests/UsbManagerTests/Android.bp
index 2909e66..331a21a 100644
--- a/tests/UsbManagerTests/Android.bp
+++ b/tests/UsbManagerTests/Android.bp
@@ -44,7 +44,7 @@
         "libstaticjvmtiagent",
     ],
     libs: [
-        "android.test.mock",
+        "android.test.mock.stubs.system",
     ],
     certificate: "platform",
     platform_apis: true,
diff --git a/tests/utils/testutils/TEST_MAPPING b/tests/utils/testutils/TEST_MAPPING
index 52fd5a8..71e9ad3 100644
--- a/tests/utils/testutils/TEST_MAPPING
+++ b/tests/utils/testutils/TEST_MAPPING
@@ -1,18 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "frameworks-base-testutils-tests",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.LargeTest"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        }
-      ]
+      "name": "frameworks-base-testutils-tests"
     }
   ],
   "postsubmit": [
diff --git a/tools/hoststubgen/hoststubgen/Android.bp b/tools/hoststubgen/hoststubgen/Android.bp
index ea77b8d..4920f7b4 100644
--- a/tools/hoststubgen/hoststubgen/Android.bp
+++ b/tools/hoststubgen/hoststubgen/Android.bp
@@ -5,6 +5,10 @@
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
     default_applicable_licenses: ["frameworks_base_license"],
+
+    // OWNER: g/ravenwood
+    // Bug component: 25698
+    default_team: "trendy_team_framework_backstage_power",
 }
 
 // Visibility only for ravenwood prototype uses.
diff --git a/tools/systemfeatures/Android.bp b/tools/systemfeatures/Android.bp
index aca25eb..a9e6328 100644
--- a/tools/systemfeatures/Android.bp
+++ b/tools/systemfeatures/Android.bp
@@ -5,6 +5,7 @@
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
     default_applicable_licenses: ["frameworks_base_license"],
+    default_team: "trendy_team_system_performance",
 }
 
 java_library_host {
@@ -25,8 +26,6 @@
     static_libs: ["systemfeatures-gen-lib"],
 }
 
-// TODO(b/203143243): Add golden diff test for generated sources.
-// Functional runtime behavior is covered in systemfeatures-gen-tests.
 genrule {
     name: "systemfeatures-gen-tests-srcs",
     cmd: "$(location systemfeatures-gen-tool) com.android.systemfeatures.RwNoFeatures --readonly=false > $(location RwNoFeatures.java) && " +
@@ -42,11 +41,12 @@
     tools: ["systemfeatures-gen-tool"],
 }
 
+// Functional runtime behavior testing.
 java_test_host {
     name: "systemfeatures-gen-tests",
     test_suites: ["general-tests"],
     srcs: [
-        "tests/**/*.java",
+        "tests/src/**/*.java",
         ":systemfeatures-gen-tests-srcs",
     ],
     test_options: {
@@ -61,3 +61,33 @@
         "truth",
     ],
 }
+
+// Rename the goldens as they may be copied into the source tree, and we don't
+// need or want the usual `.java` linting (e.g., copyright checks).
+genrule {
+    name: "systemfeatures-gen-tests-golden-srcs",
+    cmd: "for f in $(in); do cp $$f $(genDir)/tests/gen/$$(basename $$f).gen; done",
+    srcs: [":systemfeatures-gen-tests-srcs"],
+    out: [
+        "tests/gen/RwNoFeatures.java.gen",
+        "tests/gen/RoNoFeatures.java.gen",
+        "tests/gen/RwFeatures.java.gen",
+        "tests/gen/RoFeatures.java.gen",
+    ],
+}
+
+// Golden output testing. Golden sources can be updated via:
+//   $ANDROID_BUILD_TOP/frameworks/base/tools/systemfeatures/tests/golden_test.sh --update
+sh_test_host {
+    name: "systemfeatures-gen-golden-tests",
+    src: "tests/golden_test.sh",
+    filename: "systemfeatures-gen-golden-tests.sh",
+    test_config: "tests/systemfeatures-gen-golden-tests.xml",
+    data: [
+        "tests/golden/**/*.java*",
+        ":systemfeatures-gen-tests-golden-srcs",
+    ],
+    test_options: {
+        unit_test: true,
+    },
+}
diff --git a/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt b/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt
index e537ffc..5df453d 100644
--- a/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt
+++ b/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt
@@ -142,6 +142,10 @@
 
         // TODO(b/203143243): Add validation of build vs runtime values to ensure consistency.
         JavaFile.builder(outputClassName.packageName(), classBuilder.build())
+            .indent("    ")
+            .skipJavaLangImports(true)
+            .addFileComment("This file is auto-generated. DO NOT MODIFY.\n")
+            .addFileComment("Args: ${args.joinToString(" \\\n           ")}")
             .build()
             .writeTo(System.out)
     }
@@ -178,6 +182,7 @@
             val methodBuilder =
                 MethodSpec.methodBuilder(methodName)
                     .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
+                    .addJavadoc("Check for ${feature.name}.\n\n@hide")
                     .returns(Boolean::class.java)
                     .addParameter(CONTEXT_CLASS, "context")
 
@@ -228,6 +233,7 @@
             MethodSpec.methodBuilder("maybeHasFeature")
                 .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                 .addAnnotation(ClassName.get("android.annotation", "Nullable"))
+                .addJavadoc("@hide")
                 .returns(Boolean::class.javaObjectType) // Use object type for nullability
                 .addParameter(String::class.java, "featureName")
                 .addParameter(Int::class.java, "version")
diff --git a/tools/systemfeatures/tests/golden/RoFeatures.java.gen b/tools/systemfeatures/tests/golden/RoFeatures.java.gen
new file mode 100644
index 0000000..724639b
--- /dev/null
+++ b/tools/systemfeatures/tests/golden/RoFeatures.java.gen
@@ -0,0 +1,88 @@
+// This file is auto-generated. DO NOT MODIFY.
+// Args: com.android.systemfeatures.RoFeatures \
+//            --readonly=true \
+//            --feature=WATCH:1 \
+//            --feature=WIFI:0 \
+//            --feature=VULKAN:-1 \
+//            --feature=AUTO: \
+//            --feature-apis=WATCH,PC
+package com.android.systemfeatures;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import com.android.aconfig.annotations.AssumeFalseForR8;
+import com.android.aconfig.annotations.AssumeTrueForR8;
+
+/**
+ * @hide
+ */
+public final class RoFeatures {
+    /**
+     * Check for FEATURE_WATCH.
+     *
+     * @hide
+     */
+    @AssumeTrueForR8
+    public static boolean hasFeatureWatch(Context context) {
+        return true;
+    }
+
+    /**
+     * Check for FEATURE_PC.
+     *
+     * @hide
+     */
+    public static boolean hasFeaturePc(Context context) {
+        return hasFeatureFallback(context, PackageManager.FEATURE_PC);
+    }
+
+    /**
+     * Check for FEATURE_WIFI.
+     *
+     * @hide
+     */
+    @AssumeTrueForR8
+    public static boolean hasFeatureWifi(Context context) {
+        return true;
+    }
+
+    /**
+     * Check for FEATURE_VULKAN.
+     *
+     * @hide
+     */
+    @AssumeFalseForR8
+    public static boolean hasFeatureVulkan(Context context) {
+        return false;
+    }
+
+    /**
+     * Check for FEATURE_AUTO.
+     *
+     * @hide
+     */
+    @AssumeFalseForR8
+    public static boolean hasFeatureAuto(Context context) {
+        return false;
+    }
+
+    private static boolean hasFeatureFallback(Context context, String featureName) {
+        return context.getPackageManager().hasSystemFeature(featureName, 0);
+    }
+
+    /**
+     * @hide
+     */
+    @Nullable
+    public static Boolean maybeHasFeature(String featureName, int version) {
+        switch (featureName) {
+            case PackageManager.FEATURE_WATCH: return 1 >= version;
+            case PackageManager.FEATURE_WIFI: return 0 >= version;
+            case PackageManager.FEATURE_VULKAN: return -1 >= version;
+            case PackageManager.FEATURE_AUTO: return false;
+            default: break;
+        }
+        return null;
+    }
+}
diff --git a/tools/systemfeatures/tests/golden/RoNoFeatures.java.gen b/tools/systemfeatures/tests/golden/RoNoFeatures.java.gen
new file mode 100644
index 0000000..59c5b4e
--- /dev/null
+++ b/tools/systemfeatures/tests/golden/RoNoFeatures.java.gen
@@ -0,0 +1,35 @@
+// This file is auto-generated. DO NOT MODIFY.
+// Args: com.android.systemfeatures.RoNoFeatures \
+//            --readonly=true \
+//            --feature-apis=WATCH
+package com.android.systemfeatures;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.PackageManager;
+
+/**
+ * @hide
+ */
+public final class RoNoFeatures {
+    /**
+     * Check for FEATURE_WATCH.
+     *
+     * @hide
+     */
+    public static boolean hasFeatureWatch(Context context) {
+        return hasFeatureFallback(context, PackageManager.FEATURE_WATCH);
+    }
+
+    private static boolean hasFeatureFallback(Context context, String featureName) {
+        return context.getPackageManager().hasSystemFeature(featureName, 0);
+    }
+
+    /**
+     * @hide
+     */
+    @Nullable
+    public static Boolean maybeHasFeature(String featureName, int version) {
+        return null;
+    }
+}
diff --git a/tools/systemfeatures/tests/golden/RwFeatures.java.gen b/tools/systemfeatures/tests/golden/RwFeatures.java.gen
new file mode 100644
index 0000000..6f89759
--- /dev/null
+++ b/tools/systemfeatures/tests/golden/RwFeatures.java.gen
@@ -0,0 +1,65 @@
+// This file is auto-generated. DO NOT MODIFY.
+// Args: com.android.systemfeatures.RwFeatures \
+//            --readonly=false \
+//            --feature=WATCH:1 \
+//            --feature=WIFI:0 \
+//            --feature=VULKAN:-1 \
+//            --feature=AUTO:
+package com.android.systemfeatures;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.PackageManager;
+
+/**
+ * @hide
+ */
+public final class RwFeatures {
+    /**
+     * Check for FEATURE_WATCH.
+     *
+     * @hide
+     */
+    public static boolean hasFeatureWatch(Context context) {
+        return hasFeatureFallback(context, PackageManager.FEATURE_WATCH);
+    }
+
+    /**
+     * Check for FEATURE_WIFI.
+     *
+     * @hide
+     */
+    public static boolean hasFeatureWifi(Context context) {
+        return hasFeatureFallback(context, PackageManager.FEATURE_WIFI);
+    }
+
+    /**
+     * Check for FEATURE_VULKAN.
+     *
+     * @hide
+     */
+    public static boolean hasFeatureVulkan(Context context) {
+        return hasFeatureFallback(context, PackageManager.FEATURE_VULKAN);
+    }
+
+    /**
+     * Check for FEATURE_AUTO.
+     *
+     * @hide
+     */
+    public static boolean hasFeatureAuto(Context context) {
+        return hasFeatureFallback(context, PackageManager.FEATURE_AUTO);
+    }
+
+    private static boolean hasFeatureFallback(Context context, String featureName) {
+        return context.getPackageManager().hasSystemFeature(featureName, 0);
+    }
+
+    /**
+     * @hide
+     */
+    @Nullable
+    public static Boolean maybeHasFeature(String featureName, int version) {
+        return null;
+    }
+}
diff --git a/tools/systemfeatures/tests/golden/RwNoFeatures.java.gen b/tools/systemfeatures/tests/golden/RwNoFeatures.java.gen
new file mode 100644
index 0000000..2111d56
--- /dev/null
+++ b/tools/systemfeatures/tests/golden/RwNoFeatures.java.gen
@@ -0,0 +1,24 @@
+// This file is auto-generated. DO NOT MODIFY.
+// Args: com.android.systemfeatures.RwNoFeatures \
+//            --readonly=false
+package com.android.systemfeatures;
+
+import android.annotation.Nullable;
+import android.content.Context;
+
+/**
+ * @hide
+ */
+public final class RwNoFeatures {
+    private static boolean hasFeatureFallback(Context context, String featureName) {
+        return context.getPackageManager().hasSystemFeature(featureName, 0);
+    }
+
+    /**
+     * @hide
+     */
+    @Nullable
+    public static Boolean maybeHasFeature(String featureName, int version) {
+        return null;
+    }
+}
diff --git a/tools/systemfeatures/tests/golden_test.sh b/tools/systemfeatures/tests/golden_test.sh
new file mode 100755
index 0000000..c249254
--- /dev/null
+++ b/tools/systemfeatures/tests/golden_test.sh
@@ -0,0 +1,52 @@
+#!/usr/bin/env bash
+
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+set -e
+
+GEN_DIR="tests/gen"
+GOLDEN_DIR="tests/golden"
+
+if [[ $(basename $0) == "golden_test.sh" ]]; then
+  # We're running via command-line, so we need to:
+  #   1) manually update generated srcs
+  #   2) use absolute paths
+  if [ -z $ANDROID_BUILD_TOP ]; then
+    echo "You need to source and lunch before you can use this script directly."
+    exit 1
+  fi
+  GEN_DIR="$ANDROID_BUILD_TOP/out/soong/.intermediates/frameworks/base/tools/systemfeatures/systemfeatures-gen-tests-golden-srcs/gen/$GEN_DIR"
+  GOLDEN_DIR="$ANDROID_BUILD_TOP/frameworks/base/tools/systemfeatures/$GOLDEN_DIR"
+  rm -rf "$GEN_DIR"
+  "$ANDROID_BUILD_TOP"/build/soong/soong_ui.bash --make-mode systemfeatures-gen-tests-golden-srcs
+fi
+
+if [[ "$1" == "--update" ]]; then
+  rm -rf "$GOLDEN_DIR"
+  cp -R "$GEN_DIR" "$GOLDEN_DIR"
+  echo "Updated golden test files."
+else
+  echo "Running diff from test output against golden test files..."
+  if diff -ruN "$GOLDEN_DIR" "$GEN_DIR" ; then
+    echo "No changes."
+  else
+    echo
+    echo "----------------------------------------------------------------------------------------"
+    echo "If changes look OK, run:"
+    echo "  \$ANDROID_BUILD_TOP/frameworks/base/tools/systemfeatures/tests/golden_test.sh --update"
+    echo "----------------------------------------------------------------------------------------"
+    exit 1
+  fi
+fi
diff --git a/tools/systemfeatures/tests/Context.java b/tools/systemfeatures/tests/src/Context.java
similarity index 100%
rename from tools/systemfeatures/tests/Context.java
rename to tools/systemfeatures/tests/src/Context.java
diff --git a/tools/systemfeatures/tests/PackageManager.java b/tools/systemfeatures/tests/src/PackageManager.java
similarity index 100%
rename from tools/systemfeatures/tests/PackageManager.java
rename to tools/systemfeatures/tests/src/PackageManager.java
diff --git a/tools/systemfeatures/tests/SystemFeaturesGeneratorTest.java b/tools/systemfeatures/tests/src/SystemFeaturesGeneratorTest.java
similarity index 100%
rename from tools/systemfeatures/tests/SystemFeaturesGeneratorTest.java
rename to tools/systemfeatures/tests/src/SystemFeaturesGeneratorTest.java
diff --git a/tools/systemfeatures/tests/systemfeatures-gen-golden-tests.xml b/tools/systemfeatures/tests/systemfeatures-gen-golden-tests.xml
new file mode 100644
index 0000000..e3a5841
--- /dev/null
+++ b/tools/systemfeatures/tests/systemfeatures-gen-golden-tests.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Runs systemfeatures-gen golden diff test">
+    <test class="com.android.tradefed.testtype.binary.ExecutableHostTest" >
+      <option name="binary" value="systemfeatures-gen-golden-tests.sh"/>
+    </test>
+</configuration>