Merge "Add shell command to get aconfig flag values." into main
diff --git a/cmds/idmap2/Android.bp b/cmds/idmap2/Android.bp
index 55ec7da..6e51f00 100644
--- a/cmds/idmap2/Android.bp
+++ b/cmds/idmap2/Android.bp
@@ -86,6 +86,7 @@
             static_libs: [
                 "libidmap2_policies",
                 "libidmap2_protos",
+                "libpng",
             ],
             shared_libs: [
                 "libandroidfw",
@@ -107,6 +108,7 @@
                 "libcutils",
                 "libidmap2_policies",
                 "libidmap2_protos",
+                "libpng",
                 "libprotobuf-cpp-lite",
                 "libutils",
                 "libz",
@@ -185,6 +187,7 @@
     static_libs: [
         "libgmock",
         "libidmap2_protos",
+        "libpng",
     ],
     target: {
         android: {
@@ -258,6 +261,7 @@
                 "libbase",
                 "libcutils",
                 "libidmap2",
+                "libpng",
                 "libprotobuf-cpp-lite",
                 "libutils",
                 "libz",
@@ -275,6 +279,7 @@
                 "libidmap2",
                 "libidmap2_policies",
                 "liblog",
+                "libpng",
                 "libprotobuf-cpp-lite",
                 "libutils",
                 "libziparchive",
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp
index d76ca5b..f264125 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.cpp
+++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp
@@ -266,7 +266,8 @@
     } else if (res.binaryData.has_value()) {
       builder.SetResourceValue(res.resourceName, res.binaryData->get(),
                                res.binaryDataOffset, res.binaryDataSize,
-                               res.configuration.value_or(std::string()));
+                               res.configuration.value_or(std::string()),
+                               res.isNinePatch);
     } else {
       builder.SetResourceValue(res.resourceName, res.dataType, res.data,
             res.configuration.value_or(std::string()));
diff --git a/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
index 8ebd454..bca2ff3 100644
--- a/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
+++ b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
@@ -28,4 +28,5 @@
     @nullable @utf8InCpp String configuration;
     long binaryDataOffset;
     long binaryDataSize;
+    boolean isNinePatch;
 }
\ No newline at end of file
diff --git a/cmds/idmap2/include/idmap2/FabricatedOverlay.h b/cmds/idmap2/include/idmap2/FabricatedOverlay.h
index 1e7d4c2..bfcd4b9 100644
--- a/cmds/idmap2/include/idmap2/FabricatedOverlay.h
+++ b/cmds/idmap2/include/idmap2/FabricatedOverlay.h
@@ -19,6 +19,8 @@
 
 #include <libidmap2/proto/fabricated_v1.pb.h>
 
+#include "androidfw/Streams.h"
+
 #include <istream>
 #include <map>
 #include <memory>
@@ -51,7 +53,8 @@
                               std::optional<android::base::borrowed_fd>&& binary_value,
                               off64_t data_binary_offset,
                               size_t data_binary_size,
-                              const std::string& configuration);
+                              const std::string& configuration,
+                              bool nine_patch);
 
     inline Builder& setFrroPath(std::string frro_path) {
       frro_path_ = std::move(frro_path);
@@ -70,6 +73,7 @@
       off64_t data_binary_offset;
       size_t data_binary_size;
       std::string configuration;
+      bool nine_patch;
     };
 
     std::string package_name_;
@@ -81,7 +85,7 @@
   };
 
   struct BinaryData {
-    android::base::borrowed_fd file_descriptor;
+    std::unique_ptr<android::InputStream> input_stream;
     off64_t offset;
     size_t size;
   };
diff --git a/cmds/idmap2/include/idmap2/ResourceUtils.h b/cmds/idmap2/include/idmap2/ResourceUtils.h
index d4490ef4..9e463c9 100644
--- a/cmds/idmap2/include/idmap2/ResourceUtils.h
+++ b/cmds/idmap2/include/idmap2/ResourceUtils.h
@@ -45,6 +45,7 @@
   std::optional<android::base::borrowed_fd> data_binary_value;
   off64_t data_binary_offset;
   size_t data_binary_size;
+  bool nine_patch;
 };
 
 struct TargetValueWithConfig {
diff --git a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
index 47daf23..16bb896 100644
--- a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
+++ b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
@@ -20,8 +20,16 @@
 #include <sys/types.h>  // umask
 
 #include <android-base/file.h>
+#include <android-base/strings.h>
+#include <androidfw/BigBuffer.h>
+#include <androidfw/BigBufferStream.h>
+#include <androidfw/FileStream.h>
+#include <androidfw/Image.h>
+#include <androidfw/Png.h>
 #include <androidfw/ResourceUtils.h>
+#include <androidfw/StringPiece.h>
 #include <androidfw/StringPool.h>
+#include <androidfw/Streams.h>
 #include <google/protobuf/io/coded_stream.h>
 #include <google/protobuf/io/zero_copy_stream_impl.h>
 #include <utils/ByteOrder.h>
@@ -32,9 +40,9 @@
 #include <memory>
 #include <string>
 #include <utility>
+#include <sys/utsname.h>
 
 namespace android::idmap2 {
-
 constexpr auto kBufferSize = 1024;
 
 namespace {
@@ -81,7 +89,7 @@
     const std::string& resource_name, uint8_t data_type, uint32_t data_value,
     const std::string& configuration) {
   entries_.emplace_back(
-      Entry{resource_name, data_type, data_value, "", std::nullopt, 0, 0, configuration});
+      Entry{resource_name, data_type, data_value, "", std::nullopt, 0, 0, configuration, false});
   return *this;
 }
 
@@ -89,18 +97,90 @@
     const std::string& resource_name, uint8_t data_type, const std::string& data_string_value,
     const std::string& configuration) {
   entries_.emplace_back(
-      Entry{resource_name, data_type, 0, data_string_value, std::nullopt, 0, 0, configuration});
+      Entry{resource_name,
+            data_type,
+            0,
+            data_string_value,
+            std::nullopt,
+            0,
+            0,
+            configuration,
+            false});
   return *this;
 }
 
 FabricatedOverlay::Builder& FabricatedOverlay::Builder::SetResourceValue(
     const std::string& resource_name, std::optional<android::base::borrowed_fd>&& binary_value,
-    off64_t data_binary_offset, size_t data_binary_size, const std::string& configuration) {
+    off64_t data_binary_offset, size_t data_binary_size, const std::string& configuration,
+    bool nine_patch) {
   entries_.emplace_back(Entry{resource_name, 0, 0, "", binary_value,
-                              data_binary_offset, data_binary_size, configuration});
+                              data_binary_offset, data_binary_size, configuration, nine_patch});
   return *this;
 }
 
+static Result<FabricatedOverlay::BinaryData> buildBinaryData(
+        pb::ResourceValue* pb_value, const TargetValue &value) {
+  pb_value->set_data_type(Res_value::TYPE_STRING);
+  size_t binary_size;
+  off64_t binary_offset;
+  std::unique_ptr<android::InputStream> binary_stream;
+
+  if (value.nine_patch) {
+    std::string file_contents;
+    file_contents.resize(value.data_binary_size);
+    if (!base::ReadFullyAtOffset(value.data_binary_value->get(), file_contents.data(),
+                                 value.data_binary_size, value.data_binary_offset)) {
+      return Error("Failed to read binary file data.");
+    }
+    const StringPiece content(file_contents.c_str(), file_contents.size());
+    android::PngChunkFilter png_chunk_filter(content);
+    android::AndroidLogDiagnostics diag;
+    auto png = android::ReadPng(&png_chunk_filter, &diag);
+    if (!png) {
+      return Error("Error opening file as png");
+    }
+
+    std::string err;
+    std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(png->rows.get(),
+                                                              png->width, png->height,
+                                                              &err);
+    if (!nine_patch) {
+      return Error("%s", err.c_str());
+    }
+
+    png->width -= 2;
+    png->height -= 2;
+    memmove(png->rows.get(), png->rows.get() + 1, png->height * sizeof(uint8_t**));
+    for (int32_t h = 0; h < png->height; h++) {
+      memmove(png->rows[h], png->rows[h] + 4, png->width * 4);
+    }
+
+    android::BigBuffer buffer(value.data_binary_size);
+    android::BigBufferOutputStream buffer_output_stream(&buffer);
+    if (!android::WritePng(png.get(), nine_patch.get(), &buffer_output_stream, {},
+                           &diag, false)) {
+      return Error("Error writing frro png");
+    }
+
+    binary_size = buffer.size();
+    binary_offset = 0;
+    android::BigBufferInputStream *buffer_input_stream
+            = new android::BigBufferInputStream(std::move(buffer));
+    binary_stream.reset(buffer_input_stream);
+  } else {
+    binary_size = value.data_binary_size;
+    binary_offset = value.data_binary_offset;
+    android::FileInputStream *fis
+            = new android::FileInputStream(value.data_binary_value.value());
+    binary_stream.reset(fis);
+  }
+
+  return FabricatedOverlay::BinaryData{
+          std::move(binary_stream),
+          binary_offset,
+          binary_size};
+}
+
 Result<FabricatedOverlay> FabricatedOverlay::Builder::Build() {
   using ConfigMap = std::map<std::string, TargetValue, std::less<>>;
   using EntryMap = std::map<std::string, ConfigMap, std::less<>>;
@@ -150,7 +230,8 @@
 
     value->second = TargetValue{res_entry.data_type, res_entry.data_value,
                                 res_entry.data_string_value, res_entry.data_binary_value,
-                                res_entry.data_binary_offset, res_entry.data_binary_size};
+                                res_entry.data_binary_offset, res_entry.data_binary_size,
+                                res_entry.nine_patch};
   }
 
   pb::FabricatedOverlay overlay_pb;
@@ -183,18 +264,20 @@
             auto ref = string_pool.MakeRef(value.second.data_string_value);
             pb_value->set_data_value(ref.index());
           } else if (value.second.data_binary_value.has_value()) {
-              pb_value->set_data_type(Res_value::TYPE_STRING);
-              std::string uri
-                  = StringPrintf("frro:/%s?offset=%d&size=%d", frro_path_.c_str(),
-                                 static_cast<int> (FRRO_HEADER_SIZE + total_binary_bytes),
-                                 static_cast<int> (value.second.data_binary_size));
-              total_binary_bytes += value.second.data_binary_size;
-              binary_files.emplace_back(FabricatedOverlay::BinaryData{
-                  value.second.data_binary_value->get(),
-                  value.second.data_binary_offset,
-                  value.second.data_binary_size});
-              auto ref = string_pool.MakeRef(std::move(uri));
-              pb_value->set_data_value(ref.index());
+            auto binary_data = buildBinaryData(pb_value, value.second);
+            if (!binary_data) {
+              return binary_data.GetError();
+            }
+            pb_value->set_data_type(Res_value::TYPE_STRING);
+
+            std::string uri
+                = StringPrintf("frro:/%s?offset=%d&size=%d", frro_path_.c_str(),
+                               static_cast<int> (FRRO_HEADER_SIZE + total_binary_bytes),
+                               static_cast<int> (binary_data->size));
+            total_binary_bytes += binary_data->size;
+            binary_files.emplace_back(std::move(*binary_data));
+            auto ref = string_pool.MakeRef(std::move(uri));
+            pb_value->set_data_value(ref.index());
           } else {
             pb_value->set_data_value(value.second.data_value);
           }
@@ -311,9 +394,9 @@
   Write32(stream, (*data)->pb_crc);
   Write32(stream, total_binary_bytes_);
   std::string file_contents;
-  for (const FabricatedOverlay::BinaryData fd : binary_files_) {
-    file_contents.resize(fd.size);
-    if (!ReadFullyAtOffset(fd.file_descriptor, file_contents.data(), fd.size, fd.offset)) {
+  for (const FabricatedOverlay::BinaryData& bd : binary_files_) {
+    file_contents.resize(bd.size);
+    if (!bd.input_stream->ReadFullyAtOffset(file_contents.data(), bd.size, bd.offset)) {
       return Error("Failed to read binary file data.");
     }
     stream.write(file_contents.data(), file_contents.length());
diff --git a/cmds/idmap2/self_targeting/SelfTargeting.cpp b/cmds/idmap2/self_targeting/SelfTargeting.cpp
index c7f5cf3..7f9c468 100644
--- a/cmds/idmap2/self_targeting/SelfTargeting.cpp
+++ b/cmds/idmap2/self_targeting/SelfTargeting.cpp
@@ -53,7 +53,7 @@
         if (entry_params.data_binary_value.has_value()) {
             builder.SetResourceValue(entry_params.resource_name, *entry_params.data_binary_value,
                                      entry_params.binary_data_offset, entry_params.binary_data_size,
-                                     entry_params.configuration);
+                                     entry_params.configuration, entry_params.nine_patch);
         } else  if (dataType >= Res_value::TYPE_FIRST_INT && dataType <= Res_value::TYPE_LAST_INT) {
            builder.SetResourceValue(entry_params.resource_name, dataType,
                                     entry_params.data_value, entry_params.configuration);
diff --git a/cmds/idmap2/tests/FabricatedOverlayTests.cpp b/cmds/idmap2/tests/FabricatedOverlayTests.cpp
index b460bb3..6b1c7e8 100644
--- a/cmds/idmap2/tests/FabricatedOverlayTests.cpp
+++ b/cmds/idmap2/tests/FabricatedOverlayTests.cpp
@@ -59,7 +59,7 @@
               Res_value::TYPE_STRING,
               "foobar",
               "en-rUS-normal-xxhdpi-v21")
-          .SetResourceValue("com.example.target:drawable/dr1", fd, 0, 8341, "port-xxhdpi-v7")
+          .SetResourceValue("com.example.target:drawable/dr1", fd, 0, 8341, "port-xxhdpi-v7", false)
           .setFrroPath("/foo/bar/biz.frro")
           .Build();
   ASSERT_TRUE(overlay);
diff --git a/cmds/idmap2/tests/IdmapTests.cpp b/cmds/idmap2/tests/IdmapTests.cpp
index a3448fd..a384305 100644
--- a/cmds/idmap2/tests/IdmapTests.cpp
+++ b/cmds/idmap2/tests/IdmapTests.cpp
@@ -269,7 +269,7 @@
                   .SetResourceValue("integer/int1", Res_value::TYPE_INT_DEC, 2U, "land-xxhdpi-v7")
                   .SetResourceValue("string/str1", Res_value::TYPE_REFERENCE, 0x7f010000, "land")
                   .SetResourceValue("string/str2", Res_value::TYPE_STRING, "foobar", "xxhdpi-v7")
-                  .SetResourceValue("drawable/dr1", fd, 0, 8341, "port-xxhdpi-v7")
+                  .SetResourceValue("drawable/dr1", fd, 0, 8341, "port-xxhdpi-v7", false)
                   .setFrroPath("/foo/bar/biz.frro")
                   .Build();
 
diff --git a/cmds/idmap2/tests/ResourceMappingTests.cpp b/cmds/idmap2/tests/ResourceMappingTests.cpp
index 40f98c2..db44c23 100644
--- a/cmds/idmap2/tests/ResourceMappingTests.cpp
+++ b/cmds/idmap2/tests/ResourceMappingTests.cpp
@@ -212,7 +212,7 @@
                   .SetResourceValue("integer/int1", Res_value::TYPE_INT_DEC, 2U, "")
                   .SetResourceValue("string/str1", Res_value::TYPE_REFERENCE, 0x7f010000, "")
                   .SetResourceValue("string/str2", Res_value::TYPE_STRING, "foobar", "")
-                  .SetResourceValue("drawable/dr1", fd, 0, 8341, "")
+                  .SetResourceValue("drawable/dr1", fd, 0, 8341, "", false)
                   .setFrroPath("/foo/bar/biz.frro")
                   .Build();
 
diff --git a/core/api/current.txt b/core/api/current.txt
index adf3779..7731fac 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -11787,6 +11787,7 @@
   public class FabricatedOverlay {
     ctor public FabricatedOverlay(@NonNull String, @NonNull String);
     method @NonNull public android.content.om.OverlayIdentifier getIdentifier();
+    method @FlaggedApi("android.content.res.nine_patch_frro") @NonNull public void setNinePatchResourceValue(@NonNull String, @NonNull android.os.ParcelFileDescriptor, @Nullable String);
     method @NonNull public void setResourceValue(@NonNull String, @IntRange(from=android.util.TypedValue.TYPE_FIRST_INT, to=android.util.TypedValue.TYPE_LAST_INT) int, int, @Nullable String);
     method @NonNull public void setResourceValue(@NonNull String, int, @NonNull String, @Nullable String);
     method @NonNull public void setResourceValue(@NonNull String, @NonNull android.os.ParcelFileDescriptor, @Nullable String);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 71a05a9..076fddf 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1597,6 +1597,10 @@
     method public void restoreDozeSettings(int);
   }
 
+  public final class ColorDisplayManager {
+    method @FlaggedApi("android.app.modes_api") @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) public boolean isSaturationActivated();
+  }
+
   public final class DisplayManager {
     method public boolean areUserDisabledHdrTypesAllowed();
     method @RequiresPermission(android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE) public void clearGlobalUserPreferredDisplayMode();
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 1000612..2a7dbab 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -566,8 +566,10 @@
     public static final int GLOBAL_ACTION_TAKE_SCREENSHOT = 9;
 
     /**
-     * Action to send the KEYCODE_HEADSETHOOK KeyEvent, which is used to answer/hang up calls and
-     * play/stop media
+     * Action to send the KEYCODE_HEADSETHOOK KeyEvent, which is used to answer and hang up calls
+     * and play and stop media. Calling takes priority. If there is an incoming call,
+     * this action can be used to answer that call, and if there is an ongoing call, to hang up on
+     * that call.
      */
     public static final int GLOBAL_ACTION_KEYCODE_HEADSETHOOK = 10;
 
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 8af1216..adaaee2 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -2292,7 +2292,8 @@
                     case DUMP_HEAP: return "DUMP_HEAP";
                     case DUMP_ACTIVITY: return "DUMP_ACTIVITY";
                     case SET_CORE_SETTINGS: return "SET_CORE_SETTINGS";
-                    case UPDATE_PACKAGE_COMPATIBILITY_INFO: return "UPDATE_PACKAGE_COMPATIBILITY_INFO";
+                    case UPDATE_PACKAGE_COMPATIBILITY_INFO:
+                        return "UPDATE_PACKAGE_COMPATIBILITY_INFO";
                     case DUMP_PROVIDER: return "DUMP_PROVIDER";
                     case UNSTABLE_PROVIDER_DIED: return "UNSTABLE_PROVIDER_DIED";
                     case REQUEST_ASSIST_CONTEXT_EXTRAS: return "REQUEST_ASSIST_CONTEXT_EXTRAS";
@@ -3804,7 +3805,7 @@
 
         boolean isSandboxActivityContext =
                 sandboxActivitySdkBasedContext()
-                        && SdkSandboxActivityAuthority.isSdkSandboxActivity(
+                        && SdkSandboxActivityAuthority.isSdkSandboxActivityIntent(
                                 mSystemContext, r.intent);
         boolean isSandboxedSdkContextUsed = false;
         ContextImpl activityBaseContext;
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index d660078..820ff3e 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -21,6 +21,8 @@
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
 
+import static com.android.window.flags.Flags.multiCrop;
+
 import android.annotation.FloatRange;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -857,8 +859,7 @@
      */
     public static boolean isMultiCropEnabled() {
         if (sGlobals == null) {
-            sIsMultiCropEnabled = SystemProperties.getBoolean(
-                    "persist.wm.debug.wallpaper_multi_crop", false);
+            sIsMultiCropEnabled = multiCrop();
         }
         if (sIsMultiCropEnabled == null) {
             try {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index fc3a906..1e538c5 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -9146,7 +9146,7 @@
     @UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
     public boolean isDeviceOwnerApp(String packageName) {
         throwIfParentInstance("isDeviceOwnerApp");
-        if (android.permission.flags.Flags.roleControllerInSystemServer()
+        if (android.permission.flags.Flags.systemServerRoleControllerEnabled()
                 && CompatChanges.isChangeEnabled(IS_DEVICE_OWNER_USER_AWARE)) {
             return isDeviceOwnerAppOnContextUser(packageName);
         }
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 38bcfa2..5c6c49a 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -12606,8 +12606,11 @@
     }
 
     /**
-     * @deprecated Use {@link SdkSandboxActivityAuthority#isSdkSandboxActivity} instead.
+     * @deprecated Use {@link SdkSandboxActivityAuthority#isSdkSandboxActivityIntent} instead.
      * Once the other API is finalized this method will be removed.
+     *
+     * TODO(b/300059435): remove as part of the cleanup.
+     *
      * @hide
      */
     @Deprecated
diff --git a/core/java/android/content/om/FabricatedOverlay.java b/core/java/android/content/om/FabricatedOverlay.java
index df2d7e7..40ffb0f 100644
--- a/core/java/android/content/om/FabricatedOverlay.java
+++ b/core/java/android/content/om/FabricatedOverlay.java
@@ -281,8 +281,8 @@
                 @NonNull ParcelFileDescriptor value,
                 @Nullable String configuration) {
             ensureValidResourceName(resourceName);
-            mEntries.add(
-                    generateFabricatedOverlayInternalEntry(resourceName, value, configuration));
+            mEntries.add(generateFabricatedOverlayInternalEntry(
+                    resourceName, value, configuration, false));
             return this;
         }
 
@@ -361,6 +361,16 @@
     }
 
     /**
+     * Set the package that owns the overlay
+     *
+     * @param owningPackage the package that should own the overlay.
+     * @hide
+     */
+    public void setOwningPackage(@NonNull String owningPackage) {
+        mOverlay.packageName = owningPackage;
+    }
+
+    /**
      * Set the target overlayable name of the overlay
      *
      * The target package defines may define several overlayables. The {@link FabricatedOverlay}
@@ -442,13 +452,14 @@
     @NonNull
     private static FabricatedOverlayInternalEntry generateFabricatedOverlayInternalEntry(
             @NonNull String resourceName, @NonNull ParcelFileDescriptor parcelFileDescriptor,
-            @Nullable String configuration) {
+            @Nullable String configuration, boolean isNinePatch) {
         final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
         entry.resourceName = resourceName;
         entry.binaryData = Objects.requireNonNull(parcelFileDescriptor);
         entry.configuration = configuration;
         entry.binaryDataOffset = 0;
         entry.binaryDataSize = parcelFileDescriptor.getStatSize();
+        entry.isNinePatch = isNinePatch;
         return entry;
     }
 
@@ -534,7 +545,26 @@
             @Nullable String configuration) {
         ensureValidResourceName(resourceName);
         mOverlay.entries.add(
-                generateFabricatedOverlayInternalEntry(resourceName, value, configuration));
+                generateFabricatedOverlayInternalEntry(resourceName, value, configuration, false));
+    }
+
+    /**
+     * Sets the resource value in the fabricated overlay from a nine patch.
+     *
+     * @param resourceName name of the target resource to overlay (in the form
+     *     [package]:type/entry)
+     * @param value the file descriptor whose contents are the value of the frro
+     * @param configuration The string representation of the config this overlay is enabled for
+     */
+    @NonNull
+    @FlaggedApi(android.content.res.Flags.FLAG_NINE_PATCH_FRRO)
+    public void setNinePatchResourceValue(
+            @NonNull String resourceName,
+            @NonNull ParcelFileDescriptor value,
+            @Nullable String configuration) {
+        ensureValidResourceName(resourceName);
+        mOverlay.entries.add(
+                generateFabricatedOverlayInternalEntry(resourceName, value, configuration, true));
     }
 
     /**
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 323592c..d13d962 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -55,6 +55,7 @@
  * from the AndroidManifest.xml's &lt;activity&gt; and
  * &lt;receiver&gt; tags.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class ActivityInfo extends ComponentInfo implements Parcelable {
 
     private static final Parcelling.BuiltIn.ForStringSet sForStringSet =
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index f0b99f1..16a80e9 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -66,6 +66,7 @@
  * corresponds to information collected from the AndroidManifest.xml's
  * &lt;application&gt; tag.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class ApplicationInfo extends PackageItemInfo implements Parcelable {
     private static final ForBoolean sForBoolean = Parcelling.Cache.getOrCreate(ForBoolean.class);
     private static final Parcelling.BuiltIn.ForStringSet sForStringSet =
@@ -1386,6 +1387,7 @@
      *
      * @see #category
      */
+    @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.content.res.Resources.class)
     public static CharSequence getCategoryTitle(Context context, @Category int category) {
         switch (category) {
             case ApplicationInfo.CATEGORY_GAME:
@@ -2187,6 +2189,7 @@
      * @return Returns a CharSequence containing the application's description.
      * If there is no description, null is returned.
      */
+    @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.content.res.Resources.class)
     public CharSequence loadDescription(PackageManager pm) {
         if (descriptionRes != 0) {
             CharSequence label = pm.getText(packageName, descriptionRes, this);
@@ -2222,6 +2225,7 @@
     }
 
     /** {@hide} */
+    @android.ravenwood.annotation.RavenwoodThrow(blockedBy = Environment.class)
     public void initForUser(int userId) {
         uid = UserHandle.getUid(userId, UserHandle.getAppId(uid));
 
@@ -2414,6 +2418,7 @@
      * @hide
      */
     @Override
+    @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.content.res.Resources.class)
     public Drawable loadDefaultIcon(PackageManager pm) {
         if ((flags & FLAG_EXTERNAL_STORAGE) != 0
                 && isPackageUnavailable(pm)) {
@@ -2424,6 +2429,7 @@
     }
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    @android.ravenwood.annotation.RavenwoodThrow(blockedBy = PackageManager.class)
     private boolean isPackageUnavailable(PackageManager pm) {
         try {
             return pm.getPackageInfo(packageName, 0) == null;
diff --git a/core/java/android/content/pm/ComponentInfo.java b/core/java/android/content/pm/ComponentInfo.java
index 42847c8..ff48ffa 100644
--- a/core/java/android/content/pm/ComponentInfo.java
+++ b/core/java/android/content/pm/ComponentInfo.java
@@ -37,6 +37,7 @@
  * implement Parcelable, but does provide convenience methods to assist
  * in the implementation of Parcelable in subclasses.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class ComponentInfo extends PackageItemInfo {
     /**
      * Global information about the application/package this component is a
@@ -258,6 +259,7 @@
      * @hide
      */
     @Override
+    @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.content.res.Resources.class)
     public Drawable loadDefaultIcon(PackageManager pm) {
         return applicationInfo.loadIcon(pm);
     }
@@ -265,6 +267,7 @@
     /**
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.content.res.Resources.class)
     @Override protected Drawable loadDefaultBanner(PackageManager pm) {
         return applicationInfo.loadBanner(pm);
     }
@@ -273,6 +276,7 @@
      * @hide
      */
     @Override
+    @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.content.res.Resources.class)
     protected Drawable loadDefaultLogo(PackageManager pm) {
         return applicationInfo.loadLogo(pm);
     }
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index 5736a6d..5dee65b 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -29,6 +29,7 @@
  * Overall information about the contents of a package.  This corresponds
  * to all of the information collected from AndroidManifest.xml.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class PackageInfo implements Parcelable {
     /**
      * The name of this package.  From the &lt;manifest&gt; tag's "name"
diff --git a/core/java/android/content/pm/PackageItemInfo.java b/core/java/android/content/pm/PackageItemInfo.java
index 70e6f98..1f821b9 100644
--- a/core/java/android/content/pm/PackageItemInfo.java
+++ b/core/java/android/content/pm/PackageItemInfo.java
@@ -49,6 +49,7 @@
  * itself implement Parcelable, but does provide convenience methods to assist
  * in the implementation of Parcelable in subclasses.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class PackageItemInfo {
 
     /**
@@ -214,6 +215,7 @@
      * @return Returns a CharSequence containing the item's label.  If the
      * item does not have a label, its name is returned.
      */
+    @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.content.res.Resources.class)
     public @NonNull CharSequence loadLabel(@NonNull PackageManager pm) {
         if (sForceSafeLabels && !Objects.equals(packageName, ActivityThread.currentPackageName())) {
             return loadSafeLabel(pm, DEFAULT_MAX_LABEL_SIZE_PX, SAFE_STRING_FLAG_TRIM
@@ -226,6 +228,7 @@
     }
 
     /** {@hide} */
+    @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.content.res.Resources.class)
     public CharSequence loadUnsafeLabel(PackageManager pm) {
         if (nonLocalizedLabel != null) {
             return nonLocalizedLabel;
@@ -248,6 +251,7 @@
      */
     @SystemApi
     @Deprecated
+    @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.content.res.Resources.class)
     public @NonNull CharSequence loadSafeLabel(@NonNull PackageManager pm) {
         return loadSafeLabel(pm, DEFAULT_MAX_LABEL_SIZE_PX, SAFE_STRING_FLAG_TRIM
                 | SAFE_STRING_FLAG_FIRST_LINE);
@@ -261,6 +265,7 @@
      * @hide
     */
     @SystemApi
+    @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.content.res.Resources.class)
     public @NonNull CharSequence loadSafeLabel(@NonNull PackageManager pm,
             @FloatRange(from = 0) float ellipsizeDip, @TextUtils.SafeStringFlags int flags) {
         Objects.requireNonNull(pm);
@@ -281,6 +286,7 @@
      * item does not have an icon, the item's default icon is returned
      * such as the default activity icon.
      */
+    @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.content.res.Resources.class)
     public Drawable loadIcon(PackageManager pm) {
         return pm.loadItemIcon(this, getApplicationInfo());
     }
@@ -298,6 +304,7 @@
      * item does not have an icon, the item's default icon is returned
      * such as the default activity icon.
      */
+    @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.content.res.Resources.class)
     public Drawable loadUnbadgedIcon(PackageManager pm) {
         return pm.loadUnbadgedItemIcon(this, getApplicationInfo());
     }
@@ -313,6 +320,7 @@
      * @return Returns a Drawable containing the item's banner.  If the item
      * does not have a banner, this method will return null.
      */
+    @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.content.res.Resources.class)
     public Drawable loadBanner(PackageManager pm) {
         if (banner != 0) {
             Drawable dr = pm.getDrawable(packageName, banner, getApplicationInfo());
@@ -334,6 +342,7 @@
      *
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.content.res.Resources.class)
     public Drawable loadDefaultIcon(PackageManager pm) {
         return pm.getDefaultActivityIcon();
     }
@@ -349,6 +358,7 @@
      *
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.content.res.Resources.class)
     protected Drawable loadDefaultBanner(PackageManager pm) {
         return null;
     }
@@ -364,6 +374,7 @@
      * @return Returns a Drawable containing the item's logo. If the item
      * does not have a logo, this method will return null.
      */
+    @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.content.res.Resources.class)
     public Drawable loadLogo(PackageManager pm) {
         if (logo != 0) {
             Drawable d = pm.getDrawable(packageName, logo, getApplicationInfo());
@@ -385,6 +396,7 @@
      *
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.content.res.Resources.class)
     protected Drawable loadDefaultLogo(PackageManager pm) {
         return null;
     }
@@ -402,6 +414,7 @@
      * assigned as the given meta-data.  If the meta-data name is not defined
      * or the XML resource could not be found, null is returned.
      */
+    @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.content.res.Resources.class)
     public XmlResourceParser loadXmlMetaData(PackageManager pm, String name) {
         if (metaData != null) {
             int resid = metaData.getInt(name);
diff --git a/core/java/android/content/pm/PathPermission.java b/core/java/android/content/pm/PathPermission.java
index 11c9a7d..743ff9a 100644
--- a/core/java/android/content/pm/PathPermission.java
+++ b/core/java/android/content/pm/PathPermission.java
@@ -24,6 +24,7 @@
  * Description of permissions needed to access a particular path
  * in a {@link ProviderInfo}.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class PathPermission extends PatternMatcher {
     private final String mReadPermission;
     private final String mWritePermission;
diff --git a/core/java/android/content/pm/ProviderInfo.java b/core/java/android/content/pm/ProviderInfo.java
index 3984ade..9e553db 100644
--- a/core/java/android/content/pm/ProviderInfo.java
+++ b/core/java/android/content/pm/ProviderInfo.java
@@ -27,6 +27,7 @@
  * {@link android.content.pm.PackageManager#resolveContentProvider(java.lang.String, int)
  * PackageManager.resolveContentProvider()}.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class ProviderInfo extends ComponentInfo
         implements Parcelable {
     
diff --git a/core/java/android/content/pm/ResolveInfo.java b/core/java/android/content/pm/ResolveInfo.java
index 36c03fd..25bb9e1 100644
--- a/core/java/android/content/pm/ResolveInfo.java
+++ b/core/java/android/content/pm/ResolveInfo.java
@@ -42,6 +42,7 @@
  * information collected from the AndroidManifest.xml's
  * &lt;intent&gt; tags.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class ResolveInfo implements Parcelable {
     private static final String TAG = "ResolveInfo";
     private static final String INTENT_FORWARDER_ACTIVITY =
@@ -227,6 +228,7 @@
      * item does not have a label, its name is returned.
      */
     @NonNull
+    @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.content.res.Resources.class)
     public CharSequence loadLabel(@NonNull PackageManager pm) {
         if (nonLocalizedLabel != null) {
             return nonLocalizedLabel;
@@ -304,6 +306,7 @@
      * @return Returns a Drawable containing the resolution's icon.  If the
      * item does not have an icon, the default activity icon is returned.
      */
+    @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.content.res.Resources.class)
     public Drawable loadIcon(PackageManager pm) {
         Drawable dr = null;
         if (resolvePackageName != null && iconResourceId != 0) {
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 9869179..4d704c3 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -31,6 +31,7 @@
  * service. This corresponds to information collected from the
  * AndroidManifest.xml's &lt;service&gt; tags.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class ServiceInfo extends ComponentInfo
         implements Parcelable {
     /**
diff --git a/core/java/android/content/pm/Signature.java b/core/java/android/content/pm/Signature.java
index a69eee7..f173334 100644
--- a/core/java/android/content/pm/Signature.java
+++ b/core/java/android/content/pm/Signature.java
@@ -43,6 +43,7 @@
  * <p>
  * This class name is slightly misleading, since it's not actually a signature.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class Signature implements Parcelable {
     private final byte[] mSignature;
     private int mHashCode;
diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig
index 40592a1..3a00d91 100644
--- a/core/java/android/content/res/flags.aconfig
+++ b/core/java/android/content/res/flags.aconfig
@@ -24,3 +24,10 @@
     # This flag is read in PackageParser at boot time, and in aapt2 which is a build tool.
     is_fixed_read_only: true
 }
+
+flag {
+    name: "nine_patch_frro"
+    namespace: "resource_manager"
+    description: "Feature flag for creating an frro from a 9-patch"
+    bug: "309232726"
+}
diff --git a/core/java/android/hardware/display/ColorDisplayManager.java b/core/java/android/hardware/display/ColorDisplayManager.java
index aafa7d5..f927b8b 100644
--- a/core/java/android/hardware/display/ColorDisplayManager.java
+++ b/core/java/android/hardware/display/ColorDisplayManager.java
@@ -17,12 +17,14 @@
 package android.hardware.display;
 
 import android.Manifest;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
+import android.annotation.TestApi;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.metrics.LogMaker;
@@ -397,6 +399,8 @@
      * @return {@code true} if the display is not at full saturation
      * @hide
      */
+    @TestApi
+    @FlaggedApi(android.app.Flags.FLAG_MODES_API)
     @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
     public boolean isSaturationActivated() {
         return mManager.isSaturationActivated();
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 5cbc18e..cbeb821 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -16,7 +16,7 @@
 }
 
 flag {
-    name: "role_controller_in_system_server"
+    name: "system_server_role_controller_enabled"
     is_fixed_read_only: true
     namespace: "permissions"
     description: "enable role controller in system server"
diff --git a/core/java/android/service/notification/DeviceEffectsApplier.java b/core/java/android/service/notification/DeviceEffectsApplier.java
new file mode 100644
index 0000000..234ff4d
--- /dev/null
+++ b/core/java/android/service/notification/DeviceEffectsApplier.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.notification;
+
+/**
+ * Responsible for making any service calls needed to apply the set of {@link ZenDeviceEffects} that
+ * make sense for the current platform.
+ * @hide
+ */
+public interface DeviceEffectsApplier {
+    /**
+     * Applies the {@link ZenDeviceEffects} to the device.
+     *
+     * <p>The supplied {@code effects} represents the "consolidated" device effects, i.e. the
+     * union of the effects of all the {@link ZenModeConfig.ZenRule} instances that are currently
+     * active. If no rules are active (or no active rules specify custom effects) then {@code
+     * effects} will be all-default (i.e. {@link ZenDeviceEffects#hasEffects} will return {@code
+     * false}.
+     *
+     * <p>This will be called whenever the set of consolidated effects changes (normally through
+     * the activation or deactivation of zen rules).
+     */
+    void apply(ZenDeviceEffects effects);
+}
diff --git a/core/java/android/service/notification/ZenDeviceEffects.java b/core/java/android/service/notification/ZenDeviceEffects.java
index db0b7ff..0e82b6c 100644
--- a/core/java/android/service/notification/ZenDeviceEffects.java
+++ b/core/java/android/service/notification/ZenDeviceEffects.java
@@ -18,6 +18,7 @@
 
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.Flags;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -359,6 +360,27 @@
             return this;
         }
 
+        /**
+         * Applies the effects that are {@code true} on the supplied {@link ZenDeviceEffects} to
+         * this builder (essentially logically-ORing the effect set).
+         * @hide
+         */
+        @NonNull
+        public Builder add(@Nullable ZenDeviceEffects effects) {
+            if (effects == null) return this;
+            if (effects.shouldDisplayGrayscale()) setShouldDisplayGrayscale(true);
+            if (effects.shouldSuppressAmbientDisplay()) setShouldSuppressAmbientDisplay(true);
+            if (effects.shouldDimWallpaper()) setShouldDimWallpaper(true);
+            if (effects.shouldUseNightMode()) setShouldUseNightMode(true);
+            if (effects.shouldDisableAutoBrightness()) setShouldDisableAutoBrightness(true);
+            if (effects.shouldDisableTapToWake()) setShouldDisableTapToWake(true);
+            if (effects.shouldDisableTiltToWake()) setShouldDisableTiltToWake(true);
+            if (effects.shouldDisableTouch()) setShouldDisableTouch(true);
+            if (effects.shouldMinimizeRadioUsage()) setShouldMinimizeRadioUsage(true);
+            if (effects.shouldMaximizeDoze()) setShouldMaximizeDoze(true);
+            return this;
+        }
+
         /** Builds a {@link ZenDeviceEffects} object based on the builder's state. */
         @NonNull
         public ZenDeviceEffects build() {
diff --git a/core/java/android/view/AttachedSurfaceControl.java b/core/java/android/view/AttachedSurfaceControl.java
index f28574e..fd5517d 100644
--- a/core/java/android/view/AttachedSurfaceControl.java
+++ b/core/java/android/view/AttachedSurfaceControl.java
@@ -27,6 +27,9 @@
 
 import com.android.window.flags.Flags;
 
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
 /**
  * Provides an interface to the root-Surface of a View Hierarchy or Window. This
  * is used in combination with the {@link android.view.SurfaceControl} API to enable
@@ -194,6 +197,42 @@
     }
 
     /**
+     * Add a trusted presentation listener on the SurfaceControl associated with this window.
+     *
+     * @param t          Transaction that the trusted presentation listener is added on. This should
+     *                   be applied by the caller.
+     * @param thresholds The {@link SurfaceControl.TrustedPresentationThresholds} that will specify
+     *                   when the to invoke the callback.
+     * @param executor   The {@link Executor} where the callback will be invoked on.
+     * @param listener   The {@link Consumer} that will receive the callbacks when entered or
+     *                   exited the threshold.
+     *
+     * @see SurfaceControl.Transaction#setTrustedPresentationCallback(SurfaceControl,
+     * SurfaceControl.TrustedPresentationThresholds, Executor, Consumer)
+     *
+     * @hide b/287076178 un-hide with API bump
+     */
+    default void addTrustedPresentationCallback(@NonNull SurfaceControl.Transaction t,
+            @NonNull SurfaceControl.TrustedPresentationThresholds thresholds,
+            @NonNull Executor executor, @NonNull Consumer<Boolean> listener) {
+    }
+
+    /**
+     * Remove a trusted presentation listener on the SurfaceControl associated with this window.
+     *
+     * @param t          Transaction that the trusted presentation listener removed on. This should
+     *                   be applied by the caller.
+     * @param listener   The {@link Consumer} that was previously registered with
+     *                   addTrustedPresentationCallback that should be removed.
+     *
+     * @see SurfaceControl.Transaction#clearTrustedPresentationCallback(SurfaceControl)
+     * @hide b/287076178 un-hide with API bump
+     */
+    default void removeTrustedPresentationCallback(@NonNull SurfaceControl.Transaction t,
+            @NonNull Consumer<Boolean> listener) {
+    }
+
+    /**
      * Transfer the currently in progress touch gesture from the host to the requested
      * {@link SurfaceControlViewHost.SurfacePackage}. This requires that the
      * SurfaceControlViewHost was created with the current host's inputToken.
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 36b74e3..17bbee6d0 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -73,8 +73,6 @@
 import android.window.ITaskFpsCallback;
 import android.window.ScreenCapture;
 import android.window.WindowContextInfo;
-import android.window.ITrustedPresentationListener;
-import android.window.TrustedPresentationThresholds;
 
 /**
  * System private interface to the window manager.
@@ -1077,10 +1075,4 @@
     @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
             + ".permission.MONITOR_INPUT)")
     void unregisterDecorViewGestureListener(IDecorViewGestureListener listener, int displayId);
-
-    void registerTrustedPresentationListener(in IBinder window, in ITrustedPresentationListener listener,
-            in TrustedPresentationThresholds thresholds, int id);
-
-
-    void unregisterTrustedPresentationListener(in ITrustedPresentationListener listener, int id);
 }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 1a4dbef..870ec4b 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -12032,6 +12032,18 @@
         scheduleTraversals();
     }
 
+    @Override
+    public void addTrustedPresentationCallback(@NonNull SurfaceControl.Transaction t,
+            @NonNull SurfaceControl.TrustedPresentationThresholds thresholds,
+            @NonNull Executor executor, @NonNull Consumer<Boolean> listener) {
+        t.setTrustedPresentationCallback(getSurfaceControl(), thresholds, executor, listener);
+    }
+
+    @Override
+    public void removeTrustedPresentationCallback(@NonNull SurfaceControl.Transaction t,
+            @NonNull Consumer<Boolean> listener) {
+        t.clearTrustedPresentationCallback(getSurfaceControl());
+    }
 
     private void logAndTrace(String msg) {
         if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index f668088..046ea77 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -122,9 +122,7 @@
 import android.view.WindowInsets.Type;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.accessibility.AccessibilityNodeInfo;
-import android.window.ITrustedPresentationListener;
 import android.window.TaskFpsCallback;
-import android.window.TrustedPresentationThresholds;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -5886,35 +5884,4 @@
     default boolean replaceContentOnDisplayWithSc(int displayId, @NonNull SurfaceControl sc) {
         throw new UnsupportedOperationException();
     }
-
-    /**
-     * Add a trusted presentation listener associated with a window. If the listener has already
-     * been registered, an AndroidRuntimeException will be thrown.
-     *
-     * @param window  The Window to add the trusted presentation listener for
-     * @param thresholds The {@link TrustedPresentationThresholds} that will specify
-     *                   when the to invoke the callback.
-     * @param executor   The {@link Executor} where the callback will be invoked on.
-     * @param listener   The {@link ITrustedPresentationListener} that will receive the callbacks
-     *                  when entered or exited trusted presentation per the thresholds.
-     *
-     * @hide b/287076178 un-hide with API bump
-     */
-    default void registerTrustedPresentationListener(@NonNull IBinder window,
-            @NonNull TrustedPresentationThresholds thresholds,  @NonNull Executor executor,
-            @NonNull Consumer<Boolean> listener) {
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * Removes a presentation listener associated with a window. If the listener was not previously
-     * registered, the call will be a noop.
-     *
-     * @hide
-     * @see #registerTrustedPresentationListener(IBinder,
-     * TrustedPresentationThresholds, Executor, Consumer)
-     */
-    default void unregisterTrustedPresentationListener(@NonNull Consumer<Boolean> listener) {
-        throw new UnsupportedOperationException();
-    }
 }
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index a7d814e..214f1ec 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -30,13 +30,9 @@
 import android.os.ServiceManager;
 import android.os.SystemProperties;
 import android.util.AndroidRuntimeException;
-import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
-import android.util.Pair;
 import android.view.inputmethod.InputMethodManager;
-import android.window.ITrustedPresentationListener;
-import android.window.TrustedPresentationThresholds;
 
 import com.android.internal.util.FastPrintWriter;
 
@@ -47,7 +43,6 @@
 import java.util.ArrayList;
 import java.util.WeakHashMap;
 import java.util.concurrent.Executor;
-import java.util.function.Consumer;
 import java.util.function.IntConsumer;
 
 /**
@@ -148,9 +143,6 @@
 
     private Runnable mSystemPropertyUpdater;
 
-    private final TrustedPresentationListener mTrustedPresentationListener =
-            new TrustedPresentationListener();
-
     private WindowManagerGlobal() {
     }
 
@@ -332,7 +324,7 @@
             final Context context = view.getContext();
             if (context != null
                     && (context.getApplicationInfo().flags
-                    & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
+                            & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
                 wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
             }
         }
@@ -490,7 +482,7 @@
                     if (who != null) {
                         WindowLeaked leak = new WindowLeaked(
                                 what + " " + who + " has leaked window "
-                                        + root.getView() + " that was originally added here");
+                                + root.getView() + " that was originally added here");
                         leak.setStackTrace(root.getLocation().getStackTrace());
                         Log.e(TAG, "", leak);
                     }
@@ -798,86 +790,6 @@
         }
     }
 
-    public void registerTrustedPresentationListener(@NonNull IBinder window,
-            @NonNull TrustedPresentationThresholds thresholds, Executor executor,
-            @NonNull Consumer<Boolean> listener) {
-        mTrustedPresentationListener.addListener(window, thresholds, listener, executor);
-    }
-
-    public void unregisterTrustedPresentationListener(@NonNull Consumer<Boolean> listener) {
-        mTrustedPresentationListener.removeListener(listener);
-    }
-
-    private final class TrustedPresentationListener extends
-            ITrustedPresentationListener.Stub {
-        private static int sId = 0;
-        private final ArrayMap<Consumer<Boolean>, Pair<Integer, Executor>> mListeners =
-                new ArrayMap<>();
-
-        private final Object mTplLock = new Object();
-
-        private void addListener(IBinder window, TrustedPresentationThresholds thresholds,
-                Consumer<Boolean> listener, Executor executor) {
-            synchronized (mTplLock) {
-                if (mListeners.containsKey(listener)) {
-                    throw new AndroidRuntimeException("Trying to add duplicate listener");
-                }
-                int id = sId++;
-                mListeners.put(listener, new Pair<>(id, executor));
-                try {
-                    WindowManagerGlobal.getWindowManagerService()
-                            .registerTrustedPresentationListener(window, this, thresholds, id);
-                } catch (RemoteException e) {
-                    e.rethrowAsRuntimeException();
-                }
-            }
-        }
-
-        private void removeListener(Consumer<Boolean> listener) {
-            synchronized (mTplLock) {
-                var removedListener = mListeners.remove(listener);
-                if (removedListener == null) {
-                    Log.i(TAG, "listener " + listener + " does not exist.");
-                    return;
-                }
-
-                try {
-                    WindowManagerGlobal.getWindowManagerService()
-                            .unregisterTrustedPresentationListener(this, removedListener.first);
-                } catch (RemoteException e) {
-                    e.rethrowAsRuntimeException();
-                }
-            }
-        }
-
-        @Override
-        public void onTrustedPresentationChanged(int[] inTrustedStateListenerIds,
-                int[] outOfTrustedStateListenerIds) {
-            ArrayList<Runnable> firedListeners = new ArrayList<>();
-            synchronized (mTplLock) {
-                mListeners.forEach((listener, idExecutorPair) -> {
-                    final var listenerId =  idExecutorPair.first;
-                    final var executor = idExecutorPair.second;
-                    for (int id : inTrustedStateListenerIds) {
-                        if (listenerId == id) {
-                            firedListeners.add(() -> executor.execute(
-                                    () -> listener.accept(/*presentationState*/true)));
-                        }
-                    }
-                    for (int id : outOfTrustedStateListenerIds) {
-                        if (listenerId == id) {
-                            firedListeners.add(() -> executor.execute(
-                                    () -> listener.accept(/*presentationState*/false)));
-                        }
-                    }
-                });
-            }
-            for (int i = 0; i < firedListeners.size(); i++) {
-                firedListeners.get(i).run();
-            }
-        }
-    }
-
     /** @hide */
     public void addWindowlessRoot(ViewRootImpl impl) {
         synchronized (mLock) {
@@ -889,7 +801,7 @@
     public void removeWindowlessRoot(ViewRootImpl impl) {
         synchronized (mLock) {
             mWindowlessRoots.remove(impl);
-        }
+	}
     }
 
     public void setRecentsAppBehindSystemBars(boolean behindSystemBars) {
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index b4b1fde..d7b74b3 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -37,7 +37,6 @@
 import android.util.Log;
 import android.window.ITaskFpsCallback;
 import android.window.TaskFpsCallback;
-import android.window.TrustedPresentationThresholds;
 import android.window.WindowContext;
 import android.window.WindowMetricsController;
 import android.window.WindowProvider;
@@ -509,17 +508,4 @@
         }
         return false;
     }
-
-    @Override
-    public void registerTrustedPresentationListener(@NonNull IBinder window,
-            @NonNull TrustedPresentationThresholds thresholds, @NonNull Executor executor,
-            @NonNull Consumer<Boolean> listener) {
-        mGlobal.registerTrustedPresentationListener(window, thresholds, executor, listener);
-    }
-
-    @Override
-    public void unregisterTrustedPresentationListener(@NonNull Consumer<Boolean> listener) {
-        mGlobal.unregisterTrustedPresentationListener(listener);
-
-    }
 }
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index d4cfd63..fab8c77 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -737,7 +737,7 @@
          */
         public void onCancelAnimation(@AnimationType int animType) {
             final int cujType = getImeInsetsCujFromAnimation(animType);
-            if (cujType == -1) {
+            if (cujType != -1) {
                 InteractionJankMonitor.getInstance().cancel(cujType);
             }
         }
diff --git a/core/java/android/window/ITrustedPresentationListener.aidl b/core/java/android/window/ITrustedPresentationListener.aidl
deleted file mode 100644
index b33128a..0000000
--- a/core/java/android/window/ITrustedPresentationListener.aidl
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.window;
-
-/**
- * @hide
- */
-oneway interface ITrustedPresentationListener {
-    void onTrustedPresentationChanged(in int[] enteredTrustedStateIds, in int[] exitedTrustedStateIds);
-}
\ No newline at end of file
diff --git a/core/java/android/window/TrustedPresentationListener.java b/core/java/android/window/TrustedPresentationListener.java
deleted file mode 100644
index 02fd6d9..0000000
--- a/core/java/android/window/TrustedPresentationListener.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.window;
-
-/**
- * @hide
- */
-public interface TrustedPresentationListener {
-
-    void onTrustedPresentationChanged(boolean inTrustedPresentationState);
-
-}
diff --git a/core/java/android/window/TrustedPresentationThresholds.aidl b/core/java/android/window/TrustedPresentationThresholds.aidl
deleted file mode 100644
index d7088bf..0000000
--- a/core/java/android/window/TrustedPresentationThresholds.aidl
+++ /dev/null
@@ -1,3 +0,0 @@
-package android.window;
-
-parcelable TrustedPresentationThresholds;
diff --git a/core/java/android/window/TrustedPresentationThresholds.java b/core/java/android/window/TrustedPresentationThresholds.java
deleted file mode 100644
index 801d35c..0000000
--- a/core/java/android/window/TrustedPresentationThresholds.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.window;
-
-import android.annotation.FloatRange;
-import android.annotation.IntRange;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.view.SurfaceControl;
-
-import androidx.annotation.NonNull;
-
-/**
- * @hide
- */
-public final class TrustedPresentationThresholds implements Parcelable {
-    /**
-     * The min alpha the {@link SurfaceControl} is required to have to be considered inside the
-     * threshold.
-     */
-    @FloatRange(from = 0f, fromInclusive = false, to = 1f)
-    public final float mMinAlpha;
-
-    /**
-     * The min fraction of the SurfaceControl that was presented to the user to be considered
-     * inside the threshold.
-     */
-    @FloatRange(from = 0f, fromInclusive = false, to = 1f)
-    public final float mMinFractionRendered;
-
-    /**
-     * The time in milliseconds required for the {@link SurfaceControl} to be in the threshold.
-     */
-    @IntRange(from = 1)
-    public final int mStabilityRequirementMs;
-
-    private void checkValid() {
-        if (mMinAlpha <= 0 || mMinFractionRendered <= 0 || mStabilityRequirementMs < 1) {
-            throw new IllegalArgumentException(
-                    "TrustedPresentationThresholds values are invalid");
-        }
-    }
-
-    /**
-     * Creates a new TrustedPresentationThresholds.
-     *
-     * @param minAlpha               The min alpha the {@link SurfaceControl} is required to
-     *                               have to be considered inside the
-     *                               threshold.
-     * @param minFractionRendered    The min fraction of the SurfaceControl that was presented
-     *                               to the user to be considered
-     *                               inside the threshold.
-     * @param stabilityRequirementMs The time in milliseconds required for the
-     *                               {@link SurfaceControl} to be in the threshold.
-     */
-    public TrustedPresentationThresholds(
-            @FloatRange(from = 0f, fromInclusive = false, to = 1f) float minAlpha,
-            @FloatRange(from = 0f, fromInclusive = false, to = 1f) float minFractionRendered,
-            @IntRange(from = 1) int stabilityRequirementMs) {
-        this.mMinAlpha = minAlpha;
-        this.mMinFractionRendered = minFractionRendered;
-        this.mStabilityRequirementMs = stabilityRequirementMs;
-        checkValid();
-    }
-
-    @Override
-    public String toString() {
-        return "TrustedPresentationThresholds { "
-                + "minAlpha = " + mMinAlpha + ", "
-                + "minFractionRendered = " + mMinFractionRendered + ", "
-                + "stabilityRequirementMs = " + mStabilityRequirementMs
-                + " }";
-    }
-
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeFloat(mMinAlpha);
-        dest.writeFloat(mMinFractionRendered);
-        dest.writeInt(mStabilityRequirementMs);
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    /**
-     * @hide
-     */
-    TrustedPresentationThresholds(@NonNull Parcel in) {
-        mMinAlpha = in.readFloat();
-        mMinFractionRendered = in.readFloat();
-        mStabilityRequirementMs = in.readInt();
-
-        checkValid();
-    }
-
-    /**
-     * @hide
-     */
-    public static final @NonNull Creator<TrustedPresentationThresholds> CREATOR =
-            new Creator<TrustedPresentationThresholds>() {
-                @Override
-                public TrustedPresentationThresholds[] newArray(int size) {
-                    return new TrustedPresentationThresholds[size];
-                }
-
-                @Override
-                public TrustedPresentationThresholds createFromParcel(@NonNull Parcel in) {
-                    return new TrustedPresentationThresholds(in);
-                }
-            };
-}
diff --git a/core/java/android/window/flags/wallpaper_manager.aconfig b/core/java/android/window/flags/wallpaper_manager.aconfig
index 09be0cf..f03c993 100644
--- a/core/java/android/window/flags/wallpaper_manager.aconfig
+++ b/core/java/android/window/flags/wallpaper_manager.aconfig
@@ -5,4 +5,11 @@
     namespace: "wear_frameworks"
     description: "Allow out of focus process to update wallpaper complications"
     bug: "271132915"
-}
\ No newline at end of file
+}
+
+flag {
+  name: "multi_crop"
+  namespace: "systemui"
+  description: "Support storing different wallpaper crops for different display dimensions. Only effective after rebooting."
+  bug: "281648899"
+}
diff --git a/core/java/com/android/internal/protolog/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java
index 8c2a525..4bb7c33 100644
--- a/core/java/com/android/internal/protolog/ProtoLogGroup.java
+++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java
@@ -93,7 +93,6 @@
     WM_DEBUG_DREAM(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, Consts.TAG_WM),
 
     WM_DEBUG_DIMMER(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM),
-    WM_DEBUG_TPL(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM),
     TEST_GROUP(true, true, false, "WindowManagerProtoLogTest");
 
     private final boolean mEnabled;
diff --git a/core/jni/com_android_internal_content_om_OverlayManagerImpl.cpp b/core/jni/com_android_internal_content_om_OverlayManagerImpl.cpp
index 0c39a69..358531a 100644
--- a/core/jni/com_android_internal_content_om_OverlayManagerImpl.cpp
+++ b/core/jni/com_android_internal_content_om_OverlayManagerImpl.cpp
@@ -46,6 +46,7 @@
     jfieldID configuration;
     jfieldID binaryDataOffset;
     jfieldID binaryDataSize;
+    jfieldID isNinePatch;
 } gFabricatedOverlayInternalEntryOffsets;
 
 static struct parcel_file_descriptor_offsets_t {
@@ -288,13 +289,16 @@
                 env->GetLongField(entry, gFabricatedOverlayInternalEntryOffsets.binaryDataOffset);
         const auto data_size =
                 env->GetLongField(entry, gFabricatedOverlayInternalEntryOffsets.binaryDataSize);
+        const auto nine_patch =
+                env->GetBooleanField(entry, gFabricatedOverlayInternalEntryOffsets.isNinePatch);
         entries_params.push_back(
                 FabricatedOverlayEntryParameters{resourceName.c_str(), (DataType)dataType,
                                                  (DataValue)data,
                                                  string_data.value_or(std::string()), binary_data,
                                                  static_cast<off64_t>(data_offset),
                                                  static_cast<size_t>(data_size),
-                                                 configuration.value_or(std::string())});
+                                                 configuration.value_or(std::string()),
+                                                 static_cast<bool>(nine_patch)});
         ALOGV("resourceName = %s, dataType = 0x%08x, data = 0x%08x, dataString = %s,"
               " binaryData = %d, configuration = %s",
               resourceName.c_str(), dataType, data, string_data.value_or(std::string()).c_str(),
@@ -455,6 +459,9 @@
     gFabricatedOverlayInternalEntryOffsets.binaryDataSize =
             GetFieldIDOrDie(env, gFabricatedOverlayInternalEntryOffsets.classObject,
                             "binaryDataSize", "J");
+    gFabricatedOverlayInternalEntryOffsets.isNinePatch =
+            GetFieldIDOrDie(env, gFabricatedOverlayInternalEntryOffsets.classObject, "isNinePatch",
+                            "Z");
 
     jclass parcelFileDescriptorClass =
             android::FindClassOrDie(env, "android/os/ParcelFileDescriptor");
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index ba1f392..1229453 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -693,6 +693,16 @@
         -->
     </integer-array>
 
+    <!-- The device states (supplied by DeviceStateManager) that should be treated as concurrent
+    display state. Default is empty. -->
+    <integer-array name="config_concurrentDisplayDeviceStates">
+        <!-- Example:
+        <item>0</item>
+        <item>1</item>
+        <item>2</item>
+        -->
+    </integer-array>
+
     <!-- Indicates whether the window manager reacts to half-fold device states by overriding
      rotation. -->
     <bool name="config_windowManagerHalfFoldAutoRotateOverride">false</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7787c5d..93aacdf 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4142,6 +4142,7 @@
   <java-symbol type="array" name="config_foldedDeviceStates" />
   <java-symbol type="array" name="config_halfFoldedDeviceStates" />
   <java-symbol type="array" name="config_rearDisplayDeviceStates" />
+  <java-symbol type="array" name="config_concurrentDisplayDeviceStates" />
   <java-symbol type="bool" name="config_windowManagerHalfFoldAutoRotateOverride" />
   <java-symbol type="bool" name="config_windowManagerPauseRotationWhenUnfolding" />
   <java-symbol type="integer" name="config_pauseRotationWhenUnfolding_hingeEventTimeout" />
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 3cf28c9..69a6e6d 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -535,6 +535,8 @@
         <!-- Permission required for CTS test IntentRedirectionTest -->
         <permission name="android.permission.QUERY_CLONED_APPS"/>
         <permission name="android.permission.GET_BINDING_UID_IMPORTANCE"/>
+        <!-- Permission required for CTS test NotificationManagerZenTest -->
+        <permission name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" />
     </privapp-permissions>
 
     <privapp-permissions package="com.android.statementservice">
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 1912821..2237ba1 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -595,12 +595,6 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "-1518132958": {
-      "message": "fractionRendered boundsOverSource=%f",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_TPL",
-      "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
-    },
     "-1517908912": {
       "message": "requestScrollCapture: caught exception dispatching to window.token=%s",
       "level": "WARN",
@@ -967,12 +961,6 @@
       "group": "WM_DEBUG_CONTENT_RECORDING",
       "at": "com\/android\/server\/wm\/ContentRecorder.java"
     },
-    "-1209762265": {
-      "message": "Registering listener=%s with id=%d for window=%s with %s",
-      "level": "DEBUG",
-      "group": "WM_DEBUG_TPL",
-      "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
-    },
     "-1209252064": {
       "message": "Clear animatingExit: reason=clearAnimatingFlags win=%s",
       "level": "DEBUG",
@@ -1345,12 +1333,6 @@
       "group": "WM_DEBUG_WINDOW_TRANSITIONS",
       "at": "com\/android\/server\/wm\/Transition.java"
     },
-    "-888703350": {
-      "message": "Skipping %s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_TPL",
-      "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
-    },
     "-883738232": {
       "message": "Adding more than one toast window for UID at a time.",
       "level": "WARN",
@@ -2821,12 +2803,6 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "360319850": {
-      "message": "fractionRendered scale=%f",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_TPL",
-      "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
-    },
     "364992694": {
       "message": "freezeDisplayRotation: current rotation=%d, new rotation=%d, caller=%s",
       "level": "VERBOSE",
@@ -3007,12 +2983,6 @@
       "group": "WM_DEBUG_BACK_PREVIEW",
       "at": "com\/android\/server\/wm\/BackNavigationController.java"
     },
-    "532771960": {
-      "message": "Adding untrusted state listener=%s with id=%d",
-      "level": "DEBUG",
-      "group": "WM_DEBUG_TPL",
-      "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
-    },
     "535103992": {
       "message": "Wallpaper may change!  Adjusting",
       "level": "VERBOSE",
@@ -3091,12 +3061,6 @@
       "group": "WM_DEBUG_DREAM",
       "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
     },
-    "605179032": {
-      "message": "checkIfInThreshold fractionRendered=%f alpha=%f currTimeMs=%d",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_TPL",
-      "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
-    },
     "608694300": {
       "message": "  NEW SURFACE SESSION %s",
       "level": "INFO",
@@ -3325,12 +3289,6 @@
       "group": "WM_SHOW_TRANSACTIONS",
       "at": "com\/android\/server\/wm\/WindowState.java"
     },
-    "824532141": {
-      "message": "lastState=%s newState=%s alpha=%f minAlpha=%f fractionRendered=%f minFractionRendered=%f",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_TPL",
-      "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
-    },
     "829434921": {
       "message": "Draw state now committed in %s",
       "level": "VERBOSE",
@@ -3625,12 +3583,6 @@
       "group": "WM_SHOW_SURFACE_ALLOC",
       "at": "com\/android\/server\/wm\/ScreenRotationAnimation.java"
     },
-    "1090378847": {
-      "message": "Checking %d windows",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_TPL",
-      "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
-    },
     "1100065297": {
       "message": "Attempted to get IME policy of a display that does not exist: %d",
       "level": "WARN",
@@ -3763,12 +3715,6 @@
       "group": "WM_DEBUG_FOCUS",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
-    "1251721200": {
-      "message": "unregister failed, couldn't find deathRecipient for %s with id=%d",
-      "level": "ERROR",
-      "group": "WM_DEBUG_TPL",
-      "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
-    },
     "1252594551": {
       "message": "Window types in WindowContext and LayoutParams.type should match! Type from LayoutParams is %d, but type from WindowContext is %d",
       "level": "WARN",
@@ -3907,12 +3853,6 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/TaskDisplayArea.java"
     },
-    "1382634842": {
-      "message": "Unregistering listener=%s with id=%d",
-      "level": "DEBUG",
-      "group": "WM_DEBUG_TPL",
-      "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
-    },
     "1393721079": {
       "message": "Starting remote display change: from [rot = %d], to [%dx%d, rot = %d]",
       "level": "VERBOSE",
@@ -3961,12 +3901,6 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "1445704347": {
-      "message": "coveredRegionsAbove updated with %s frame:%s region:%s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_TPL",
-      "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
-    },
     "1448683958": {
       "message": "Override pending remote transitionSet=%b adapter=%s",
       "level": "INFO",
@@ -4267,12 +4201,6 @@
       "group": "WM_DEBUG_RECENTS_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RecentsAnimation.java"
     },
-    "1786463281": {
-      "message": "Adding trusted state listener=%s with id=%d",
-      "level": "DEBUG",
-      "group": "WM_DEBUG_TPL",
-      "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
-    },
     "1789321832": {
       "message": "Then token:%s is invalid. It might be removed",
       "level": "WARN",
@@ -4447,12 +4375,6 @@
       "group": "WM_DEBUG_TASKS",
       "at": "com\/android\/server\/wm\/RootWindowContainer.java"
     },
-    "1955470028": {
-      "message": "computeFractionRendered: visibleRegion=%s screenBounds=%s contentSize=%s scale=%f,%f",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_TPL",
-      "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
-    },
     "1964565370": {
       "message": "Starting remote animation",
       "level": "INFO",
@@ -4737,9 +4659,6 @@
     "WM_DEBUG_TASKS": {
       "tag": "WindowManager"
     },
-    "WM_DEBUG_TPL": {
-      "tag": "WindowManager"
-    },
     "WM_DEBUG_WALLPAPER": {
       "tag": "WindowManager"
     },
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index 5d16196..662a5c4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -242,6 +242,18 @@
         return mDeviceConfig.isLandscape();
     }
 
+    /**
+     * On large screen (not small tablet), while in portrait, expanded bubbles are aligned to
+     * the bottom of the screen.
+     *
+     * @return whether bubbles are bottom aligned while expanded
+     */
+    public boolean areBubblesBottomAligned() {
+        return isLargeScreen()
+                && !mDeviceConfig.isSmallTablet()
+                && !isLandscape();
+    }
+
     /** @return whether the screen is considered large. */
     public boolean isLargeScreen() {
         return mDeviceConfig.isLargeScreen();
@@ -417,7 +429,10 @@
                 - bottomPadding;
     }
 
-    private int getExpandedViewHeightForLargeScreen() {
+    /**
+     * Returns the height to use for the expanded view when showing on a large screen.
+     */
+    public int getExpandedViewHeightForLargeScreen() {
         // the expanded view height on large tablets is calculated based on the shortest screen
         // size and is the same in both portrait and landscape
         int maxVerticalInset = Math.max(mInsets.top, mInsets.bottom);
@@ -460,13 +475,21 @@
         boolean isOverflow = bubble == null || BubbleOverflow.KEY.equals(bubble.getKey());
         float expandedViewHeight = getExpandedViewHeight(bubble);
         float topAlignment = getExpandedViewYTopAligned();
+        int manageButtonHeight =
+                isOverflow ? mExpandedViewPadding : mManageButtonHeightIncludingMargins;
+
+        // On largescreen portrait bubbles are bottom aligned.
+        if (areBubblesBottomAligned() && expandedViewHeight == MAX_HEIGHT) {
+            return mPositionRect.bottom - manageButtonHeight
+                    - getExpandedViewHeightForLargeScreen() - mPointerWidth;
+        }
+
         if (!showBubblesVertically() || expandedViewHeight == MAX_HEIGHT) {
             // Top-align when bubbles are shown at the top or are max size.
             return topAlignment;
         }
+
         // If we're here, we're showing vertically & developer has made height less than maximum.
-        int manageButtonHeight =
-                isOverflow ? mExpandedViewPadding : mManageButtonHeightIncludingMargins;
         float pointerPosition = getPointerPosition(bubblePosition);
         float bottomIfCentered = pointerPosition + (expandedViewHeight / 2) + manageButtonHeight;
         float topIfCentered = pointerPosition - (expandedViewHeight / 2);
@@ -524,14 +547,8 @@
             // Last bubble has screen index 0 and first bubble has max screen index value.
             onScreenIndex = state.numberOfBubbles - 1 - index;
         }
-
         final float positionInRow = onScreenIndex * (mBubbleSize + mSpacingBetweenBubbles);
-        final float expandedStackSize = getExpandedStackSize(state.numberOfBubbles);
-        final float centerPosition = showBubblesVertically
-                ? mPositionRect.centerY()
-                : mPositionRect.centerX();
-        // alignment - centered on the edge
-        final float rowStart = centerPosition - (expandedStackSize / 2f);
+        final float rowStart = getBubbleRowStart(state);
         float x;
         float y;
         if (showBubblesVertically) {
@@ -557,6 +574,25 @@
         return new PointF(x, y);
     }
 
+    private float getBubbleRowStart(BubbleStackView.StackViewState state) {
+        final float expandedStackSize = getExpandedStackSize(state.numberOfBubbles);
+        final float rowStart;
+        if (areBubblesBottomAligned()) {
+            final float expandedViewHeight = getExpandedViewHeightForLargeScreen();
+            final float expandedViewBottom = mScreenRect.bottom
+                    - Math.max(mInsets.bottom, mInsets.top)
+                    - mManageButtonHeight - mPointerWidth;
+            final float expandedViewCenter = expandedViewBottom - (expandedViewHeight / 2f);
+            rowStart = expandedViewCenter - (expandedStackSize / 2f);
+        } else {
+            final float centerPosition = showBubblesVertically()
+                    ? mPositionRect.centerY()
+                    : mPositionRect.centerX();
+            rowStart = centerPosition - (expandedStackSize / 2f);
+        }
+        return rowStart;
+    }
+
     /**
      * Returns the position of the bubble on-screen when the stack is expanded and the IME
      * is showing.
@@ -577,9 +613,8 @@
         final float bottomHeight = getImeHeight() + mInsets.bottom + (mSpacingBetweenBubbles * 2);
         final float bottomInset = mScreenRect.bottom - bottomHeight;
         final float expandedStackSize = getExpandedStackSize(state.numberOfBubbles);
-        final float centerPosition = mPositionRect.centerY();
-        final float rowBottom = centerPosition + (expandedStackSize / 2f);
-        final float rowTop = centerPosition - (expandedStackSize / 2f);
+        final float rowTop = getBubbleRowStart(state);
+        final float rowBottom = rowTop + expandedStackSize;
         float rowTopForIme = rowTop;
         if (rowBottom > bottomInset) {
             // We overlap with IME, must shift the bubbles
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
index e734300..49db8d9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
@@ -26,11 +26,12 @@
 
 /** Helper utility class of methods and constants that are available to be imported in Launcher. */
 public class SplitScreenConstants {
-    /**
-     * Duration used for every split fade-in or fade-out.
-     */
+    /** Duration used for every split fade-in or fade-out. */
     public static final int FADE_DURATION = 133;
 
+    /** Key for passing in widget intents when invoking split from launcher workspace. */
+    public static final String KEY_EXTRA_WIDGET_INTENT = "key_extra_widget_intent";
+
     ///////////////
     // IMPORTANT for the following SPLIT_POSITION and SNAP_TO constants:
     // These int values must not be changed -- they are persisted to user-defined app pairs, and
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
index 162ce19..a31a773 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -40,6 +40,7 @@
 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 android.app.ActivityOptions;
 import android.app.ActivityTaskManager;
 import android.app.PendingIntent;
 import android.content.ActivityNotFoundException;
@@ -246,8 +247,15 @@
             @SplitPosition int position) {
         final boolean isTask = description.hasMimeType(MIMETYPE_APPLICATION_TASK);
         final boolean isShortcut = description.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT);
-        final Bundle opts = intent.hasExtra(EXTRA_ACTIVITY_OPTIONS)
-                ? intent.getBundleExtra(EXTRA_ACTIVITY_OPTIONS) : new Bundle();
+        final ActivityOptions baseActivityOpts = ActivityOptions.makeBasic();
+        baseActivityOpts.setDisallowEnterPictureInPictureWhileLaunching(true);
+        final Bundle opts = baseActivityOpts.toBundle();
+        if (intent.hasExtra(EXTRA_ACTIVITY_OPTIONS)) {
+            opts.putAll(intent.getBundleExtra(EXTRA_ACTIVITY_OPTIONS));
+        }
+        // Put BAL flags to avoid activity start aborted.
+        opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, true);
+        opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, true);
         final UserHandle user = intent.getParcelableExtra(EXTRA_USER);
 
         if (isTask) {
@@ -259,9 +267,6 @@
             mStarter.startShortcut(packageName, id, position, opts, user);
         } else {
             final PendingIntent launchIntent = intent.getParcelableExtra(EXTRA_PENDING_INTENT);
-            // Put BAL flags to avoid activity start aborted.
-            opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, true);
-            opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, true);
             mStarter.startIntent(launchIntent, user.getIdentifier(), null /* fillIntent */,
                     position, opts);
         }
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 37b24e5..56f1c78 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
@@ -25,6 +25,7 @@
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
 
 import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.common.split.SplitScreenConstants.KEY_EXTRA_WIDGET_INTENT;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
@@ -719,10 +720,10 @@
         //       recents that hasn't launched and is not being organized
         final String packageName2 = SplitScreenUtils.getPackageName(taskId, mTaskOrganizer);
         final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer);
+        boolean setSecondIntentMultipleTask = false;
         if (samePackage(packageName1, packageName2, userId1, userId2)) {
             if (supportMultiInstancesSplit(packageName1)) {
-                fillInIntent = new Intent();
-                fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+                setSecondIntentMultipleTask = true;
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
             } else {
                 if (mRecentTasksOptional.isPresent()) {
@@ -737,6 +738,10 @@
                         Toast.LENGTH_SHORT).show();
             }
         }
+        if (options2 != null) {
+            Intent widgetIntent = options2.getParcelable(KEY_EXTRA_WIDGET_INTENT, Intent.class);
+            fillInIntent = resolveWidgetFillinIntent(widgetIntent, setSecondIntentMultipleTask);
+        }
         mStageCoordinator.startIntentAndTask(pendingIntent, fillInIntent, options1, taskId,
                 options2, splitPosition, snapPosition, remoteTransition, instanceId);
     }
@@ -787,12 +792,12 @@
                 ? ActivityOptions.fromBundle(options1) : ActivityOptions.makeBasic();
         final ActivityOptions activityOptions2 = options2 != null
                 ? ActivityOptions.fromBundle(options2) : ActivityOptions.makeBasic();
+        boolean setSecondIntentMultipleTask = false;
         if (samePackage(packageName1, packageName2, userId1, userId2)) {
             if (supportMultiInstancesSplit(packageName1)) {
                 fillInIntent1 = new Intent();
                 fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
-                fillInIntent2 = new Intent();
-                fillInIntent2.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+                setSecondIntentMultipleTask = true;
 
                 if (shortcutInfo1 != null) {
                     activityOptions1.setApplyMultipleTaskFlagForShortcut(true);
@@ -811,6 +816,10 @@
                         Toast.LENGTH_SHORT).show();
             }
         }
+        if (options2 != null) {
+            Intent widgetIntent = options2.getParcelable(KEY_EXTRA_WIDGET_INTENT, Intent.class);
+            fillInIntent2 = resolveWidgetFillinIntent(widgetIntent, setSecondIntentMultipleTask);
+        }
         mStageCoordinator.startIntents(pendingIntent1, fillInIntent1, shortcutInfo1,
                 activityOptions1.toBundle(), pendingIntent2, fillInIntent2, shortcutInfo2,
                 activityOptions2.toBundle(), splitPosition, snapPosition, remoteTransition,
@@ -916,6 +925,34 @@
         return false;
     }
 
+    /**
+     * Determines whether the widgetIntent needs to be modified if multiple tasks of its
+     * corresponding package/app are supported. There are 4 possible paths:
+     *  <li> We select a widget for second app which is the same as the first app </li>
+     *  <li> We select a widget for second app which is different from the first app </li>
+     *  <li> No widgets involved, we select a second app that is the same as first app </li>
+     *  <li> No widgets involved, we select a second app that is different from the first app
+     *       (returns null) </li>
+     *
+     * @return an {@link Intent} with the appropriate {@link Intent#FLAG_ACTIVITY_MULTIPLE_TASK}
+     *         added on or not depending on {@param launchMultipleTasks}.
+     */
+    @Nullable
+    private Intent resolveWidgetFillinIntent(@Nullable Intent widgetIntent,
+            boolean launchMultipleTasks) {
+        Intent fillInIntent2 = null;
+        if (launchMultipleTasks && widgetIntent != null) {
+            fillInIntent2 = widgetIntent;
+            fillInIntent2.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+        } else if (widgetIntent != null) {
+            fillInIntent2 = widgetIntent;
+        } else if (launchMultipleTasks) {
+            fillInIntent2 = new Intent();
+            fillInIntent2.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+        }
+        return fillInIntent2;
+    }
+
     RemoteAnimationTarget[] onGoingToRecentsLegacy(RemoteAnimationTarget[] apps) {
         if (ENABLE_SHELL_TRANSITIONS) return null;
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java
index e5ae6e5..6ebee73 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java
@@ -332,6 +332,201 @@
                 .isWithin(0.1f).of(expectedHeight);
     }
 
+    @Test
+    public void testAreBubblesBottomAligned_largeScreen_true() {
+        Insets insets = Insets.of(10, 20, 5, 15);
+        Rect screenBounds = new Rect(0, 0, 1800, 2600);
+
+        DeviceConfig deviceConfig = new ConfigBuilder()
+                .setLargeScreen()
+                .setInsets(insets)
+                .setScreenBounds(screenBounds)
+                .build();
+        mPositioner.update(deviceConfig);
+
+        assertThat(mPositioner.areBubblesBottomAligned()).isTrue();
+    }
+
+    @Test
+    public void testAreBubblesBottomAligned_largeScreen_false() {
+        Insets insets = Insets.of(10, 20, 5, 15);
+        Rect screenBounds = new Rect(0, 0, 1800, 2600);
+
+        DeviceConfig deviceConfig = new ConfigBuilder()
+                .setLargeScreen()
+                .setLandscape()
+                .setInsets(insets)
+                .setScreenBounds(screenBounds)
+                .build();
+        mPositioner.update(deviceConfig);
+
+        assertThat(mPositioner.areBubblesBottomAligned()).isFalse();
+    }
+
+    @Test
+    public void testAreBubblesBottomAligned_smallTablet_false() {
+        Insets insets = Insets.of(10, 20, 5, 15);
+        Rect screenBounds = new Rect(0, 0, 1800, 2600);
+
+        DeviceConfig deviceConfig = new ConfigBuilder()
+                .setLargeScreen()
+                .setSmallTablet()
+                .setInsets(insets)
+                .setScreenBounds(screenBounds)
+                .build();
+        mPositioner.update(deviceConfig);
+
+        assertThat(mPositioner.areBubblesBottomAligned()).isFalse();
+    }
+
+    @Test
+    public void testAreBubblesBottomAligned_phone_false() {
+        Insets insets = Insets.of(10, 20, 5, 15);
+        Rect screenBounds = new Rect(0, 0, 1800, 2600);
+
+        DeviceConfig deviceConfig = new ConfigBuilder()
+                .setInsets(insets)
+                .setScreenBounds(screenBounds)
+                .build();
+        mPositioner.update(deviceConfig);
+
+        assertThat(mPositioner.areBubblesBottomAligned()).isFalse();
+    }
+
+    @Test
+    public void testExpandedViewY_phoneLandscape() {
+        Insets insets = Insets.of(10, 20, 5, 15);
+        Rect screenBounds = new Rect(0, 0, 1800, 2600);
+
+        DeviceConfig deviceConfig = new ConfigBuilder()
+                .setLandscape()
+                .setInsets(insets)
+                .setScreenBounds(screenBounds)
+                .build();
+        mPositioner.update(deviceConfig);
+
+        Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
+        Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
+
+        // This bubble will have max height so it'll always be top aligned
+        assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
+                .isEqualTo(mPositioner.getExpandedViewYTopAligned());
+    }
+
+    @Test
+    public void testExpandedViewY_phonePortrait() {
+        Insets insets = Insets.of(10, 20, 5, 15);
+        Rect screenBounds = new Rect(0, 0, 1800, 2600);
+
+        DeviceConfig deviceConfig = new ConfigBuilder()
+                .setInsets(insets)
+                .setScreenBounds(screenBounds)
+                .build();
+        mPositioner.update(deviceConfig);
+
+        Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
+        Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
+
+        // Always top aligned in phone portrait
+        assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
+                .isEqualTo(mPositioner.getExpandedViewYTopAligned());
+    }
+
+    @Test
+    public void testExpandedViewY_smallTabletLandscape() {
+        Insets insets = Insets.of(10, 20, 5, 15);
+        Rect screenBounds = new Rect(0, 0, 1800, 2600);
+
+        DeviceConfig deviceConfig = new ConfigBuilder()
+                .setSmallTablet()
+                .setLandscape()
+                .setInsets(insets)
+                .setScreenBounds(screenBounds)
+                .build();
+        mPositioner.update(deviceConfig);
+
+        Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
+        Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
+
+        // This bubble will have max height which is always top aligned on small tablets
+        assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
+                .isEqualTo(mPositioner.getExpandedViewYTopAligned());
+    }
+
+    @Test
+    public void testExpandedViewY_smallTabletPortrait() {
+        Insets insets = Insets.of(10, 20, 5, 15);
+        Rect screenBounds = new Rect(0, 0, 1800, 2600);
+
+        DeviceConfig deviceConfig = new ConfigBuilder()
+                .setSmallTablet()
+                .setInsets(insets)
+                .setScreenBounds(screenBounds)
+                .build();
+        mPositioner.update(deviceConfig);
+
+        Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
+        Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
+
+        // This bubble will have max height which is always top aligned on small tablets
+        assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
+                .isEqualTo(mPositioner.getExpandedViewYTopAligned());
+    }
+
+    @Test
+    public void testExpandedViewY_largeScreenLandscape() {
+        Insets insets = Insets.of(10, 20, 5, 15);
+        Rect screenBounds = new Rect(0, 0, 1800, 2600);
+
+        DeviceConfig deviceConfig = new ConfigBuilder()
+                .setLargeScreen()
+                .setLandscape()
+                .setInsets(insets)
+                .setScreenBounds(screenBounds)
+                .build();
+        mPositioner.update(deviceConfig);
+
+        Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
+        Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
+
+        // This bubble will have max height which is always top aligned on landscape, large tablet
+        assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
+                .isEqualTo(mPositioner.getExpandedViewYTopAligned());
+    }
+
+    @Test
+    public void testExpandedViewY_largeScreenPortrait() {
+        Insets insets = Insets.of(10, 20, 5, 15);
+        Rect screenBounds = new Rect(0, 0, 1800, 2600);
+
+        DeviceConfig deviceConfig = new ConfigBuilder()
+                .setLargeScreen()
+                .setInsets(insets)
+                .setScreenBounds(screenBounds)
+                .build();
+        mPositioner.update(deviceConfig);
+
+        Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
+        Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
+
+        int manageButtonHeight =
+                mContext.getResources().getDimensionPixelSize(R.dimen.bubble_manage_button_height);
+        int manageButtonPlusMargin = manageButtonHeight + 2
+                * mContext.getResources().getDimensionPixelSize(
+                        R.dimen.bubble_manage_button_margin);
+        int pointerWidth = mContext.getResources().getDimensionPixelSize(
+                R.dimen.bubble_pointer_width);
+
+        final float expectedExpandedViewY = mPositioner.getAvailableRect().bottom
+                - manageButtonPlusMargin
+                - mPositioner.getExpandedViewHeightForLargeScreen()
+                - pointerWidth;
+
+        // Bubbles are bottom aligned on portrait, large tablet
+        assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
+                .isEqualTo(expectedExpandedViewY);
+    }
+
     /**
      * Calculates the Y position bubbles should be placed based on the config. Based on
      * the calculations in {@link BubblePositioner#getDefaultStartPosition()} and
diff --git a/libs/androidfw/FileStream.cpp b/libs/androidfw/FileStream.cpp
index b86c9cb..e898949 100644
--- a/libs/androidfw/FileStream.cpp
+++ b/libs/androidfw/FileStream.cpp
@@ -22,6 +22,7 @@
 
 #include "android-base/errors.h"
 #include "android-base/file.h"  // for O_BINARY
+#include "android-base/logging.h"
 #include "android-base/macros.h"
 #include "android-base/utf8.h"
 
@@ -37,9 +38,9 @@
 namespace android {
 
 FileInputStream::FileInputStream(const std::string& path, size_t buffer_capacity)
-    : buffer_capacity_(buffer_capacity) {
+    : should_close_(true), buffer_capacity_(buffer_capacity) {
   int mode = O_RDONLY | O_CLOEXEC | O_BINARY;
-  fd_.reset(TEMP_FAILURE_RETRY(::android::base::utf8::open(path.c_str(), mode)));
+  fd_ = TEMP_FAILURE_RETRY(::android::base::utf8::open(path.c_str(), mode));
   if (fd_ == -1) {
     error_ = SystemErrorCodeToString(errno);
   } else {
@@ -48,7 +49,7 @@
 }
 
 FileInputStream::FileInputStream(int fd, size_t buffer_capacity)
-    : fd_(fd), buffer_capacity_(buffer_capacity) {
+    : fd_(fd), should_close_(true), buffer_capacity_(buffer_capacity) {
   if (fd_ < 0) {
     error_ = "Bad File Descriptor";
   } else {
@@ -56,6 +57,17 @@
   }
 }
 
+FileInputStream::FileInputStream(android::base::borrowed_fd fd, size_t buffer_capacity)
+    : fd_(fd.get()), should_close_(false), buffer_capacity_(buffer_capacity) {
+
+  if (fd_ < 0) {
+    error_ = "Bad File Descriptor";
+  } else {
+    buffer_.reset(new uint8_t[buffer_capacity_]);
+  }
+}
+
+
 bool FileInputStream::Next(const void** data, size_t* size) {
   if (HadError()) {
     return false;
@@ -73,7 +85,12 @@
   ssize_t n = TEMP_FAILURE_RETRY(read(fd_, buffer_.get(), buffer_capacity_));
   if (n < 0) {
     error_ = SystemErrorCodeToString(errno);
-    fd_.reset();
+    if (fd_ != -1) {
+      if (should_close_) {
+        close(fd_);
+      }
+      fd_ = -1;
+    }
     buffer_.reset();
     return false;
   }
diff --git a/libs/androidfw/include/androidfw/BigBufferStream.h b/libs/androidfw/include/androidfw/BigBufferStream.h
index e55fe0d..c23194b 100644
--- a/libs/androidfw/include/androidfw/BigBufferStream.h
+++ b/libs/androidfw/include/androidfw/BigBufferStream.h
@@ -24,8 +24,13 @@
 class BigBufferInputStream : public KnownSizeInputStream {
  public:
   inline explicit BigBufferInputStream(const BigBuffer* buffer)
-      : buffer_(buffer), iter_(buffer->begin()) {
+      : owning_buffer_(0), buffer_(buffer), iter_(buffer->begin()) {
   }
+
+  inline explicit BigBufferInputStream(android::BigBuffer&& buffer)
+      : owning_buffer_(std::move(buffer)), buffer_(&owning_buffer_), iter_(buffer_->begin()) {
+  }
+
   virtual ~BigBufferInputStream() = default;
 
   bool Next(const void** data, size_t* size) override;
@@ -47,6 +52,7 @@
  private:
   DISALLOW_COPY_AND_ASSIGN(BigBufferInputStream);
 
+  android::BigBuffer owning_buffer_;
   const BigBuffer* buffer_;
   BigBuffer::const_iterator iter_;
   size_t offset_ = 0;
diff --git a/libs/androidfw/include/androidfw/FileStream.h b/libs/androidfw/include/androidfw/FileStream.h
index fb84a91..87c42d1 100644
--- a/libs/androidfw/include/androidfw/FileStream.h
+++ b/libs/androidfw/include/androidfw/FileStream.h
@@ -18,6 +18,7 @@
 
 #include <memory>
 #include <string>
+#include <unistd.h>
 
 #include "Streams.h"
 #include "android-base/macros.h"
@@ -35,6 +36,16 @@
   // Take ownership of `fd`.
   explicit FileInputStream(int fd, size_t buffer_capacity = kDefaultBufferCapacity);
 
+  // Take ownership of `fd`.
+  explicit FileInputStream(android::base::borrowed_fd fd,
+                           size_t buffer_capacity = kDefaultBufferCapacity);
+
+  ~FileInputStream() {
+    if (should_close_ && (fd_ != -1)) {
+      close(fd_);
+    }
+  }
+
   bool Next(const void** data, size_t* size) override;
 
   void BackUp(size_t count) override;
@@ -50,8 +61,9 @@
  private:
   DISALLOW_COPY_AND_ASSIGN(FileInputStream);
 
-  android::base::unique_fd fd_;
+  int fd_ = -1;
   std::string error_;
+  bool should_close_;
   std::unique_ptr<uint8_t[]> buffer_;
   size_t buffer_capacity_ = 0u;
   size_t buffer_offset_ = 0u;
diff --git a/libs/androidfw/include/androidfw/IDiagnostics.h b/libs/androidfw/include/androidfw/IDiagnostics.h
index 865a298..d1dda81 100644
--- a/libs/androidfw/include/androidfw/IDiagnostics.h
+++ b/libs/androidfw/include/androidfw/IDiagnostics.h
@@ -17,10 +17,15 @@
 #ifndef _ANDROID_DIAGNOSTICS_H
 #define _ANDROID_DIAGNOSTICS_H
 
+// on some systems ERROR is defined as 0 so android::base::ERROR becomes android::base::0
+// which doesn't compile. We undef it here to avoid that and because we don't ever need that def.
+#undef ERROR
+
 #include <sstream>
 #include <string>
 
 #include "Source.h"
+#include "android-base/logging.h"
 #include "android-base/macros.h"
 #include "androidfw/StringPiece.h"
 
@@ -144,6 +149,36 @@
   DISALLOW_COPY_AND_ASSIGN(NoOpDiagnostics);
 };
 
+class AndroidLogDiagnostics : public IDiagnostics {
+  public:
+    AndroidLogDiagnostics() = default;
+
+    void Log(Level level, DiagMessageActual& actual_msg) override {
+      android::base::LogSeverity severity;
+      switch (level) {
+        case Level::Error:
+          severity = android::base::ERROR;
+          break;
+
+        case Level::Warn:
+          severity = android::base::WARNING;
+          break;
+
+        case Level::Note:
+          severity = android::base::INFO;
+          break;
+      }
+      if (!actual_msg.source.path.empty()) {
+        LOG(severity) << actual_msg.source << ": " + actual_msg.message;
+      } else {
+        LOG(severity) << actual_msg.message;
+      }
+    }
+
+    DISALLOW_COPY_AND_ASSIGN(AndroidLogDiagnostics);
+};
+
+
 }  // namespace android
 
 #endif /* _ANDROID_DIAGNOSTICS_H */
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index fdb3551..c0514fd 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -1875,6 +1875,7 @@
   off64_t binary_data_offset;
   size_t binary_data_size;
   std::string configuration;
+  bool nine_patch;
 };
 
 class AssetManager2;
diff --git a/packages/CompanionDeviceManager/res/drawable-night/ic_permission_media_routing_control.xml b/packages/CompanionDeviceManager/res/drawable-night/ic_permission_media_routing_control.xml
new file mode 100644
index 0000000..a0426a3
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable-night/ic_permission_media_routing_control.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="960"
+        android:viewportHeight="960"
+        android:tint="@android:color/system_accent1_200">
+    <path android:fillColor="@android:color/system_accent1_200"
+          android:pathData="M360,840L200,840Q167,840 143.5,816.5Q120,793 120,760L120,480Q120,405 148.5,339.5Q177,274 225.5,225.5Q274,177 339.5,148.5Q405,120 480,120Q555,120 620.5,148.5Q686,177 734.5,225.5Q783,274 811.5,339.5Q840,405 840,480L840,760Q840,793 816.5,816.5Q793,840 760,840L600,840L600,520L760,520L760,480Q760,363 678.5,281.5Q597,200 480,200Q363,200 281.5,281.5Q200,363 200,480L200,520L360,520L360,840ZM280,600L200,600L200,760Q200,760 200,760Q200,760 200,760L280,760L280,600ZM680,600L680,760L760,760Q760,760 760,760Q760,760 760,760L760,600L680,600ZM280,600L280,600L200,600Q200,600 200,600Q200,600 200,600L200,600L280,600ZM680,600L760,600L760,600Q760,600 760,600Q760,600 760,600L680,600L680,600Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/drawable/ic_permission_media_routing_control.xml b/packages/CompanionDeviceManager/res/drawable/ic_permission_media_routing_control.xml
new file mode 100644
index 0000000..9a7525b
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable/ic_permission_media_routing_control.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<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/system_accent1_600"
+          android:pathData="M360,840L200,840Q167,840 143.5,816.5Q120,793 120,760L120,480Q120,405 148.5,339.5Q177,274 225.5,225.5Q274,177 339.5,148.5Q405,120 480,120Q555,120 620.5,148.5Q686,177 734.5,225.5Q783,274 811.5,339.5Q840,405 840,480L840,760Q840,793 816.5,816.5Q793,840 760,840L600,840L600,520L760,520L760,480Q760,363 678.5,281.5Q597,200 480,200Q363,200 281.5,281.5Q200,363 200,480L200,520L360,520L360,840ZM280,600L200,600L200,760Q200,760 200,760Q200,760 200,760L280,760L280,600ZM680,600L680,760L760,760Q760,760 760,760Q760,760 760,760L760,600L680,600ZM280,600L280,600L200,600Q200,600 200,600Q200,600 200,600L200,600L280,600ZM680,600L760,600L760,600Q760,600 760,600Q760,600 760,600L680,600L680,600Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index 7a6fad4..281eba6 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -149,6 +149,9 @@
     <!-- Nearby devices' permission will be granted of corresponding profile [CHAR LIMIT=30] -->
     <string name="permission_nearby_devices">Nearby devices</string>
 
+    <!-- Change media output permission will be granted to the corresponding profile [CHAR LIMIT=30] -->
+    <string name="permission_media_routing_control">Change media output</string>
+
     <!-- Storage permission will be granted of corresponding profile [CHAR LIMIT=30] -->
     <string name="permission_storage">Photos and media</string>
 
@@ -194,6 +197,9 @@
     <!-- Description of nearby_device_streaming permission of corresponding profile [CHAR LIMIT=NONE] -->
     <string name="permission_nearby_device_streaming_summary">Stream apps and other system features from your phone</string>
 
+    <!-- Description of change media output permission to be granted to the corresponding profile [CHAR LIMIT=NONE] -->
+    <string name="permission_media_routing_control_summary">Access a list of available devices and control which one streams or casts audio or video from other apps</string>
+
     <!-- The type of the device for phone [CHAR LIMIT=30] -->
     <string name="device_type" product="default">phone</string>
 
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
index 060c032..551e975 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
@@ -26,6 +26,7 @@
 import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_APP_STREAMING;
 import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_CALENDAR;
 import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_CALL_LOGS;
+import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_CHANGE_MEDIA_OUTPUT;
 import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_CONTACTS;
 import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_MICROPHONE;
 import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_NEARBY_DEVICES;
@@ -41,6 +42,8 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 
+import com.android.media.flags.Flags;
+
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
@@ -73,9 +76,15 @@
                 PERMISSION_NOTIFICATION, PERMISSION_STORAGE));
         map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING,
                 Arrays.asList(PERMISSION_NEARBY_DEVICE_STREAMING));
-        map.put(DEVICE_PROFILE_WATCH, Arrays.asList(PERMISSION_NOTIFICATION, PERMISSION_PHONE,
-                PERMISSION_CALL_LOGS, PERMISSION_SMS, PERMISSION_CONTACTS, PERMISSION_CALENDAR,
-                PERMISSION_NEARBY_DEVICES));
+        if (!Flags.enablePrivilegedRoutingForMediaRoutingControl()) {
+            map.put(DEVICE_PROFILE_WATCH, Arrays.asList(PERMISSION_NOTIFICATION, PERMISSION_PHONE,
+                    PERMISSION_CALL_LOGS, PERMISSION_SMS, PERMISSION_CONTACTS, PERMISSION_CALENDAR,
+                    PERMISSION_NEARBY_DEVICES));
+        } else {
+            map.put(DEVICE_PROFILE_WATCH, Arrays.asList(PERMISSION_NOTIFICATION, PERMISSION_PHONE,
+                    PERMISSION_CALL_LOGS, PERMISSION_SMS, PERMISSION_CONTACTS, PERMISSION_CALENDAR,
+                    PERMISSION_NEARBY_DEVICES, PERMISSION_CHANGE_MEDIA_OUTPUT));
+        }
         map.put(DEVICE_PROFILE_GLASSES, Arrays.asList(PERMISSION_NOTIFICATION, PERMISSION_PHONE,
                 PERMISSION_SMS, PERMISSION_CONTACTS, PERMISSION_MICROPHONE,
                 PERMISSION_NEARBY_DEVICES));
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java
index 7ed1816..e21aee3 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java
@@ -54,6 +54,7 @@
     static final int PERMISSION_NEARBY_DEVICE_STREAMING = 8;
     static final int PERMISSION_MICROPHONE = 9;
     static final int PERMISSION_CALL_LOGS = 10;
+    static final int PERMISSION_CHANGE_MEDIA_OUTPUT = 11;
 
     private static final Map<Integer, Integer> sTitleMap;
     static {
@@ -69,6 +70,7 @@
         map.put(PERMISSION_NEARBY_DEVICE_STREAMING, R.string.permission_nearby_device_streaming);
         map.put(PERMISSION_MICROPHONE, R.string.permission_microphone);
         map.put(PERMISSION_CALL_LOGS, R.string.permission_call_logs);
+        map.put(PERMISSION_CHANGE_MEDIA_OUTPUT, R.string.permission_media_routing_control);
         sTitleMap = unmodifiableMap(map);
     }
 
@@ -87,6 +89,7 @@
                 R.string.permission_nearby_device_streaming_summary);
         map.put(PERMISSION_MICROPHONE, R.string.permission_microphone_summary);
         map.put(PERMISSION_CALL_LOGS, R.string.permission_call_logs_summary);
+        map.put(PERMISSION_CHANGE_MEDIA_OUTPUT, R.string.permission_media_routing_control_summary);
         sSummaryMap = unmodifiableMap(map);
     }
 
@@ -105,6 +108,7 @@
                 R.drawable.ic_permission_nearby_device_streaming);
         map.put(PERMISSION_MICROPHONE, R.drawable.ic_permission_microphone);
         map.put(PERMISSION_CALL_LOGS, R.drawable.ic_permission_call_logs);
+        map.put(PERMISSION_CHANGE_MEDIA_OUTPUT, R.drawable.ic_permission_media_routing_control);
         sIconMap = unmodifiableMap(map);
     }
 
diff --git a/packages/SettingsLib/Spa/TEST_MAPPING b/packages/SettingsLib/Spa/TEST_MAPPING
index b7ce518..be1e888 100644
--- a/packages/SettingsLib/Spa/TEST_MAPPING
+++ b/packages/SettingsLib/Spa/TEST_MAPPING
@@ -9,5 +9,10 @@
     {
       "name": "SettingsSpaUnitTests"
     }
+  ],
+  "postsubmit": [
+    {
+      "name": "SpaScreenshotTests"
+    }
   ]
 }
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_actionButtons.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_actionButtons.png
index 74113d8..1a59722 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_actionButtons.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_actionButtons.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_barChart.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_barChart.png
index 0d22c6a..69017d0 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_barChart.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_barChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_footer.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_footer.png
index f77b8a7..dc2e594 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_footer.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_footer.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_imageIllustration.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_imageIllustration.png
index 9372791..7e1de6a 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_imageIllustration.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_imageIllustration.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_lineChart.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_lineChart.png
index dda9e9e..2218b5b 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_lineChart.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_lineChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_mainSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_mainSwitchPreference.png
index bf19a2c..e18f42b 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_mainSwitchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_mainSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_pieChart.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_pieChart.png
index b14e196e..21291a7 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_pieChart.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_pieChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_preference.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_preference.png
index c77f9b1..5c1a0e24 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_preference.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_preference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_progressBar.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_progressBar.png
index f513830..4003506 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_progressBar.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_progressBar.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_slider.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_slider.png
index dda6f0a..1c669a6 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_slider.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_slider.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_spinner.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_spinner.png
index 7468169..101eb0c 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_spinner.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_spinner.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_switchPreference.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_switchPreference.png
index 669f443..9cb16ef 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_switchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_switchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_twoTargetSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_twoTargetSwitchPreference.png
index 8e37cc0..20b11e3 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_twoTargetSwitchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_twoTargetSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_actionButtons.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_actionButtons.png
index b0543e0..c2ad8ad 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_actionButtons.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_actionButtons.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_barChart.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_barChart.png
index 3755928..3408cca 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_barChart.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_barChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_footer.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_footer.png
index f77b8a7..dc2e594 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_footer.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_footer.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_imageIllustration.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_imageIllustration.png
index 7800149..c3ca8cd 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_imageIllustration.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_imageIllustration.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_lineChart.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_lineChart.png
index be40959..53b92dd 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_lineChart.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_lineChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_mainSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_mainSwitchPreference.png
index 2d5894e..caa26a3 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_mainSwitchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_mainSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_pieChart.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_pieChart.png
index 2a54b9e..02a943d 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_pieChart.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_pieChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_preference.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_preference.png
index b0fc305..c1abbc2 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_preference.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_preference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_progressBar.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_progressBar.png
index 73f2407..cdb62f9 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_progressBar.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_progressBar.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_slider.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_slider.png
index 213c0b2..b3ae6b3 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_slider.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_slider.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_spinner.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_spinner.png
index 7468169..101eb0c 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_spinner.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_spinner.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_switchPreference.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_switchPreference.png
index ce7ab78..4227bb7 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_switchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_switchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_twoTargetSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_twoTargetSwitchPreference.png
index 9d92f7a..52c4341 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_twoTargetSwitchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_twoTargetSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_actionButtons.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_actionButtons.png
index 5255d14..eebac76 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_actionButtons.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_actionButtons.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_barChart.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_barChart.png
index 8f3f664..477d16c 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_barChart.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_barChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_footer.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_footer.png
index cc1de55..0dc83f9 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_footer.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_footer.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_imageIllustration.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_imageIllustration.png
index e29f26a..f32d7421 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_imageIllustration.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_imageIllustration.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_lineChart.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_lineChart.png
index 8b9bc5c..1de3b7f 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_lineChart.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_lineChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_mainSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_mainSwitchPreference.png
index b1676b3..5e5ed6a 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_mainSwitchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_mainSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_pieChart.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_pieChart.png
index 2a7b341..c133a6e 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_pieChart.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_pieChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_preference.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_preference.png
index 4845ea8..55acf95 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_preference.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_preference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_progressBar.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_progressBar.png
index 6e860d3..f72dbc2 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_progressBar.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_progressBar.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_slider.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_slider.png
index d1abaa6..5a1c85d 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_slider.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_slider.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_spinner.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_spinner.png
index c211e0e..bc8144e 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_spinner.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_spinner.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_switchPreference.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_switchPreference.png
index 9c57e33..2b0351a 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_switchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_switchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_twoTargetSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_twoTargetSwitchPreference.png
index d416a0b..bfcdb55 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_twoTargetSwitchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_twoTargetSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverAsUserFlow.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverAsUserFlow.kt
index 2c60db4..8c52d57 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverAsUserFlow.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverAsUserFlow.kt
@@ -21,13 +21,17 @@
 import android.content.Intent
 import android.content.IntentFilter
 import android.os.UserHandle
+import android.util.Log
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.catch
 import kotlinx.coroutines.flow.conflate
 import kotlinx.coroutines.flow.flowOn
 
+private const val TAG = "BroadcastReceiverAsUser"
+
 /**
  * A [BroadcastReceiver] flow for the given [intentFilter].
  */
@@ -50,4 +54,6 @@
     )
 
     awaitClose { unregisterReceiver(broadcastReceiver) }
+}.catch { e ->
+    Log.e(TAG, "Error while broadcastReceiverAsUserFlow", e)
 }.conflate().flowOn(Dispatchers.Default)
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverAsUserFlowTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverAsUserFlowTest.kt
index dfb8e22..9cb33d2 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverAsUserFlowTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverAsUserFlowTest.kt
@@ -32,9 +32,11 @@
 import org.junit.runner.RunWith
 import org.mockito.kotlin.any
 import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.doThrow
 import org.mockito.kotlin.eq
 import org.mockito.kotlin.isNull
 import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
 
 @RunWith(AndroidJUnit4::class)
 class BroadcastReceiverAsUserFlowTest {
@@ -83,6 +85,18 @@
         assertThat(onReceiveIsCalled).isTrue()
     }
 
+    @Test
+    fun broadcastReceiverAsUserFlow_unregisterReceiverThrowException_noCrash() = runBlocking {
+        context.stub {
+            on { unregisterReceiver(any()) } doThrow IllegalArgumentException()
+        }
+        val flow = context.broadcastReceiverAsUserFlow(INTENT_FILTER, USER_HANDLE)
+
+        flow.firstWithTimeoutOrNull()
+
+        assertThat(registeredBroadcastReceiver).isNotNull()
+    }
+
     private companion object {
         val USER_HANDLE: UserHandle = UserHandle.of(0)
 
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index b6a0c7b..d12d9d6 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -562,6 +562,9 @@
     <!-- Permissions required for CTS test - NotificationManagerTest -->
     <uses-permission android:name="android.permission.MANAGE_NOTIFICATION_LISTENERS" />
 
+    <!-- Permissions required for CTS test - NotificationManagerZenTest -->
+    <uses-permission android:name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" />
+
     <!-- Permissions required for CTS test - CtsContactsProviderTestCases -->
     <uses-permission android:name="android.contacts.permission.MANAGE_SIM_ACCOUNTS" />
     <uses-permission android:name="android.permission.SET_DEFAULT_ACCOUNT_FOR_CONTACTS" />
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 7cf562f..c2c5e00 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -190,6 +190,7 @@
         "androidx.room_room-runtime",
         "androidx.room_room-ktx",
         "com.google.android.material_material",
+        "device_state_flags_lib",
         "kotlinx_coroutines_android",
         "kotlinx_coroutines",
         "iconloader_base",
@@ -302,6 +303,7 @@
         "androidx.exifinterface_exifinterface",
         "androidx.room_room-runtime",
         "androidx.room_room-ktx",
+        "device_state_flags_lib",
         "kotlinx-coroutines-android",
         "kotlinx-coroutines-core",
         "kotlinx_coroutines_test",
diff --git a/packages/SystemUI/res/layout/connected_display_dialog.xml b/packages/SystemUI/res/layout/connected_display_dialog.xml
index 3f65aa7..8d7f7eb 100644
--- a/packages/SystemUI/res/layout/connected_display_dialog.xml
+++ b/packages/SystemUI/res/layout/connected_display_dialog.xml
@@ -45,6 +45,15 @@
         android:text="@string/connected_display_dialog_start_mirroring"
         android:textAppearance="@style/TextAppearance.Dialog.Title" />
 
+    <TextView
+        android:id="@+id/dual_display_warning"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        android:visibility="gone"
+        android:text="@string/connected_display_dialog_dual_display_stop_warning"
+        android:textAppearance="@style/TextAppearance.Dialog.Body" />
+
     <LinearLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index f49d2a1..7ca0b6e 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3245,6 +3245,8 @@
 
     <!--- Title of the dialog appearing when an external display is connected, asking whether to start mirroring [CHAR LIMIT=NONE]-->
     <string name="connected_display_dialog_start_mirroring">Mirror to external display?</string>
+    <!--- Body of the mirroring dialog, shown when dual display is enabled. This signals that enabling mirroring will stop concurrent displays on a foldable device. [CHAR LIMIT=NONE]-->
+    <string name="connected_display_dialog_dual_display_stop_warning">Any dual screen activity currently running will be stopped</string>
     <!--- Label of the "enable display" button of the dialog appearing when an external display is connected [CHAR LIMIT=NONE]-->
     <string name="mirror_display">Mirror display</string>
     <!--- Label of the dismiss button of the dialog appearing when an external display is connected [CHAR LIMIT=NONE]-->
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
index 341d214..dd4ca92 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
@@ -21,7 +21,6 @@
 import android.app.admin.DevicePolicyManager
 import android.content.IntentFilter
 import android.os.UserHandle
-import com.android.internal.widget.LockPatternChecker
 import com.android.internal.widget.LockPatternUtils
 import com.android.internal.widget.LockscreenCredential
 import com.android.keyguard.KeyguardSecurityModel
@@ -40,8 +39,6 @@
 import dagger.Module
 import java.util.function.Function
 import javax.inject.Inject
-import kotlin.coroutines.resume
-import kotlin.coroutines.suspendCoroutine
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -193,8 +190,8 @@
     override val throttling: MutableStateFlow<AuthenticationThrottlingModel?> =
         MutableStateFlow(null)
 
-    private val UserRepository.selectedUserId: Int
-        get() = getSelectedUserInfo().id
+    private val selectedUserId: Int
+        get() = userRepository.getSelectedUserInfo().id
 
     override val authenticationMethod: Flow<AuthenticationMethodModel> =
         combine(userRepository.selectedUserInfo, mobileConnectionsRepository.isAnySimSecure) {
@@ -233,19 +230,15 @@
 
     override suspend fun getAuthenticationMethod(): AuthenticationMethodModel {
         return withContext(backgroundDispatcher) {
-            blockingAuthenticationMethodInternal(userRepository.selectedUserId)
+            blockingAuthenticationMethodInternal(selectedUserId)
         }
     }
 
     override suspend fun getPinLength(): Int {
-        return withContext(backgroundDispatcher) {
-            val selectedUserId = userRepository.selectedUserId
-            lockPatternUtils.getPinLength(selectedUserId)
-        }
+        return withContext(backgroundDispatcher) { lockPatternUtils.getPinLength(selectedUserId) }
     }
 
     override suspend fun reportAuthenticationAttempt(isSuccessful: Boolean) {
-        val selectedUserId = userRepository.selectedUserId
         withContext(backgroundDispatcher) {
             if (isSuccessful) {
                 lockPatternUtils.reportSuccessfulPasswordAttempt(selectedUserId)
@@ -258,52 +251,32 @@
 
     override suspend fun getFailedAuthenticationAttemptCount(): Int {
         return withContext(backgroundDispatcher) {
-            val selectedUserId = userRepository.selectedUserId
             lockPatternUtils.getCurrentFailedPasswordAttempts(selectedUserId)
         }
     }
 
     override suspend fun getThrottlingEndTimestamp(): Long {
         return withContext(backgroundDispatcher) {
-            val selectedUserId = userRepository.selectedUserId
             lockPatternUtils.getLockoutAttemptDeadline(selectedUserId)
         }
     }
 
     override suspend fun setThrottleDuration(durationMs: Int) {
         withContext(backgroundDispatcher) {
-            lockPatternUtils.setLockoutAttemptDeadline(
-                userRepository.selectedUserId,
-                durationMs,
-            )
+            lockPatternUtils.setLockoutAttemptDeadline(selectedUserId, durationMs)
         }
     }
 
     override suspend fun checkCredential(
         credential: LockscreenCredential
     ): AuthenticationResultModel {
-        return suspendCoroutine { continuation ->
-            LockPatternChecker.checkCredential(
-                lockPatternUtils,
-                credential,
-                userRepository.selectedUserId,
-                object : LockPatternChecker.OnCheckCallback {
-                    override fun onChecked(matched: Boolean, throttleTimeoutMs: Int) {
-                        continuation.resume(
-                            AuthenticationResultModel(
-                                isSuccessful = matched,
-                                throttleDurationMs = throttleTimeoutMs,
-                            )
-                        )
-                    }
-
-                    override fun onCancelled() {
-                        continuation.resume(AuthenticationResultModel(isSuccessful = false))
-                    }
-
-                    override fun onEarlyMatched() = Unit
-                }
-            )
+        return withContext(backgroundDispatcher) {
+            try {
+                val matched = lockPatternUtils.checkCredential(credential, selectedUserId) {}
+                AuthenticationResultModel(isSuccessful = matched, throttleDurationMs = 0)
+            } catch (ex: LockPatternUtils.RequestThrottledException) {
+                AuthenticationResultModel(isSuccessful = false, throttleDurationMs = ex.timeoutMs)
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
index 1ba0220..eb87505 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
@@ -45,6 +45,7 @@
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 
 /**
  * Hosts application business logic related to user authentication.
@@ -227,6 +228,10 @@
             // Since authentication succeeded, we should refresh throttling to make sure that our
             // state is completely reflecting the upstream source of truth.
             refreshThrottling()
+
+            // Force a garbage collection in an attempt to erase any credentials left in memory.
+            // Do it after a 5-sec delay to avoid making the bouncer dismiss animation janky.
+            initiateGarbageCollection(delayMs = 5000)
         }
 
         return if (authenticationResult.isSuccessful) {
@@ -310,6 +315,15 @@
         }
     }
 
+    private suspend fun initiateGarbageCollection(delayMs: Long) {
+        withContext(backgroundDispatcher) {
+            delay(delayMs)
+            System.gc()
+            System.runFinalization()
+            System.gc()
+        }
+    }
+
     companion object {
         const val TAG = "AuthenticationInteractor"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
index b1a153a..24cd9b5 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
@@ -66,6 +66,8 @@
     public static final int MODE_ON = 1;
     public static final int MODE_OFF = 2;
     public static final int MODE_ESTIMATE = 3;
+    @VisibleForTesting
+    public static final long LAYOUT_TRANSITION_DURATION = 200;
 
     private final AccessorizedBatteryDrawable mDrawable;
     private final ImageView mBatteryIconView;
@@ -134,7 +136,7 @@
 
     private void setupLayoutTransition() {
         LayoutTransition transition = new LayoutTransition();
-        transition.setDuration(200);
+        transition.setDuration(LAYOUT_TRANSITION_DURATION);
 
         // Animates appearing/disappearing of the battery percentage text using fade-in/fade-out
         // and disables all other animation types
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
index ff23837..b0143f5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
@@ -60,6 +60,8 @@
     val currentRotation: StateFlow<DisplayRotation>
 }
 
+// TODO(b/296211844): This class could directly use DeviceStateRepository and DisplayRepository
+// instead.
 @SysUISingleton
 class DisplayStateRepositoryImpl
 @Inject
diff --git a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
index 65cd84b..373279c 100644
--- a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.display
 
+import com.android.systemui.display.data.repository.DeviceStateRepository
+import com.android.systemui.display.data.repository.DeviceStateRepositoryImpl
 import com.android.systemui.display.data.repository.DisplayRepository
 import com.android.systemui.display.data.repository.DisplayRepositoryImpl
 import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
@@ -32,4 +34,9 @@
     ): ConnectedDisplayInteractor
 
     @Binds fun bindsDisplayRepository(displayRepository: DisplayRepositoryImpl): DisplayRepository
+
+    @Binds
+    fun bindsDeviceStateRepository(
+        deviceStateRepository: DeviceStateRepositoryImpl
+    ): DeviceStateRepository
 }
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DeviceStateRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DeviceStateRepository.kt
new file mode 100644
index 0000000..83337f7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DeviceStateRepository.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.display.data.repository
+
+import android.content.Context
+import android.hardware.devicestate.DeviceStateManager
+import com.android.internal.R
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.stateIn
+
+interface DeviceStateRepository {
+    val state: StateFlow<DeviceState>
+
+    enum class DeviceState {
+        /** Device state in [R.array.config_foldedDeviceStates] */
+        FOLDED,
+        /** Device state in [R.array.config_halfFoldedDeviceStates] */
+        HALF_FOLDED,
+        /** Device state in [R.array.config_openDeviceStates] */
+        UNFOLDED,
+        /** Device state in [R.array.config_rearDisplayDeviceStates] */
+        REAR_DISPLAY,
+        /** Device state in [R.array.config_concurrentDisplayDeviceStates] */
+        CONCURRENT_DISPLAY,
+        /** Device state in none of the other arrays. */
+        UNKNOWN,
+    }
+}
+
+class DeviceStateRepositoryImpl
+@Inject
+constructor(
+    context: Context,
+    deviceStateManager: DeviceStateManager,
+    @Background bgScope: CoroutineScope,
+    @Background executor: Executor
+) : DeviceStateRepository {
+
+    override val state: StateFlow<DeviceState> =
+        conflatedCallbackFlow {
+                val callback =
+                    DeviceStateManager.DeviceStateCallback { state ->
+                        trySend(deviceStateToPosture(state))
+                    }
+                deviceStateManager.registerCallback(executor, callback)
+                awaitClose { deviceStateManager.unregisterCallback(callback) }
+            }
+            .stateIn(bgScope, started = SharingStarted.WhileSubscribed(), DeviceState.UNKNOWN)
+
+    private fun deviceStateToPosture(deviceStateId: Int): DeviceState {
+        return deviceStateMap.firstOrNull { (ids, _) -> deviceStateId in ids }?.deviceState
+            ?: DeviceState.UNKNOWN
+    }
+
+    private val deviceStateMap =
+        listOf(
+                R.array.config_foldedDeviceStates to DeviceState.FOLDED,
+                R.array.config_halfFoldedDeviceStates to DeviceState.HALF_FOLDED,
+                R.array.config_openDeviceStates to DeviceState.UNFOLDED,
+                R.array.config_rearDisplayDeviceStates to DeviceState.REAR_DISPLAY,
+                R.array.config_concurrentDisplayDeviceStates to DeviceState.CONCURRENT_DISPLAY,
+            )
+            .map { IdsPerDeviceState(context.resources.getIntArray(it.first).toSet(), it.second) }
+
+    private data class IdsPerDeviceState(val ids: Set<Int>, val deviceState: DeviceState)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt
index 20a9e5d..73b7a8a 100644
--- a/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt
@@ -21,6 +21,7 @@
 import android.view.Display
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.display.data.repository.DeviceStateRepository
 import com.android.systemui.display.data.repository.DisplayRepository
 import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
 import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State
@@ -55,6 +56,9 @@
     /** Pending display that can be enabled to be used by the system. */
     val pendingDisplay: Flow<PendingDisplay?>
 
+    /** Pending display that can be enabled to be used by the system. */
+    val concurrentDisplaysInProgress: Flow<Boolean>
+
     /** Possible connected display state. */
     enum class State {
         DISCONNECTED,
@@ -84,6 +88,7 @@
     private val virtualDeviceManager: VirtualDeviceManager,
     keyguardRepository: KeyguardRepository,
     displayRepository: DisplayRepository,
+    deviceStateRepository: DeviceStateRepository,
     @Background backgroundCoroutineDispatcher: CoroutineDispatcher,
 ) : ConnectedDisplayInteractor {
 
@@ -128,9 +133,16 @@
             }
         }
 
+    override val concurrentDisplaysInProgress: Flow<Boolean> =
+        deviceStateRepository.state
+            .map { it == DeviceStateRepository.DeviceState.CONCURRENT_DISPLAY }
+            .distinctUntilChanged()
+            .flowOn(backgroundCoroutineDispatcher)
+
     private fun DisplayRepository.PendingDisplay.toInteractorPendingDisplay(): PendingDisplay =
         object : PendingDisplay {
             override suspend fun enable() = this@toInteractorPendingDisplay.enable()
+
             override suspend fun ignore() = this@toInteractorPendingDisplay.ignore()
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
index d500d1c2..c0a873a 100644
--- a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
@@ -37,11 +37,13 @@
     private val onCancelMirroring: View.OnClickListener,
     private val navbarBottomInsetsProvider: () -> Int,
     configurationController: ConfigurationController? = null,
+    private val showConcurrentDisplayInfo: Boolean = false,
     theme: Int = R.style.Theme_SystemUI_Dialog,
 ) : SystemUIBottomSheetDialog(context, configurationController, theme) {
 
     private lateinit var mirrorButton: TextView
     private lateinit var dismissButton: TextView
+    private lateinit var dualDisplayWarning: TextView
     private var enabledPressed = false
 
     override fun onCreate(savedInstanceState: Bundle?) {
@@ -56,6 +58,11 @@
         dismissButton =
             requireViewById<TextView>(R.id.cancel).apply { setOnClickListener(onCancelMirroring) }
 
+        dualDisplayWarning =
+            requireViewById<TextView>(R.id.dual_display_warning).apply {
+                visibility = if (showConcurrentDisplayInfo) View.VISIBLE else View.GONE
+            }
+
         setOnDismissListener {
             if (!enabledPressed) {
                 onCancelMirroring.onClick(null)
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 19b4d22..10aa703 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
@@ -17,6 +17,7 @@
 
 import android.app.Dialog
 import android.content.Context
+import com.android.server.policy.feature.flags.Flags
 import com.android.systemui.biometrics.Utils
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
@@ -28,8 +29,9 @@
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.launch
 
 /**
@@ -44,25 +46,33 @@
     private val connectedDisplayInteractor: ConnectedDisplayInteractor,
     @Application private val scope: CoroutineScope,
     @Background private val bgDispatcher: CoroutineDispatcher,
-    private val configurationController: ConfigurationController
+    private val configurationController: ConfigurationController,
 ) {
 
     private var dialog: Dialog? = null
 
     /** Starts listening for pending displays. */
     fun init() {
-        connectedDisplayInteractor.pendingDisplay
-            .onEach { pendingDisplay ->
+        val pendingDisplayFlow = connectedDisplayInteractor.pendingDisplay
+        val concurrentDisplaysInProgessFlow =
+            if (Flags.enableDualDisplayBlocking()) {
+                connectedDisplayInteractor.concurrentDisplaysInProgress
+            } else {
+                flow { emit(false) }
+            }
+        pendingDisplayFlow
+            .combine(concurrentDisplaysInProgessFlow) { pendingDisplay, concurrentDisplaysInProgress
+                ->
                 if (pendingDisplay == null) {
                     hideDialog()
                 } else {
-                    showDialog(pendingDisplay)
+                    showDialog(pendingDisplay, concurrentDisplaysInProgress)
                 }
             }
             .launchIn(scope)
     }
 
-    private fun showDialog(pendingDisplay: PendingDisplay) {
+    private fun showDialog(pendingDisplay: PendingDisplay, concurrentDisplaysInProgess: Boolean) {
         hideDialog()
         dialog =
             MirroringConfirmationDialog(
@@ -77,6 +87,7 @@
                     },
                     navbarBottomInsetsProvider = { Utils.getNavbarInsets(context).bottom },
                     configurationController,
+                    showConcurrentDisplayInfo = concurrentDisplaysInProgess
                 )
                 .apply { show() }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 11d6507..e8ceabf 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -438,12 +438,6 @@
     val LOCKSCREEN_ENABLE_LANDSCAPE =
             unreleasedFlag("lockscreen.enable_landscape")
 
-    // TODO(b/281648899): Tracking bug
-    @Keep
-    @JvmField
-    val WALLPAPER_MULTI_CROP =
-        sysPropBooleanFlag("persist.wm.debug.wallpaper_multi_crop", default = false)
-
     // 1200 - predictive back
     @Keep
     @JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index 8397caa..dd194eaa 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -794,6 +794,13 @@
 
     /** update Qs height state */
     public void setExpansionHeight(float height) {
+        // TODO(b/277909752): remove below log when bug is fixed
+        if (mSplitShadeEnabled && mShadeExpandedFraction == 1.0f && height == 0
+                && mBarState == SHADE) {
+            Log.wtf(TAG,
+                    "setting QS height to 0 in split shade while shade is open(ing). "
+                            + "Value of isExpandImmediate() = " + isExpandImmediate());
+        }
         int maxHeight = getMaxExpansionHeight();
         height = Math.min(Math.max(
                 height, getMinExpansionHeight()), maxHeight);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 6cb079a..b6d4ded 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -139,7 +139,6 @@
     private static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30);
     private static final SourceType BASE_VALUE = SourceType.from("BaseValue");
     private static final SourceType FROM_PARENT = SourceType.from("FromParent(ENR)");
-    private static final SourceType PINNED = SourceType.from("Pinned");
 
     // We don't correctly track dark mode until the content views are inflated, so always update
     // the background on first content update just in case it happens to be during a theme change.
@@ -147,7 +146,6 @@
     private boolean mIsSnoozed;
     private boolean mShowSnooze = false;
     private boolean mIsFaded;
-    private boolean mAnimatePinnedRoundness = false;
 
     /**
      * Listener for when {@link ExpandableNotificationRow} is laid out.
@@ -1053,14 +1051,6 @@
         if (isAboveShelf() != wasAboveShelf) {
             mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf);
         }
-        if (pinned) {
-            // Should be animated if someone explicitly set it to 0 and the row is shown.
-            boolean animated = mAnimatePinnedRoundness && isShown();
-            requestRoundness(/* top = */ 1f, /* bottom = */ 1f, PINNED, animated);
-        } else {
-            requestRoundnessReset(PINNED);
-            mAnimatePinnedRoundness = true;
-        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
index d635f89..bf0c823 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
@@ -29,7 +29,7 @@
             TAG,
             LogLevel.ERROR,
             { str1 = logKey(key) },
-            { "Heads up view disappearing $str1 for ANIMATION_TYPE_ADD" }
+            { "Heads up view appearing $str1 for ANIMATION_TYPE_ADD" }
         )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DeviceStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DeviceStateRepositoryTest.kt
new file mode 100644
index 0000000..21b8aca
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DeviceStateRepositoryTest.kt
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.display.data.repository
+
+import android.hardware.devicestate.DeviceStateManager
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.FlowValue
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.kotlinArgumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.runner.RunWith
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class DeviceStateRepositoryTest : SysuiTestCase() {
+
+    private val deviceStateManager = mock<DeviceStateManager>()
+    private val deviceStateManagerListener =
+        kotlinArgumentCaptor<DeviceStateManager.DeviceStateCallback>()
+
+    private val testScope = TestScope(UnconfinedTestDispatcher())
+    private val fakeExecutor = FakeExecutor(FakeSystemClock())
+
+    private lateinit var deviceStateRepository: DeviceStateRepositoryImpl
+
+    @Before
+    fun setup() {
+        mContext.orCreateTestableResources.apply {
+            addOverride(R.array.config_foldedDeviceStates, listOf(TEST_FOLDED).toIntArray())
+            addOverride(R.array.config_halfFoldedDeviceStates, TEST_HALF_FOLDED.toIntArray())
+            addOverride(R.array.config_openDeviceStates, TEST_UNFOLDED.toIntArray())
+            addOverride(R.array.config_rearDisplayDeviceStates, TEST_REAR_DISPLAY.toIntArray())
+            addOverride(
+                R.array.config_concurrentDisplayDeviceStates,
+                TEST_CONCURRENT_DISPLAY.toIntArray()
+            )
+        }
+        deviceStateRepository =
+            DeviceStateRepositoryImpl(
+                mContext,
+                deviceStateManager,
+                TestScope(UnconfinedTestDispatcher()),
+                fakeExecutor
+            )
+
+        // It should register only after there are clients collecting the flow
+        verify(deviceStateManager, never()).registerCallback(any(), any())
+    }
+
+    @Test
+    fun folded_receivesFoldedState() =
+        testScope.runTest {
+            val state = displayState()
+
+            deviceStateManagerListener.value.onStateChanged(TEST_FOLDED)
+
+            assertThat(state()).isEqualTo(DeviceState.FOLDED)
+        }
+
+    @Test
+    fun halfFolded_receivesHalfFoldedState() =
+        testScope.runTest {
+            val state = displayState()
+
+            deviceStateManagerListener.value.onStateChanged(TEST_HALF_FOLDED)
+
+            assertThat(state()).isEqualTo(DeviceState.HALF_FOLDED)
+        }
+
+    @Test
+    fun unfolded_receivesUnfoldedState() =
+        testScope.runTest {
+            val state = displayState()
+
+            deviceStateManagerListener.value.onStateChanged(TEST_UNFOLDED)
+
+            assertThat(state()).isEqualTo(DeviceState.UNFOLDED)
+        }
+
+    @Test
+    fun rearDisplay_receivesRearDisplayState() =
+        testScope.runTest {
+            val state = displayState()
+
+            deviceStateManagerListener.value.onStateChanged(TEST_REAR_DISPLAY)
+
+            assertThat(state()).isEqualTo(DeviceState.REAR_DISPLAY)
+        }
+
+    @Test
+    fun concurrentDisplay_receivesConcurrentDisplayState() =
+        testScope.runTest {
+            val state = displayState()
+
+            deviceStateManagerListener.value.onStateChanged(TEST_CONCURRENT_DISPLAY)
+
+            assertThat(state()).isEqualTo(DeviceState.CONCURRENT_DISPLAY)
+        }
+
+    @Test
+    fun unknownState_receivesUnknownState() =
+        testScope.runTest {
+            val state = displayState()
+
+            deviceStateManagerListener.value.onStateChanged(123456)
+
+            assertThat(state()).isEqualTo(DeviceState.UNKNOWN)
+        }
+
+    private fun TestScope.displayState(): FlowValue<DeviceState?> {
+        val flowValue = collectLastValue(deviceStateRepository.state)
+        verify(deviceStateManager)
+            .registerCallback(
+                any(),
+                deviceStateManagerListener.capture(),
+            )
+        return flowValue
+    }
+
+    private fun Int.toIntArray() = listOf(this).toIntArray()
+
+    private companion object {
+        // Used to fake the ids in the test. Note that there is no guarantees different devices will
+        // have the same ids (that's why the ones in this test start from 41)
+        const val TEST_FOLDED = 41
+        const val TEST_HALF_FOLDED = 42
+        const val TEST_UNFOLDED = 43
+        const val TEST_REAR_DISPLAY = 44
+        const val TEST_CONCURRENT_DISPLAY = 45
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt
index 1f18705..42b0f50 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt
@@ -28,6 +28,9 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.FlowValue
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.display.data.repository.DeviceStateRepository
+import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState.CONCURRENT_DISPLAY
+import com.android.systemui.display.data.repository.FakeDeviceStateRepository
 import com.android.systemui.display.data.repository.FakeDisplayRepository
 import com.android.systemui.display.data.repository.createPendingDisplay
 import com.android.systemui.display.data.repository.display
@@ -59,11 +62,13 @@
 
     private val fakeDisplayRepository = FakeDisplayRepository()
     private val fakeKeyguardRepository = FakeKeyguardRepository()
+    private val fakeDeviceStateRepository = FakeDeviceStateRepository()
     private val connectedDisplayStateProvider: ConnectedDisplayInteractor =
         ConnectedDisplayInteractorImpl(
             virtualDeviceManager,
             fakeKeyguardRepository,
             fakeDisplayRepository,
+            fakeDeviceStateRepository,
             UnconfinedTestDispatcher(),
         )
     private val testScope = TestScope(UnconfinedTestDispatcher())
@@ -283,6 +288,44 @@
             assertThat(pendingDisplay).isNull()
         }
 
+    @Test
+    fun concurrentDisplaysInProgress_started_returnsTrue() =
+        testScope.runTest {
+            val concurrentDisplaysInProgress =
+                collectLastValue(connectedDisplayStateProvider.concurrentDisplaysInProgress)
+
+            fakeDeviceStateRepository.emit(CONCURRENT_DISPLAY)
+
+            assertThat(concurrentDisplaysInProgress()).isTrue()
+        }
+
+    @Test
+    fun concurrentDisplaysInProgress_stopped_returnsFalse() =
+        testScope.runTest {
+            val concurrentDisplaysInProgress =
+                collectLastValue(connectedDisplayStateProvider.concurrentDisplaysInProgress)
+
+            fakeDeviceStateRepository.emit(CONCURRENT_DISPLAY)
+            fakeDeviceStateRepository.emit(DeviceStateRepository.DeviceState.UNKNOWN)
+
+            assertThat(concurrentDisplaysInProgress()).isFalse()
+        }
+
+    @Test
+    fun concurrentDisplaysInProgress_otherStates_returnsFalse() =
+        testScope.runTest {
+            val concurrentDisplaysInProgress =
+                collectLastValue(connectedDisplayStateProvider.concurrentDisplaysInProgress)
+
+            DeviceStateRepository.DeviceState.entries
+                .filter { it != CONCURRENT_DISPLAY }
+                .forEach { deviceState ->
+                    fakeDeviceStateRepository.emit(deviceState)
+
+                    assertThat(concurrentDisplaysInProgress()).isFalse()
+                }
+        }
+
     private fun TestScope.lastValue(): FlowValue<State?> =
         collectLastValue(connectedDisplayStateProvider.connectedDisplayState)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt
index bbc63f2..ae84df5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt
@@ -21,7 +21,6 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
 import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
-import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State.CONNECTED
 import com.android.systemui.privacy.PrivacyItemController
 import com.android.systemui.statusbar.policy.BatteryController
 import com.android.systemui.util.mockito.any
@@ -107,5 +106,7 @@
             get() = flow
         override val pendingDisplay: Flow<PendingDisplay?>
             get() = MutableSharedFlow<PendingDisplay>()
+        override val concurrentDisplaysInProgress: Flow<Boolean>
+            get() = TODO("Not yet implemented")
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
index da6c28a..7deee5a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
@@ -321,5 +321,7 @@
             get() = TODO("Not yet implemented")
         override val pendingDisplay: Flow<PendingDisplay?>
             get() = TODO("Not yet implemented")
+        override val concurrentDisplaysInProgress: Flow<Boolean>
+            get() = TODO("Not yet implemented")
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDeviceStateRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDeviceStateRepository.kt
new file mode 100644
index 0000000..5f6dc6e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDeviceStateRepository.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.display.data.repository
+
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+/** Fake [DeviceStateRepository] implementation for testing. */
+class FakeDeviceStateRepository : DeviceStateRepository {
+    private val flow = MutableStateFlow(DeviceStateRepository.DeviceState.UNKNOWN)
+
+    /** Emits [value] as [displays] flow value. */
+    suspend fun emit(value: DeviceStateRepository.DeviceState) = flow.emit(value)
+
+    override val state: StateFlow<DeviceStateRepository.DeviceState>
+        get() = flow
+}
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index 2902932..468b7c9 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -41,6 +41,17 @@
 android.content.IntentFilter
 android.content.UriMatcher
 
+android.content.pm.PackageInfo
+android.content.pm.ApplicationInfo
+android.content.pm.PackageItemInfo
+android.content.pm.ComponentInfo
+android.content.pm.ActivityInfo
+android.content.pm.ServiceInfo
+android.content.pm.PathPermission
+android.content.pm.ProviderInfo
+android.content.pm.ResolveInfo
+android.content.pm.Signature
+
 android.database.AbstractCursor
 android.database.CharArrayBuffer
 android.database.ContentObservable
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 258820a..77a5e3d 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -43,7 +43,9 @@
 import android.app.PendingIntent;
 import android.app.admin.DevicePolicyManagerInternal;
 import android.app.admin.DevicePolicyManagerInternal.OnCrossProfileWidgetProvidersChangeListener;
+import android.app.usage.Flags;
 import android.app.usage.UsageEvents;
+import android.app.usage.UsageStatsManager;
 import android.app.usage.UsageStatsManagerInternal;
 import android.appwidget.AppWidgetManager;
 import android.appwidget.AppWidgetManagerInternal;
@@ -83,6 +85,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.PersistableBundle;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -3815,14 +3818,27 @@
                 final SparseArray<String> uid2PackageName = new SparseArray<String>();
                 uid2PackageName.put(providerId.uid, packageName);
                 mAppOpsManagerInternal.updateAppWidgetVisibility(uid2PackageName, true);
-                mUsageStatsManagerInternal.reportEvent(packageName,
-                        UserHandle.getUserId(providerId.uid), UsageEvents.Event.USER_INTERACTION);
+                reportWidgetInteractionEvent(packageName, UserHandle.getUserId(providerId.uid),
+                        "tap");
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
     }
 
+    private void reportWidgetInteractionEvent(@NonNull String packageName, @UserIdInt int userId,
+            @NonNull String action) {
+        if (Flags.userInteractionTypeApi()) {
+            PersistableBundle extras = new PersistableBundle();
+            extras.putString(UsageStatsManager.EXTRA_EVENT_CATEGORY, "android.appwidget");
+            extras.putString(UsageStatsManager.EXTRA_EVENT_ACTION, action);
+            mUsageStatsManagerInternal.reportUserInteractionEvent(packageName, userId, extras);
+        } else {
+            mUsageStatsManagerInternal.reportEvent(packageName, userId,
+                    UsageEvents.Event.USER_INTERACTION);
+        }
+    }
+
     private final class CallbackHandler extends Handler {
         public static final int MSG_NOTIFY_UPDATE_APP_WIDGET = 1;
         public static final int MSG_NOTIFY_PROVIDER_CHANGED = 2;
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index 382ee6e..4bb9f4f 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -103,7 +103,7 @@
     // will be half the full timeout).
     //
     // The pre-watchdog event is similar to a full watchdog except it does not crash system server.
-    private static final int PRE_WATCHDOG_TIMEOUT_RATIO = 3;
+    private static final int PRE_WATCHDOG_TIMEOUT_RATIO = 4;
 
     // These are temporally ordered: larger values as lateness increases
     static final int COMPLETED = 0;
diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java
index e3aa161..a313bcf 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -1745,8 +1745,8 @@
 
         @Override
         public boolean setSaturationLevel(int level) {
-            final boolean hasTransformsPermission = getContext()
-                    .checkCallingPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
+            final boolean hasTransformsPermission = getContext().checkCallingOrSelfPermission(
+                    Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
                     == PackageManager.PERMISSION_GRANTED;
             final boolean hasLegacyPermission = getContext()
                     .checkCallingPermission(Manifest.permission.CONTROL_DISPLAY_SATURATION)
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 98f627c..b700785 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -1188,23 +1188,6 @@
     }
 
     /**
-     * {@link BroadcastReceiver} that is intended to listen to broadcasts sent to the system user
-     * only.
-     */
-    private final class ImmsBroadcastReceiverForSystemUser extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
-            if (Intent.ACTION_USER_ADDED.equals(action)
-                    || Intent.ACTION_USER_REMOVED.equals(action)) {
-                updateCurrentProfileIds();
-            } else {
-                Slog.w(TAG, "Unexpected intent " + intent);
-            }
-        }
-    }
-
-    /**
      * {@link BroadcastReceiver} that is intended to listen to broadcasts sent to all the users.
      */
     private final class ImmsBroadcastReceiverForAllUsers extends BroadcastReceiver {
@@ -1611,9 +1594,16 @@
         @Override
         public void onUserUnlocking(@NonNull TargetUser user) {
             // Called on ActivityManager thread.
+            SecureSettingsWrapper.onUserUnlocking(user.getUserIdentifier());
             mService.mHandler.obtainMessage(MSG_SYSTEM_UNLOCK_USER, user.getUserIdentifier(), 0)
                     .sendToTarget();
         }
+
+        @Override
+        public void onUserStarting(TargetUser user) {
+            // Called on ActivityManager thread.
+            SecureSettingsWrapper.onUserStarting(user.getUserIdentifier());
+        }
     }
 
     void onUnlockUser(@UserIdInt int userId) {
@@ -1665,6 +1655,7 @@
             @Nullable InputMethodBindingController bindingControllerForTesting) {
         mContext = context;
         mRes = context.getResources();
+        SecureSettingsWrapper.onStart(mContext);
         // TODO(b/196206770): Disallow I/O on this thread. Currently it's needed for loading
         // additional subtypes in switchUserOnHandlerLocked().
         final ServiceThread thread =
@@ -1700,7 +1691,6 @@
         // mSettings should be created before buildInputMethodListLocked
         mSettings = new InputMethodSettings(mContext, mMethodMap, userId, !mSystemReady);
 
-        updateCurrentProfileIds();
         AdditionalSubtypeUtils.load(mAdditionalSubtypeMap, userId);
         mSwitchingController =
                 InputMethodSubtypeSwitchingController.createInstanceLocked(mSettings, context);
@@ -1818,7 +1808,6 @@
         final boolean useCopyOnWriteSettings =
                 !mSystemReady || !mUserManagerInternal.isUserUnlockingOrUnlocked(newUserId);
         mSettings.switchCurrentUser(newUserId, useCopyOnWriteSettings);
-        updateCurrentProfileIds();
         // Additional subtypes should be reset when the user is changed
         AdditionalSubtypeUtils.load(mAdditionalSubtypeMap, newUserId);
         final String defaultImiId = mSettings.getSelectedInputMethod();
@@ -1869,12 +1858,6 @@
         }
     }
 
-    void updateCurrentProfileIds() {
-        mSettings.setCurrentProfileIds(
-                mUserManagerInternal.getProfileIds(mSettings.getCurrentUserId(),
-                        false /* enabledOnly */));
-    }
-
     /**
      * TODO(b/32343335): The entire systemRunning() method needs to be revisited.
      */
@@ -1921,12 +1904,6 @@
                 mMyPackageMonitor.register(mContext, null, UserHandle.ALL, true);
                 mSettingsObserver.registerContentObserverLocked(currentUserId);
 
-                final IntentFilter broadcastFilterForSystemUser = new IntentFilter();
-                broadcastFilterForSystemUser.addAction(Intent.ACTION_USER_ADDED);
-                broadcastFilterForSystemUser.addAction(Intent.ACTION_USER_REMOVED);
-                mContext.registerReceiver(new ImmsBroadcastReceiverForSystemUser(),
-                        broadcastFilterForSystemUser);
-
                 final IntentFilter broadcastFilterForAllUsers = new IntentFilter();
                 broadcastFilterForAllUsers.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
                 mContext.registerReceiverAsUser(new ImmsBroadcastReceiverForAllUsers(),
@@ -3788,8 +3765,14 @@
             mVisibilityStateComputer.mShowForced = false;
         }
 
-        // cross-profile access is always allowed here to allow profile-switching.
-        if (!mSettings.isCurrentProfile(userId)) {
+        final int currentUserId = mSettings.getCurrentUserId();
+        if (userId != currentUserId) {
+            if (ArrayUtils.contains(
+                    mUserManagerInternal.getProfileIds(currentUserId, false), userId)) {
+                // cross-profile access is always allowed here to allow profile-switching.
+                scheduleSwitchUserTaskLocked(userId, cs.mClient);
+                return InputBindResult.USER_SWITCHING;
+            }
             Slog.w(TAG, "A background user is requesting window. Hiding IME.");
             Slog.w(TAG, "If you need to impersonate a foreground user/profile from"
                     + " a background user, use EditorInfo.targetInputMethodUser with"
@@ -3799,11 +3782,6 @@
             return InputBindResult.INVALID_USER;
         }
 
-        if (userId != mSettings.getCurrentUserId()) {
-            scheduleSwitchUserTaskLocked(userId, cs.mClient);
-            return InputBindResult.USER_SWITCHING;
-        }
-
         final boolean sameWindowFocused = mCurFocusedWindow == windowToken;
         final boolean isTextEditor = (startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0;
         final boolean startInputByWinGainedFocus =
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index c661c86..b4338f6 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -21,7 +21,6 @@
 import android.annotation.UserHandleAware;
 import android.annotation.UserIdInt;
 import android.content.ComponentName;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
@@ -34,7 +33,6 @@
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.ArrayMap;
-import android.util.ArraySet;
 import android.util.IntArray;
 import android.util.Pair;
 import android.util.Printer;
@@ -51,7 +49,6 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 import java.util.function.Predicate;
 
@@ -211,36 +208,15 @@
      */
     @UserHandleAware
     public static class InputMethodSettings {
-        private final TextUtils.SimpleStringSplitter mInputMethodSplitter =
-                new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
-
-        private final TextUtils.SimpleStringSplitter mSubtypeSplitter =
-                new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
-
         @NonNull
         private Context mUserAwareContext;
-        private ContentResolver mResolver;
         private final ArrayMap<String, InputMethodInfo> mMethodMap;
 
-        /**
-         * On-memory data store to emulate when {@link #mCopyOnWrite} is {@code true}.
-         */
-        private final ArrayMap<String, String> mCopyOnWriteDataStore = new ArrayMap<>();
-
-        private static final ArraySet<String> CLONE_TO_MANAGED_PROFILE = new ArraySet<>();
-        static {
-            Settings.Secure.getCloneToManagedProfileSettings(CLONE_TO_MANAGED_PROFILE);
-        }
-
-        private static final UserManagerInternal sUserManagerInternal =
-                LocalServices.getService(UserManagerInternal.class);
-
         private boolean mCopyOnWrite = false;
         @NonNull
         private String mEnabledInputMethodsStrCache = "";
         @UserIdInt
         private int mCurrentUserId;
-        private int[] mCurrentProfileIds = new int[0];
 
         private static void buildEnabledInputMethodsSettingString(
                 StringBuilder builder, Pair<String, ArrayList<String>> ime) {
@@ -281,7 +257,6 @@
             mUserAwareContext = context.getUserId() == userId
                     ? context
                     : context.createContextAsUser(UserHandle.of(userId), 0 /* flags */);
-            mResolver = mUserAwareContext.getContentResolver();
         }
 
         InputMethodSettings(@NonNull Context context,
@@ -305,79 +280,38 @@
                 Slog.d(TAG, "--- Switch the current user from " + mCurrentUserId + " to " + userId);
             }
             if (mCurrentUserId != userId || mCopyOnWrite != copyOnWrite) {
-                mCopyOnWriteDataStore.clear();
                 mEnabledInputMethodsStrCache = "";
-                // TODO: mCurrentProfileIds should be cleared here.
             }
             if (mUserAwareContext.getUserId() != userId) {
                 initContentWithUserContext(mUserAwareContext, userId);
             }
             mCurrentUserId = userId;
             mCopyOnWrite = copyOnWrite;
-            // TODO: mCurrentProfileIds should be updated here.
         }
 
         private void putString(@NonNull String key, @Nullable String str) {
-            if (mCopyOnWrite) {
-                mCopyOnWriteDataStore.put(key, str);
-            } else {
-                final int userId = CLONE_TO_MANAGED_PROFILE.contains(key)
-                        ? sUserManagerInternal.getProfileParentId(mCurrentUserId) : mCurrentUserId;
-                Settings.Secure.putStringForUser(mResolver, key, str, userId);
-            }
+            SecureSettingsWrapper.putString(key, str, mCurrentUserId);
         }
 
         @Nullable
         private String getString(@NonNull String key, @Nullable String defaultValue) {
-            final String result;
-            if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) {
-                result = mCopyOnWriteDataStore.get(key);
-            } else {
-                result = Settings.Secure.getStringForUser(mResolver, key, mCurrentUserId);
-            }
-            return result != null ? result : defaultValue;
+            return SecureSettingsWrapper.getString(key, defaultValue, mCurrentUserId);
         }
 
         private void putInt(String key, int value) {
-            if (mCopyOnWrite) {
-                mCopyOnWriteDataStore.put(key, String.valueOf(value));
-            } else {
-                final int userId = CLONE_TO_MANAGED_PROFILE.contains(key)
-                        ? sUserManagerInternal.getProfileParentId(mCurrentUserId) : mCurrentUserId;
-                Settings.Secure.putIntForUser(mResolver, key, value, userId);
-            }
+            SecureSettingsWrapper.putInt(key, value, mCurrentUserId);
         }
 
         private int getInt(String key, int defaultValue) {
-            if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) {
-                final String result = mCopyOnWriteDataStore.get(key);
-                return result != null ? Integer.parseInt(result) : defaultValue;
-            }
-            return Settings.Secure.getIntForUser(mResolver, key, defaultValue, mCurrentUserId);
+            return SecureSettingsWrapper.getInt(key, defaultValue, mCurrentUserId);
         }
 
         private void putBoolean(String key, boolean value) {
-            putInt(key, value ? 1 : 0);
+            SecureSettingsWrapper.putBoolean(key, value, mCurrentUserId);
         }
 
         private boolean getBoolean(String key, boolean defaultValue) {
-            return getInt(key, defaultValue ? 1 : 0) == 1;
-        }
-
-        public void setCurrentProfileIds(int[] currentProfileIds) {
-            synchronized (this) {
-                mCurrentProfileIds = currentProfileIds;
-            }
-        }
-
-        public boolean isCurrentProfile(int userId) {
-            synchronized (this) {
-                if (userId == mCurrentUserId) return true;
-                for (int i = 0; i < mCurrentProfileIds.length; i++) {
-                    if (userId == mCurrentProfileIds[i]) return true;
-                }
-                return false;
-            }
+            return SecureSettingsWrapper.getBoolean(key, defaultValue, mCurrentUserId);
         }
 
         ArrayList<InputMethodInfo> getEnabledInputMethodListLocked() {
@@ -428,8 +362,8 @@
 
         List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
             return buildInputMethodsAndSubtypeList(getEnabledInputMethodsStr(),
-                    mInputMethodSplitter,
-                    mSubtypeSplitter);
+                    new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR),
+                    new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR));
         }
 
         List<String> getEnabledInputMethodNames() {
@@ -700,16 +634,20 @@
             if (TextUtils.isEmpty(subtypeHistoryStr)) {
                 return imsList;
             }
-            mInputMethodSplitter.setString(subtypeHistoryStr);
-            while (mInputMethodSplitter.hasNext()) {
-                String nextImsStr = mInputMethodSplitter.next();
-                mSubtypeSplitter.setString(nextImsStr);
-                if (mSubtypeSplitter.hasNext()) {
+            final TextUtils.SimpleStringSplitter inputMethodSplitter =
+                    new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
+            final TextUtils.SimpleStringSplitter subtypeSplitter =
+                    new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
+            inputMethodSplitter.setString(subtypeHistoryStr);
+            while (inputMethodSplitter.hasNext()) {
+                String nextImsStr = inputMethodSplitter.next();
+                subtypeSplitter.setString(nextImsStr);
+                if (subtypeSplitter.hasNext()) {
                     String subtypeId = NOT_A_SUBTYPE_ID_STR;
                     // The first element is ime id.
-                    String imeId = mSubtypeSplitter.next();
-                    while (mSubtypeSplitter.hasNext()) {
-                        subtypeId = mSubtypeSplitter.next();
+                    String imeId = subtypeSplitter.next();
+                    while (subtypeSplitter.hasNext()) {
+                        subtypeId = subtypeSplitter.next();
                         break;
                     }
                     imsList.add(new Pair<>(imeId, subtypeId));
@@ -950,7 +888,6 @@
 
         public void dumpLocked(final Printer pw, final String prefix) {
             pw.println(prefix + "mCurrentUserId=" + mCurrentUserId);
-            pw.println(prefix + "mCurrentProfileIds=" + Arrays.toString(mCurrentProfileIds));
             pw.println(prefix + "mCopyOnWrite=" + mCopyOnWrite);
             pw.println(prefix + "mEnabledInputMethodsStrCache=" + mEnabledInputMethodsStrCache);
         }
@@ -1029,9 +966,7 @@
     static List<String> getEnabledInputMethodIdsForFiltering(@NonNull Context context,
             @UserIdInt int userId) {
         final String enabledInputMethodsStr = TextUtils.nullIfEmpty(
-                Settings.Secure.getStringForUser(
-                        context.getContentResolver(),
-                        Settings.Secure.ENABLED_INPUT_METHODS,
+                SecureSettingsWrapper.getString(Settings.Secure.ENABLED_INPUT_METHODS, null,
                         userId));
         if (enabledInputMethodsStr == null) {
             return List.of();
diff --git a/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java b/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java
new file mode 100644
index 0000000..559b625
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ActivityManagerInternal;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.provider.Settings;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
+
+/**
+ * A thread-safe utility class to encapsulate accesses to {@link Settings.Secure} that may need a
+ * special handling for direct-boot support.
+ *
+ * <p>Any changes made until the user storage is unlocked are non-persistent and will be reset
+ * to the persistent value when the user storage is unlocked.</p>
+ */
+final class SecureSettingsWrapper {
+    @Nullable
+    private static volatile ContentResolver sContentResolver = null;
+
+    /**
+     * Not intended to be instantiated.
+     */
+    private SecureSettingsWrapper() {
+    }
+
+    private static final ArraySet<String> CLONE_TO_MANAGED_PROFILE = new ArraySet<>();
+    static {
+        Settings.Secure.getCloneToManagedProfileSettings(CLONE_TO_MANAGED_PROFILE);
+    }
+
+    @AnyThread
+    @UserIdInt
+    private static int getUserIdForClonedSettings(@NonNull String key, @UserIdInt int userId) {
+        return CLONE_TO_MANAGED_PROFILE.contains(key)
+                ? LocalServices.getService(UserManagerInternal.class).getProfileParentId(userId)
+                : userId;
+    }
+
+    private interface ReaderWriter {
+        @AnyThread
+        void putString(@NonNull String key, @Nullable String value);
+
+        @AnyThread
+        @Nullable
+        String getString(@NonNull String key, @Nullable String defaultValue);
+
+        @AnyThread
+        void putInt(String key, int value);
+
+        @AnyThread
+        int getInt(String key, int defaultValue);
+    }
+
+    private static class UnlockedUserImpl implements ReaderWriter {
+        @UserIdInt
+        private final int mUserId;
+
+        private final ContentResolver mContentResolver;
+
+        UnlockedUserImpl(@UserIdInt int userId, @NonNull ContentResolver contentResolver) {
+            mUserId = userId;
+            mContentResolver = contentResolver;
+        }
+
+        @AnyThread
+        @Override
+        public void putString(String key, String value) {
+            final int userId = getUserIdForClonedSettings(key, mUserId);
+            Settings.Secure.putStringForUser(mContentResolver, key, value, userId);
+        }
+
+        @AnyThread
+        @Nullable
+        @Override
+        public String getString(String key, String defaultValue) {
+            final String result = Settings.Secure.getStringForUser(mContentResolver, key, mUserId);
+            return result != null ? result : defaultValue;
+        }
+
+        @AnyThread
+        @Override
+        public void putInt(String key, int value) {
+            final int userId = getUserIdForClonedSettings(key, mUserId);
+            Settings.Secure.putIntForUser(mContentResolver, key, value, userId);
+        }
+
+        @AnyThread
+        @Override
+        public int getInt(String key, int defaultValue) {
+            return Settings.Secure.getIntForUser(mContentResolver, key, defaultValue, mUserId);
+        }
+    }
+
+    /**
+     * For users whose storages are not unlocked yet, we do not want to update IME related Secure
+     * Settings. Any write operations will be forwarded to
+     * {@link LockedUserImpl#mNonPersistentKeyValues} so that we can return the volatile data until
+     * the user storage is unlocked.
+     */
+    private static final class LockedUserImpl extends UnlockedUserImpl {
+        @GuardedBy("mNonPersistentKeyValues")
+        private final ArrayMap<String, String> mNonPersistentKeyValues = new ArrayMap<>();
+
+        LockedUserImpl(@UserIdInt int userId, @NonNull ContentResolver contentResolver) {
+            super(userId, contentResolver);
+        }
+
+        @AnyThread
+        @Override
+        public void putString(String key, String value) {
+            synchronized (mNonPersistentKeyValues) {
+                mNonPersistentKeyValues.put(key, value);
+            }
+        }
+
+        @AnyThread
+        @Nullable
+        @Override
+        public String getString(String key, String defaultValue) {
+            synchronized (mNonPersistentKeyValues) {
+                if (mNonPersistentKeyValues.containsKey(key)) {
+                    final String result = mNonPersistentKeyValues.get(key);
+                    return result != null ? result : defaultValue;
+                }
+                return super.getString(key, defaultValue);
+            }
+        }
+
+        @AnyThread
+        @Override
+        public void putInt(String key, int value) {
+            synchronized (mNonPersistentKeyValues) {
+                mNonPersistentKeyValues.put(key, String.valueOf(value));
+            }
+        }
+
+        @AnyThread
+        @Override
+        public int getInt(String key, int defaultValue) {
+            synchronized (mNonPersistentKeyValues) {
+                if (mNonPersistentKeyValues.containsKey(key)) {
+                    final String result = mNonPersistentKeyValues.get(key);
+                    return result != null ? Integer.parseInt(result) : defaultValue;
+                }
+                return super.getInt(key, defaultValue);
+            }
+        }
+    }
+
+    @GuardedBy("sUserMap")
+    @NonNull
+    private static final SparseArray<ReaderWriter> sUserMap = new SparseArray<>();
+
+    private static final ReaderWriter NOOP = new ReaderWriter() {
+        @Override
+        public void putString(String key, String str) {
+        }
+
+        @Override
+        public String getString(String key, String defaultValue) {
+            return defaultValue;
+        }
+
+        @Override
+        public void putInt(String key, int value) {
+        }
+
+        @Override
+        public int getInt(String key, int defaultValue) {
+            return defaultValue;
+        }
+    };
+
+    private static ReaderWriter createImpl(@NonNull UserManagerInternal userManagerInternal,
+            @UserIdInt int userId) {
+        return userManagerInternal.isUserUnlockingOrUnlocked(userId)
+                ? new UnlockedUserImpl(userId, sContentResolver)
+                : new LockedUserImpl(userId, sContentResolver);
+    }
+
+    @NonNull
+    @AnyThread
+    private static ReaderWriter putOrGet(@UserIdInt int userId,
+            @NonNull ReaderWriter readerWriter) {
+        final boolean isUnlockedUserImpl = readerWriter instanceof UnlockedUserImpl;
+        synchronized (sUserMap) {
+            final ReaderWriter current = sUserMap.get(userId);
+            if (current == null) {
+                sUserMap.put(userId, readerWriter);
+                return readerWriter;
+            }
+            // Upgrading from CopyOnWriteImpl to DirectImpl is allowed.
+            if (current instanceof LockedUserImpl && isUnlockedUserImpl) {
+                sUserMap.put(userId, readerWriter);
+                return readerWriter;
+            }
+            return current;
+        }
+    }
+
+    @NonNull
+    @AnyThread
+    private static ReaderWriter get(@UserIdInt int userId) {
+        synchronized (sUserMap) {
+            final ReaderWriter readerWriter = sUserMap.get(userId);
+            if (readerWriter != null) {
+                return readerWriter;
+            }
+        }
+        final UserManagerInternal userManagerInternal =
+                LocalServices.getService(UserManagerInternal.class);
+        if (!userManagerInternal.exists(userId)) {
+            return NOOP;
+        }
+        return putOrGet(userId, createImpl(userManagerInternal, userId));
+    }
+
+    /**
+     * Called when {@link InputMethodManagerService} is starting.
+     *
+     * @param context the {@link Context} to be used.
+     */
+    @AnyThread
+    static void onStart(@NonNull Context context) {
+        sContentResolver = context.getContentResolver();
+
+        final int userId = LocalServices.getService(ActivityManagerInternal.class)
+                .getCurrentUserId();
+        final UserManagerInternal userManagerInternal =
+                LocalServices.getService(UserManagerInternal.class);
+        putOrGet(userId, createImpl(userManagerInternal, userId));
+
+        userManagerInternal.addUserLifecycleListener(
+                new UserManagerInternal.UserLifecycleListener() {
+                    @Override
+                    public void onUserRemoved(UserInfo user) {
+                        synchronized (sUserMap) {
+                            sUserMap.remove(userId);
+                        }
+                    }
+                }
+        );
+    }
+
+    /**
+     * Called when a user is starting.
+     *
+     * @param userId the ID of the user who is starting.
+     */
+    @AnyThread
+    static void onUserStarting(@UserIdInt int userId) {
+        putOrGet(userId, createImpl(LocalServices.getService(UserManagerInternal.class), userId));
+    }
+
+    /**
+     * Called when a user is being unlocked.
+     *
+     * @param userId the ID of the user whose storage is being unlocked.
+     */
+    @AnyThread
+    static void onUserUnlocking(@UserIdInt int userId) {
+        final ReaderWriter readerWriter = new UnlockedUserImpl(userId, sContentResolver);
+        putOrGet(userId, readerWriter);
+    }
+
+    /**
+     * Put the given string {@code value} to {@code key}.
+     *
+     * @param key a secure settings key.
+     * @param value a secure settings value.
+     * @param userId the ID of a user whose secure settings will be updated.
+     * @see Settings.Secure#putStringForUser(ContentResolver, String, String, int)
+     */
+    @AnyThread
+    static void putString(String key, String value, @UserIdInt int userId) {
+        get(userId).putString(key, value);
+    }
+
+    /**
+     * Get a string value with the given {@code key}
+     *
+     * @param key a secure settings key.
+     * @param defaultValue the default value when the value is not found.
+     * @param userId the ID of a user whose secure settings will be updated.
+     * @return The string value if it is found. {@code defaultValue} otherwise.
+     * @see Settings.Secure#getStringForUser(ContentResolver, String, int)
+     */
+    @AnyThread
+    @Nullable
+    static String getString(String key, String defaultValue, @UserIdInt int userId) {
+        return get(userId).getString(key, defaultValue);
+    }
+
+    /**
+     * Put the given integer {@code value} to {@code key}.
+     *
+     * @param key a secure settings key.
+     * @param value a secure settings value.
+     * @param userId the ID of a user whose secure settings will be updated.
+     * @see Settings.Secure#putIntForUser(ContentResolver, String, int, int)
+     */
+    @AnyThread
+    static void putInt(String key, int value, @UserIdInt int userId) {
+        get(userId).putInt(key, value);
+    }
+
+    /**
+     * Get an integer value with the given {@code key}
+     *
+     * @param key a secure settings key.
+     * @param defaultValue the default value when the value is not found.
+     * @param userId the ID of a user whose secure settings will be updated.
+     * @return The integer value if it is found. {@code defaultValue} otherwise.
+c     */
+    @AnyThread
+    static int getInt(String key, int defaultValue, @UserIdInt int userId) {
+        return get(userId).getInt(key, defaultValue);
+    }
+
+    /**
+     * Put the given boolean {@code value} to {@code key}.
+     *
+     * @param key a secure settings key.
+     * @param value a secure settings value.
+     * @param userId the ID of a user whose secure settings will be updated.
+     */
+    @AnyThread
+    static void putBoolean(String key, boolean value, @UserIdInt int userId) {
+        get(userId).putInt(key, value ? 1 : 0);
+    }
+
+    /**
+     * Get a boolean value with the given {@code key}
+     *
+     * @param key a secure settings key.
+     * @param defaultValue the default value when the value is not found.
+     * @param userId the ID of a user whose secure settings will be updated.
+     * @return The boolean value if it is found. {@code defaultValue} otherwise.
+     */
+    @AnyThread
+    static boolean getBoolean(String key, boolean defaultValue, @UserIdInt int userId) {
+        return get(userId).getInt(key, defaultValue ? 1 : 0) == 1;
+    }
+}
diff --git a/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
new file mode 100644
index 0000000..9fdeda4
--- /dev/null
+++ b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import android.app.UiModeManager;
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.hardware.display.ColorDisplayManager;
+import android.os.Binder;
+import android.os.PowerManager;
+import android.service.notification.DeviceEffectsApplier;
+import android.service.notification.ZenDeviceEffects;
+
+/** Default implementation for {@link DeviceEffectsApplier}. */
+class DefaultDeviceEffectsApplier implements DeviceEffectsApplier {
+
+    private static final String SUPPRESS_AMBIENT_DISPLAY_TOKEN =
+            "DefaultDeviceEffectsApplier:SuppressAmbientDisplay";
+    private static final int SATURATION_LEVEL_GRAYSCALE = 0;
+    private static final int SATURATION_LEVEL_FULL_COLOR = 100;
+    private static final float WALLPAPER_DIM_AMOUNT_DIMMED = 0.6f;
+    private static final float WALLPAPER_DIM_AMOUNT_NORMAL = 0f;
+
+    private final ColorDisplayManager mColorDisplayManager;
+    private final PowerManager mPowerManager;
+    private final UiModeManager mUiModeManager;
+    private final WallpaperManager mWallpaperManager;
+
+    DefaultDeviceEffectsApplier(Context context) {
+        mColorDisplayManager = context.getSystemService(ColorDisplayManager.class);
+        mPowerManager = context.getSystemService(PowerManager.class);
+        mUiModeManager = context.getSystemService(UiModeManager.class);
+        mWallpaperManager = context.getSystemService(WallpaperManager.class);
+    }
+
+    @Override
+    public void apply(ZenDeviceEffects effects) {
+        Binder.withCleanCallingIdentity(() -> {
+            mPowerManager.suppressAmbientDisplay(SUPPRESS_AMBIENT_DISPLAY_TOKEN,
+                    effects.shouldSuppressAmbientDisplay());
+
+            if (mColorDisplayManager != null) {
+                mColorDisplayManager.setSaturationLevel(
+                        effects.shouldDisplayGrayscale() ? SATURATION_LEVEL_GRAYSCALE
+                                : SATURATION_LEVEL_FULL_COLOR);
+            }
+
+            if (mWallpaperManager != null) {
+                mWallpaperManager.setWallpaperDimAmount(
+                        effects.shouldDimWallpaper() ? WALLPAPER_DIM_AMOUNT_DIMMED
+                                : WALLPAPER_DIM_AMOUNT_NORMAL);
+            }
+
+            // TODO: b/308673343 - Apply dark theme (via UiModeManager) when screen is off.
+        });
+    }
+}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 3c6887c1..02845fb 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2941,6 +2941,12 @@
             registerDeviceConfigChange();
             migrateDefaultNAS();
             maybeShowInitialReviewPermissionsNotification();
+
+            if (android.app.Flags.modesApi()) {
+                // Cannot be done earlier, as some services aren't ready until this point.
+                mZenModeHelper.setDeviceEffectsApplier(
+                        new DefaultDeviceEffectsApplier(getContext()));
+            }
         } else if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
             mSnoozeHelper.scheduleRepostsForPersistedNotifications(System.currentTimeMillis());
         } else if (phase == SystemService.PHASE_DEVICE_SPECIFIC_SERVICES_READY) {
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 5c37eea..218519f 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -27,8 +27,8 @@
 
 import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
 
-import android.annotation.IntDef;
 import android.annotation.DrawableRes;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
@@ -76,6 +76,7 @@
 import android.provider.Settings.Global;
 import android.service.notification.Condition;
 import android.service.notification.ConditionProviderService;
+import android.service.notification.DeviceEffectsApplier;
 import android.service.notification.ZenDeviceEffects;
 import android.service.notification.ZenModeConfig;
 import android.service.notification.ZenModeConfig.ZenRule;
@@ -175,6 +176,8 @@
 
     @VisibleForTesting protected int mZenMode;
     @VisibleForTesting protected NotificationManager.Policy mConsolidatedPolicy;
+    @GuardedBy("mConfigLock")
+    private ZenDeviceEffects mConsolidatedDeviceEffects = new ZenDeviceEffects.Builder().build();
     private int mUser = UserHandle.USER_SYSTEM;
 
     private final Object mConfigLock = new Object();
@@ -182,6 +185,8 @@
     @VisibleForTesting protected ZenModeConfig mConfig;
     @VisibleForTesting protected AudioManagerInternal mAudioManager;
     protected PackageManager mPm;
+    @GuardedBy("mConfigLock")
+    private DeviceEffectsApplier mDeviceEffectsApplier;
     private long mSuppressedEffects;
 
     public static final long SUPPRESSED_EFFECT_NOTIFICATIONS = 1;
@@ -189,7 +194,7 @@
     public static final long SUPPRESSED_EFFECT_ALL = SUPPRESSED_EFFECT_CALLS
             | SUPPRESSED_EFFECT_NOTIFICATIONS;
 
-    @VisibleForTesting protected boolean mIsBootComplete;
+    @VisibleForTesting protected boolean mIsSystemServicesReady;
 
     private String[] mPriorityOnlyDndExemptPackages;
 
@@ -285,10 +290,33 @@
         mPm = mContext.getPackageManager();
         mHandler.postMetricsTimer();
         cleanUpZenRules();
-        mIsBootComplete = true;
+        mIsSystemServicesReady = true;
         showZenUpgradeNotification(mZenMode);
     }
 
+    /**
+     * Set the {@link DeviceEffectsApplier} used to apply the consolidated effects.
+     *
+     * <p>If effects were calculated previously (for example, when we loaded a {@link ZenModeConfig}
+     * that includes activated rules), they will be applied immediately.
+     */
+    void setDeviceEffectsApplier(@NonNull DeviceEffectsApplier deviceEffectsApplier) {
+        if (!Flags.modesApi()) {
+            return;
+        }
+        ZenDeviceEffects consolidatedDeviceEffects;
+        synchronized (mConfigLock) {
+            if (mDeviceEffectsApplier != null) {
+                throw new IllegalStateException("Already set up a DeviceEffectsApplier!");
+            }
+            mDeviceEffectsApplier = deviceEffectsApplier;
+            consolidatedDeviceEffects = mConsolidatedDeviceEffects;
+        }
+        if (consolidatedDeviceEffects.hasEffects()) {
+            applyConsolidatedDeviceEffects();
+        }
+    }
+
     public void onUserSwitched(int user) {
         loadConfigForUser(user, "onUserSwitched");
     }
@@ -1349,7 +1377,7 @@
 
             mConfig = config;
             dispatchOnConfigChanged();
-            updateConsolidatedPolicy(reason);
+            updateAndApplyConsolidatedPolicyAndDeviceEffects(reason);
         }
         final String val = Integer.toString(config.hashCode());
         Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val);
@@ -1398,7 +1426,7 @@
         ZenLog.traceSetZenMode(zen, reason);
         mZenMode = zen;
         setZenModeSetting(mZenMode);
-        updateConsolidatedPolicy(reason);
+        updateAndApplyConsolidatedPolicyAndDeviceEffects(reason);
         boolean shouldApplyToRinger = setRingerMode && (zen != zenBefore || (
                 zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
                         && policyHashBefore != mConsolidatedPolicy.hashCode()));
@@ -1459,25 +1487,56 @@
         }
     }
 
-    private void updateConsolidatedPolicy(String reason) {
+    private void updateAndApplyConsolidatedPolicyAndDeviceEffects(String reason) {
         synchronized (mConfigLock) {
             if (mConfig == null) return;
             ZenPolicy policy = new ZenPolicy();
+            ZenDeviceEffects.Builder deviceEffectsBuilder = new ZenDeviceEffects.Builder();
             if (mConfig.manualRule != null) {
                 applyCustomPolicy(policy, mConfig.manualRule);
+                if (Flags.modesApi()) {
+                    deviceEffectsBuilder.add(mConfig.manualRule.zenDeviceEffects);
+                }
             }
 
             for (ZenRule automaticRule : mConfig.automaticRules.values()) {
                 if (automaticRule.isAutomaticActive()) {
                     applyCustomPolicy(policy, automaticRule);
+                    if (Flags.modesApi()) {
+                        deviceEffectsBuilder.add(automaticRule.zenDeviceEffects);
+                    }
                 }
             }
+
             Policy newPolicy = mConfig.toNotificationPolicy(policy);
             if (!Objects.equals(mConsolidatedPolicy, newPolicy)) {
                 mConsolidatedPolicy = newPolicy;
                 dispatchOnConsolidatedPolicyChanged();
                 ZenLog.traceSetConsolidatedZenPolicy(mConsolidatedPolicy, reason);
             }
+
+            if (Flags.modesApi()) {
+                ZenDeviceEffects deviceEffects = deviceEffectsBuilder.build();
+                if (!deviceEffects.equals(mConsolidatedDeviceEffects)) {
+                    mConsolidatedDeviceEffects = deviceEffects;
+                    mHandler.postApplyDeviceEffects();
+                }
+            }
+        }
+    }
+
+    private void applyConsolidatedDeviceEffects() {
+        if (!Flags.modesApi()) {
+            return;
+        }
+        DeviceEffectsApplier applier;
+        ZenDeviceEffects effects;
+        synchronized (mConfigLock) {
+            applier = mDeviceEffectsApplier;
+            effects = mConsolidatedDeviceEffects;
+        }
+        if (applier != null) {
+            applier.apply(effects);
         }
     }
 
@@ -1893,7 +1952,7 @@
     private void showZenUpgradeNotification(int zen) {
         final boolean isWatch = mContext.getPackageManager().hasSystemFeature(
             PackageManager.FEATURE_WATCH);
-        final boolean showNotification = mIsBootComplete
+        final boolean showNotification = mIsSystemServicesReady
                 && zen != Global.ZEN_MODE_OFF
                 && !isWatch
                 && Settings.Secure.getInt(mContext.getContentResolver(),
@@ -2067,6 +2126,7 @@
         private static final int MSG_DISPATCH = 1;
         private static final int MSG_METRICS = 2;
         private static final int MSG_RINGER_AUDIO = 5;
+        private static final int MSG_APPLY_EFFECTS = 6;
 
         private static final long METRICS_PERIOD_MS = 6 * 60 * 60 * 1000;
 
@@ -2089,6 +2149,11 @@
             sendMessage(obtainMessage(MSG_RINGER_AUDIO, shouldApplyToRinger));
         }
 
+        private void postApplyDeviceEffects() {
+            removeMessages(MSG_APPLY_EFFECTS);
+            sendEmptyMessage(MSG_APPLY_EFFECTS);
+        }
+
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
@@ -2101,6 +2166,10 @@
                 case MSG_RINGER_AUDIO:
                     boolean shouldApplyToRinger = (boolean) msg.obj;
                     updateRingerAndAudio(shouldApplyToRinger);
+                    break;
+                case MSG_APPLY_EFFECTS:
+                    applyConsolidatedDeviceEffects();
+                    break;
             }
         }
     }
diff --git a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
index f77d7898..d9c8ec6 100644
--- a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
+++ b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
@@ -317,11 +317,11 @@
             return 1;
         }
         final String overlayPackageName = "com.android.shell";
-        FabricatedOverlay.Builder overlayBuilder = new FabricatedOverlay.Builder(
-                overlayPackageName, name, targetPackage)
-                .setTargetOverlayable(targetOverlayable);
+        FabricatedOverlay overlay = new FabricatedOverlay(name, targetPackage);
+        overlay.setTargetOverlayable(targetOverlayable);
+        overlay.setOwningPackage(overlayPackageName);
         if (filename != null) {
-            int result = addOverlayValuesFromXml(overlayBuilder, targetPackage, filename);
+            int result = addOverlayValuesFromXml(overlay, targetPackage, filename);
             if (result != 0) {
                 return result;
             }
@@ -329,18 +329,18 @@
             final String resourceName = getNextArgRequired();
             final String typeStr = getNextArgRequired();
             final String strData = String.join(" ", peekRemainingArgs());
-            if (addOverlayValue(overlayBuilder, resourceName, typeStr, strData, config) != 0) {
+            if (addOverlayValue(overlay, resourceName, typeStr, strData, config) != 0) {
                 return 1;
             }
         }
 
         mInterface.commit(new OverlayManagerTransaction.Builder()
-                .registerFabricatedOverlay(overlayBuilder.build()).build());
+                .registerFabricatedOverlay(overlay).build());
         return 0;
     }
 
     private int addOverlayValuesFromXml(
-            FabricatedOverlay.Builder overlayBuilder, String targetPackage, String filename) {
+            FabricatedOverlay overlay, String targetPackage, String filename) {
         final PrintWriter err = getErrPrintWriter();
         File file = new File(filename);
         if (!file.exists()) {
@@ -388,7 +388,7 @@
                             return 1;
                         }
                         String config = parser.getAttributeValue(null, "config");
-                        if (addOverlayValue(overlayBuilder, targetPackage + ':' + target,
+                        if (addOverlayValue(overlay, targetPackage + ':' + target,
                                   overlayType, value, config) != 0) {
                             return 1;
                         }
@@ -405,8 +405,8 @@
         return 0;
     }
 
-    private int addOverlayValue(FabricatedOverlay.Builder overlayBuilder,
-            String resourceName, String typeString, String valueString, String configuration) {
+    private int addOverlayValue(FabricatedOverlay overlay, String resourceName, String typeString,
+                                String valueString, String configuration) {
         final int type;
         typeString = typeString.toLowerCase(Locale.getDefault());
         if (TYPE_MAP.containsKey(typeString)) {
@@ -419,10 +419,14 @@
             }
         }
         if (type == TypedValue.TYPE_STRING) {
-            overlayBuilder.setResourceValue(resourceName, type, valueString, configuration);
+            overlay.setResourceValue(resourceName, type, valueString, configuration);
         } else if (type < 0) {
             ParcelFileDescriptor pfd =  openFileForSystem(valueString, "r");
-            overlayBuilder.setResourceValue(resourceName, pfd, configuration);
+            if (valueString.endsWith(".9.png")) {
+                overlay.setNinePatchResourceValue(resourceName, pfd, configuration);
+            } else {
+                overlay.setResourceValue(resourceName, pfd, configuration);
+            }
         } else {
             final int intData;
             if (valueString.startsWith("0x")) {
@@ -430,7 +434,7 @@
             } else {
                 intData = Integer.parseUnsignedInt(valueString);
             }
-            overlayBuilder.setResourceValue(resourceName, type, intData, configuration);
+            overlay.setResourceValue(resourceName, type, intData, configuration);
         }
         return 0;
     }
diff --git a/services/core/java/com/android/server/pdb/TEST_MAPPING b/services/core/java/com/android/server/pdb/TEST_MAPPING
new file mode 100644
index 0000000..1aa8601
--- /dev/null
+++ b/services/core/java/com/android/server/pdb/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+    "postsubmit": [
+        {
+            "name": "FrameworksServicesTests",
+            "options": [
+                {
+                    "include-filter": "com.android.server.pdb.PersistentDataBlockServiceTest"
+                }
+            ]
+        }
+    ]
+}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index e088d9a..1485b96 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -40,6 +40,7 @@
 import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER_LOCK_ORIG;
 import static com.android.server.wallpaper.WallpaperUtils.getWallpaperDir;
 import static com.android.server.wallpaper.WallpaperUtils.makeWallpaperIdLocked;
+import static com.android.window.flags.Flags.multiCrop;
 
 import android.annotation.NonNull;
 import android.app.ActivityManager;
@@ -93,7 +94,6 @@
 import android.os.SELinux;
 import android.os.ShellCallback;
 import android.os.SystemClock;
-import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.storage.StorageManager;
@@ -1516,8 +1516,7 @@
         mColorsChangedListeners = new SparseArray<>();
         mWallpaperDataParser = new WallpaperDataParser(mContext, mWallpaperDisplayHelper,
                 mWallpaperCropper);
-        mIsMultiCropEnabled =
-                SystemProperties.getBoolean("persist.wm.debug.wallpaper_multi_crop", false);
+        mIsMultiCropEnabled = multiCrop();
         LocalServices.addService(WallpaperManagerInternal.class, new LocalService());
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index b8b102f..91f45a7 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -76,6 +76,7 @@
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IMMERSIVE;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_LOCKTASK;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
+import static com.android.sdksandbox.flags.Flags.sandboxActivitySdkBasedContext;
 import static com.android.server.am.ActivityManagerService.STOCK_PM_FLAGS;
 import static com.android.server.am.ActivityManagerServiceDumpActivitiesProto.ROOT_WINDOW_CONTAINER;
 import static com.android.server.am.ActivityManagerServiceDumpProcessesProto.CONFIG_WILL_CHANGE;
@@ -125,7 +126,6 @@
 import static com.android.server.wm.Task.REPARENT_KEEP_ROOT_TASK_AT_FRONT;
 import static com.android.server.wm.WindowManagerService.MY_PID;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
-import static com.android.sdksandbox.flags.Flags.sandboxActivitySdkBasedContext;
 
 import android.Manifest;
 import android.annotation.IntDef;
@@ -1261,10 +1261,10 @@
                 true /*validateIncomingUser*/);
     }
 
-    static boolean isSdkSandboxActivity(Context context, Intent intent) {
+    static boolean isSdkSandboxActivityIntent(Context context, Intent intent) {
         return intent != null
                 && (sandboxActivitySdkBasedContext()
-                        ? SdkSandboxActivityAuthority.isSdkSandboxActivity(context, intent)
+                        ? SdkSandboxActivityAuthority.isSdkSandboxActivityIntent(context, intent)
                         : intent.isSandboxActivity(context));
     }
 
@@ -1278,7 +1278,7 @@
         assertPackageMatchesCallingUid(callingPackage);
         enforceNotIsolatedCaller("startActivityAsUser");
 
-        if (isSdkSandboxActivity(mContext, intent)) {
+        if (isSdkSandboxActivityIntent(mContext, intent)) {
             SdkSandboxManagerLocal sdkSandboxManagerLocal = LocalManagerRegistry.getManager(
                     SdkSandboxManagerLocal.class);
             sdkSandboxManagerLocal.enforceAllowedToHostSandboxedActivity(
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index a21b9b4..4a479aa 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -1085,7 +1085,8 @@
             // Remove the process record so it won't be considered as alive.
             mService.mProcessNames.remove(wpc.mName, wpc.mUid);
             mService.mProcessMap.remove(wpc.getPid());
-        } else if (ActivityTaskManagerService.isSdkSandboxActivity(mService.mContext, r.intent)) {
+        } else if (ActivityTaskManagerService.isSdkSandboxActivityIntent(
+                mService.mContext, r.intent)) {
             Slog.e(TAG, "Abort sandbox activity launching as no sandbox process to host it.");
             r.finishIfPossible("No sandbox process for the activity", false /* oomAdj */);
             r.launchFailed = true;
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index be7b855..c5902c9 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -395,7 +395,8 @@
      *
      * @return false if unable to predict what will happen
      */
-    private static boolean getAnimatablePrevActivities(@NonNull Task currentTask,
+    @VisibleForTesting
+    static boolean getAnimatablePrevActivities(@NonNull Task currentTask,
             @NonNull ActivityRecord currentActivity,
             @NonNull ArrayList<ActivityRecord> outPrevActivities) {
         if (currentActivity.mAtmService
@@ -413,45 +414,86 @@
         // Searching previous
         final ActivityRecord prevActivity = currentTask.getActivity((below) -> !below.finishing,
                 currentActivity, false /*includeBoundary*/, true /*traverseTopToBottom*/);
-        if (prevActivity == null) {
-            // No previous activity in this task, can still predict if previous task exists.
-            return true;
-        }
-        if (currentTask.getActivity((above) -> !above.finishing, currentActivity,
-                false /*includeBoundary*/, false /*traverseTopToBottom*/) != null) {
-            // another activity is above this activity, don't know what will happen
-            return false;
-        }
 
         final TaskFragment currTF = currentActivity.getTaskFragment();
-        final TaskFragment prevTF = prevActivity.getTaskFragment();
-        if (currTF != prevTF && prevTF != null) {
-            final TaskFragment prevTFAdjacent = prevTF.getAdjacentTaskFragment();
-            if (prevTFAdjacent != null) {
-                if (prevTFAdjacent == currTF) {
-                    outPrevActivities.clear();
-                    // No more activity in task, so it can predict if previous task exists.
-                    // Otherwise, unable to predict what will happen when app receive
-                    // back key, skip animation.
-                    return currentTask.getActivity((below) -> !below.finishing, prevActivity,
+        if (currTF != null && currTF.asTask() == null) {
+            // The currentActivity is embedded, search for the candidate previous activities.
+            if (prevActivity != null && currTF.hasChild(prevActivity)) {
+                // PrevActivity is under the same task fragment, that's it.
+                outPrevActivities.add(prevActivity);
+                return true;
+            }
+            if (currTF.getAdjacentTaskFragment() != null) {
+                // The two TFs are adjacent (visually displayed side-by-side), search if any
+                // activity below the lowest one
+                // If companion, those two TF will be closed together.
+                if (currTF.getCompanionTaskFragment() != null) {
+                    final WindowContainer commonParent = currTF.getParent();
+                    final TaskFragment adjacentTF = currTF.getAdjacentTaskFragment();
+                    final TaskFragment lowerTF = commonParent.mChildren.indexOf(currTF)
+                            < commonParent.mChildren.indexOf(adjacentTF)
+                            ? currTF : adjacentTF;
+                    final ActivityRecord lowerActivity = lowerTF.getTopNonFinishingActivity();
+                    // TODO (b/274997067) close currTF + companionTF, open next activities if any.
+                    // Allow to predict next task if no more activity in task. Or return previous
+                    // activities for cross-activity animation.
+                    return currentTask.getActivity((below) -> !below.finishing, lowerActivity,
                             false /*includeBoundary*/, true /*traverseTopToBottom*/) == null;
-                } else {
-                    final ActivityRecord prevActivityAdjacent =
-                            prevTFAdjacent.getTopNonFinishingActivity();
-                    if (prevActivityAdjacent != null) {
-                        outPrevActivities.add(prevActivityAdjacent);
-                    } else {
-                        // Don't know what will happen.
-                        outPrevActivities.clear();
-                        return false;
-                    }
                 }
+                // Unable to predict if no companion, it can only close current activity and make
+                // prev Activity full screened.
+                return false;
+            } else if (currTF.getCompanionTaskFragment() != null) {
+                // TF is isStacked, search bottom activity from companion TF.
+                //
+                // Sample hierarchy: search for underPrevious if any.
+                //     Current TF
+                //     Companion TF (bottomActivityInCompanion)
+                //     Bottom Activity not inside companion TF (underPrevious)
+                final TaskFragment companionTF = currTF.getCompanionTaskFragment();
+                // find bottom activity in Companion TF.
+                final ActivityRecord bottomActivityInCompanion = companionTF.getActivity(
+                        (below) -> !below.finishing, false /* traverseTopToBottom */);
+                final ActivityRecord underPrevious = currentTask.getActivity(
+                        (below) -> !below.finishing, bottomActivityInCompanion,
+                        false /*includeBoundary*/, true /*traverseTopToBottom*/);
+                if (underPrevious != null) {
+                    outPrevActivities.add(underPrevious);
+                    addPreviousAdjacentActivityIfExist(underPrevious, outPrevActivities);
+                }
+                return true;
             }
         }
+
+        if (prevActivity == null) {
+            // No previous activity in this Task nor TaskFragment, it can still predict if previous
+            // task exists.
+            return true;
+        }
+        // Add possible adjacent activity if prevActivity is embedded
+        addPreviousAdjacentActivityIfExist(prevActivity, outPrevActivities);
         outPrevActivities.add(prevActivity);
         return true;
     }
 
+    private static void addPreviousAdjacentActivityIfExist(@NonNull ActivityRecord prevActivity,
+            @NonNull ArrayList<ActivityRecord> outPrevActivities) {
+        final TaskFragment prevTF = prevActivity.getTaskFragment();
+        if (prevTF == null || prevTF.asTask() != null) {
+            return;
+        }
+
+        final TaskFragment prevTFAdjacent = prevTF.getAdjacentTaskFragment();
+        if (prevTFAdjacent == null || prevTFAdjacent.asTask() != null) {
+            return;
+        }
+        final ActivityRecord prevActivityAdjacent =
+                prevTFAdjacent.getTopNonFinishingActivity();
+        if (prevActivityAdjacent != null) {
+            outPrevActivities.add(prevActivityAdjacent);
+        }
+    }
+
     private static void findAdjacentActivityIfExist(@NonNull ActivityRecord mainActivity,
             @NonNull ArrayList<ActivityRecord> outList) {
         final TaskFragment mainTF = mainActivity.getTaskFragment();
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index f51bf7f..0006bd2 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -420,7 +420,7 @@
     @Override
     ActivityRecord getActivity(Predicate<ActivityRecord> callback, boolean traverseTopToBottom,
             ActivityRecord boundary) {
-        if (mType == Type.ABOVE_TASKS || mType == Type.BELOW_TASKS) {
+        if (mType == Type.ABOVE_TASKS) {
             return null;
         }
         return super.getActivity(callback, traverseTopToBottom, boundary);
@@ -428,23 +428,39 @@
 
     @Override
     Task getTask(Predicate<Task> callback, boolean traverseTopToBottom) {
-        if (mType == Type.ABOVE_TASKS || mType == Type.BELOW_TASKS) {
+        if (mType == Type.ABOVE_TASKS) {
             return null;
         }
         return super.getTask(callback, traverseTopToBottom);
     }
 
     @Override
+    Task getRootTask(Predicate<Task> callback, boolean traverseTopToBottom) {
+        if (mType == Type.ABOVE_TASKS) {
+            return null;
+        }
+        return super.getRootTask(callback, traverseTopToBottom);
+    }
+
+    @Override
     boolean forAllActivities(Predicate<ActivityRecord> callback, boolean traverseTopToBottom) {
-        if (mType == Type.ABOVE_TASKS || mType == Type.BELOW_TASKS) {
+        if (mType == Type.ABOVE_TASKS) {
             return false;
         }
         return super.forAllActivities(callback, traverseTopToBottom);
     }
 
     @Override
+    void forAllActivities(Consumer<ActivityRecord> callback, boolean traverseTopToBottom) {
+        if (mType == Type.ABOVE_TASKS) {
+            return;
+        }
+        super.forAllActivities(callback, traverseTopToBottom);
+    }
+
+    @Override
     boolean forAllRootTasks(Predicate<Task> callback, boolean traverseTopToBottom) {
-        if (mType == Type.ABOVE_TASKS || mType == Type.BELOW_TASKS) {
+        if (mType == Type.ABOVE_TASKS) {
             return false;
         }
         return super.forAllRootTasks(callback, traverseTopToBottom);
@@ -452,7 +468,7 @@
 
     @Override
     boolean forAllTasks(Predicate<Task> callback) {
-        if (mType == Type.ABOVE_TASKS || mType == Type.BELOW_TASKS) {
+        if (mType == Type.ABOVE_TASKS) {
             return false;
         }
         return super.forAllTasks(callback);
@@ -460,13 +476,29 @@
 
     @Override
     boolean forAllLeafTasks(Predicate<Task> callback) {
-        if (mType == Type.ABOVE_TASKS || mType == Type.BELOW_TASKS) {
+        if (mType == Type.ABOVE_TASKS) {
             return false;
         }
         return super.forAllLeafTasks(callback);
     }
 
     @Override
+    void forAllLeafTasks(Consumer<Task> callback, boolean traverseTopToBottom) {
+        if (mType == Type.ABOVE_TASKS) {
+            return;
+        }
+        super.forAllLeafTasks(callback, traverseTopToBottom);
+    }
+
+    @Override
+    boolean forAllLeafTaskFragments(Predicate<TaskFragment> callback) {
+        if (mType == Type.ABOVE_TASKS) {
+            return false;
+        }
+        return super.forAllLeafTaskFragments(callback);
+    }
+
+    @Override
     void forAllDisplayAreas(Consumer<DisplayArea> callback) {
         super.forAllDisplayAreas(callback);
         callback.accept(this);
diff --git a/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java b/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java
deleted file mode 100644
index e82dc37..0000000
--- a/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java
+++ /dev/null
@@ -1,448 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static android.graphics.Matrix.MSCALE_X;
-import static android.graphics.Matrix.MSCALE_Y;
-import static android.graphics.Matrix.MSKEW_X;
-import static android.graphics.Matrix.MSKEW_Y;
-
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TPL;
-
-import android.graphics.Matrix;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.Region;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.IntArray;
-import android.util.Pair;
-import android.util.Size;
-import android.view.InputWindowHandle;
-import android.window.ITrustedPresentationListener;
-import android.window.TrustedPresentationThresholds;
-import android.window.WindowInfosListener;
-
-import com.android.internal.protolog.common.ProtoLog;
-import com.android.server.wm.utils.RegionUtils;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Optional;
-
-/**
- * Class to handle TrustedPresentationListener registrations in a thread safe manner. This class
- * also takes care of cleaning up listeners when the remote process dies.
- */
-public class TrustedPresentationListenerController {
-
-    // Should only be accessed by the posting to the handler
-    private class Listeners {
-        private final class ListenerDeathRecipient implements IBinder.DeathRecipient {
-            IBinder mListenerBinder;
-            int mInstances;
-
-            ListenerDeathRecipient(IBinder listenerBinder) {
-                mListenerBinder = listenerBinder;
-                mInstances = 0;
-                try {
-                    mListenerBinder.linkToDeath(this, 0);
-                } catch (RemoteException ignore) {
-                }
-            }
-
-            void addInstance() {
-                mInstances++;
-            }
-
-            // return true if there are no instances alive
-            boolean removeInstance() {
-                mInstances--;
-                if (mInstances > 0) {
-                    return false;
-                }
-                mListenerBinder.unlinkToDeath(this, 0);
-                return true;
-            }
-
-            public void binderDied() {
-                mHandler.post(() -> {
-                    mUniqueListeners.remove(mListenerBinder);
-                    removeListeners(mListenerBinder, Optional.empty());
-                });
-            }
-        }
-
-        // tracks binder deaths for cleanup
-        ArrayMap<IBinder, ListenerDeathRecipient> mUniqueListeners = new ArrayMap<>();
-        ArrayMap<IBinder /*window*/, ArrayList<TrustedPresentationInfo>> mWindowToListeners =
-                new ArrayMap<>();
-
-        void register(IBinder window, ITrustedPresentationListener listener,
-                TrustedPresentationThresholds thresholds, int id) {
-            var listenersForWindow = mWindowToListeners.computeIfAbsent(window,
-                    iBinder -> new ArrayList<>());
-            listenersForWindow.add(new TrustedPresentationInfo(thresholds, id, listener));
-
-            // register death listener
-            var listenerBinder = listener.asBinder();
-            var deathRecipient = mUniqueListeners.computeIfAbsent(listenerBinder,
-                    ListenerDeathRecipient::new);
-            deathRecipient.addInstance();
-        }
-
-        void unregister(ITrustedPresentationListener trustedPresentationListener, int id) {
-            var listenerBinder = trustedPresentationListener.asBinder();
-            var deathRecipient = mUniqueListeners.get(listenerBinder);
-            if (deathRecipient == null) {
-                ProtoLog.e(WM_DEBUG_TPL, "unregister failed, couldn't find"
-                        + " deathRecipient for %s with id=%d", trustedPresentationListener, id);
-                return;
-            }
-
-            if (deathRecipient.removeInstance()) {
-                mUniqueListeners.remove(listenerBinder);
-            }
-            removeListeners(listenerBinder, Optional.of(id));
-        }
-
-        boolean isEmpty() {
-            return mWindowToListeners.isEmpty();
-        }
-
-        ArrayList<TrustedPresentationInfo> get(IBinder windowToken) {
-            return mWindowToListeners.get(windowToken);
-        }
-
-        private void removeListeners(IBinder listenerBinder, Optional<Integer> id) {
-            for (int i = mWindowToListeners.size() - 1; i >= 0; i--) {
-                var listeners = mWindowToListeners.valueAt(i);
-                for (int j = listeners.size() - 1; j >= 0; j--) {
-                    var listener = listeners.get(j);
-                    if (listener.mListener.asBinder() == listenerBinder && (id.isEmpty()
-                            || listener.mId == id.get())) {
-                        listeners.remove(j);
-                    }
-                }
-                if (listeners.isEmpty()) {
-                    mWindowToListeners.removeAt(i);
-                }
-            }
-        }
-    }
-
-    private final Object mHandlerThreadLock = new Object();
-    private HandlerThread mHandlerThread;
-    private Handler mHandler;
-
-    private WindowInfosListener mWindowInfosListener;
-
-    Listeners mRegisteredListeners = new Listeners();
-
-    private InputWindowHandle[] mLastWindowHandles;
-
-    private final Object mIgnoredWindowTokensLock = new Object();
-
-    private final ArraySet<IBinder> mIgnoredWindowTokens = new ArraySet<>();
-
-    private void startHandlerThreadIfNeeded() {
-        synchronized (mHandlerThreadLock) {
-            if (mHandler == null) {
-                mHandlerThread = new HandlerThread("WindowInfosListenerForTpl");
-                mHandlerThread.start();
-                mHandler = new Handler(mHandlerThread.getLooper());
-            }
-        }
-    }
-
-    void addIgnoredWindowTokens(IBinder token) {
-        synchronized (mIgnoredWindowTokensLock) {
-            mIgnoredWindowTokens.add(token);
-        }
-    }
-
-    void removeIgnoredWindowTokens(IBinder token) {
-        synchronized (mIgnoredWindowTokensLock) {
-            mIgnoredWindowTokens.remove(token);
-        }
-    }
-
-    void registerListener(IBinder window, ITrustedPresentationListener listener,
-            TrustedPresentationThresholds thresholds, int id) {
-        startHandlerThreadIfNeeded();
-        mHandler.post(() -> {
-            ProtoLog.d(WM_DEBUG_TPL, "Registering listener=%s with id=%d for window=%s with %s",
-                    listener, id, window, thresholds);
-
-            mRegisteredListeners.register(window, listener, thresholds, id);
-            registerWindowInfosListener();
-            // Update the initial state for the new registered listener
-            computeTpl(mLastWindowHandles);
-        });
-    }
-
-    void unregisterListener(ITrustedPresentationListener listener, int id) {
-        startHandlerThreadIfNeeded();
-        mHandler.post(() -> {
-            ProtoLog.d(WM_DEBUG_TPL, "Unregistering listener=%s with id=%d",
-                    listener, id);
-
-            mRegisteredListeners.unregister(listener, id);
-            if (mRegisteredListeners.isEmpty()) {
-                unregisterWindowInfosListener();
-            }
-        });
-    }
-
-    void dump(PrintWriter pw) {
-        final String innerPrefix = "  ";
-        pw.println("TrustedPresentationListenerController:");
-        pw.println(innerPrefix + "Active unique listeners ("
-                + mRegisteredListeners.mUniqueListeners.size() + "):");
-        for (int i = 0; i < mRegisteredListeners.mWindowToListeners.size(); i++) {
-            pw.println(
-                    innerPrefix + "  window=" + mRegisteredListeners.mWindowToListeners.keyAt(i));
-            final var listeners = mRegisteredListeners.mWindowToListeners.valueAt(i);
-            for (int j = 0; j < listeners.size(); j++) {
-                final var listener = listeners.get(j);
-                pw.println(innerPrefix + innerPrefix + "  listener=" + listener.mListener.asBinder()
-                        + " id=" + listener.mId
-                        + " thresholds=" + listener.mThresholds);
-            }
-        }
-    }
-
-    private void registerWindowInfosListener() {
-        if (mWindowInfosListener != null) {
-            return;
-        }
-
-        mWindowInfosListener = new WindowInfosListener() {
-            @Override
-            public void onWindowInfosChanged(InputWindowHandle[] windowHandles,
-                    DisplayInfo[] displayInfos) {
-                mHandler.post(() -> computeTpl(windowHandles));
-            }
-        };
-        mLastWindowHandles = mWindowInfosListener.register().first;
-    }
-
-    private void unregisterWindowInfosListener() {
-        if (mWindowInfosListener == null) {
-            return;
-        }
-
-        mWindowInfosListener.unregister();
-        mWindowInfosListener = null;
-        mLastWindowHandles = null;
-    }
-
-    private void computeTpl(InputWindowHandle[] windowHandles) {
-        mLastWindowHandles = windowHandles;
-        if (mLastWindowHandles == null || mLastWindowHandles.length == 0
-                || mRegisteredListeners.isEmpty()) {
-            return;
-        }
-
-        Rect tmpRect = new Rect();
-        Matrix tmpInverseMatrix = new Matrix();
-        float[] tmpMatrix = new float[9];
-        Region coveredRegionsAbove = new Region();
-        long currTimeMs = System.currentTimeMillis();
-        ProtoLog.v(WM_DEBUG_TPL, "Checking %d windows", mLastWindowHandles.length);
-
-        ArrayMap<ITrustedPresentationListener, Pair<IntArray, IntArray>> listenerUpdates =
-                new ArrayMap<>();
-        ArraySet<IBinder> ignoredWindowTokens;
-        synchronized (mIgnoredWindowTokensLock) {
-            ignoredWindowTokens = new ArraySet<>(mIgnoredWindowTokens);
-        }
-        for (var windowHandle : mLastWindowHandles) {
-            if (ignoredWindowTokens.contains(windowHandle.getWindowToken())) {
-                ProtoLog.v(WM_DEBUG_TPL, "Skipping %s", windowHandle.name);
-                continue;
-            }
-            tmpRect.set(windowHandle.frame);
-            var listeners = mRegisteredListeners.get(windowHandle.getWindowToken());
-            if (listeners != null) {
-                Region region = new Region();
-                region.op(tmpRect, coveredRegionsAbove, Region.Op.DIFFERENCE);
-                windowHandle.transform.invert(tmpInverseMatrix);
-                tmpInverseMatrix.getValues(tmpMatrix);
-                float scaleX = (float) Math.sqrt(tmpMatrix[MSCALE_X] * tmpMatrix[MSCALE_X]
-                        + tmpMatrix[MSKEW_X] * tmpMatrix[MSKEW_X]);
-                float scaleY = (float) Math.sqrt(tmpMatrix[MSCALE_Y] * tmpMatrix[MSCALE_Y]
-                        + tmpMatrix[MSKEW_Y] * tmpMatrix[MSKEW_Y]);
-
-                float fractionRendered = computeFractionRendered(region, new RectF(tmpRect),
-                        windowHandle.contentSize,
-                        scaleX, scaleY);
-
-                checkIfInThreshold(listeners, listenerUpdates, fractionRendered, windowHandle.alpha,
-                        currTimeMs);
-            }
-
-            coveredRegionsAbove.op(tmpRect, Region.Op.UNION);
-            ProtoLog.v(WM_DEBUG_TPL, "coveredRegionsAbove updated with %s frame:%s region:%s",
-                    windowHandle.name, tmpRect.toShortString(), coveredRegionsAbove);
-        }
-
-        for (int i = 0; i < listenerUpdates.size(); i++) {
-            var updates = listenerUpdates.valueAt(i);
-            var listener = listenerUpdates.keyAt(i);
-            try {
-                listener.onTrustedPresentationChanged(updates.first.toArray(),
-                        updates.second.toArray());
-            } catch (RemoteException ignore) {
-            }
-        }
-    }
-
-    private void addListenerUpdate(
-            ArrayMap<ITrustedPresentationListener, Pair<IntArray, IntArray>> listenerUpdates,
-            ITrustedPresentationListener listener, int id, boolean presentationState) {
-        var updates = listenerUpdates.get(listener);
-        if (updates == null) {
-            updates = new Pair<>(new IntArray(), new IntArray());
-            listenerUpdates.put(listener, updates);
-        }
-        if (presentationState) {
-            updates.first.add(id);
-        } else {
-            updates.second.add(id);
-        }
-    }
-
-
-    private void checkIfInThreshold(
-            ArrayList<TrustedPresentationInfo> listeners,
-            ArrayMap<ITrustedPresentationListener, Pair<IntArray, IntArray>> listenerUpdates,
-            float fractionRendered, float alpha, long currTimeMs) {
-        ProtoLog.v(WM_DEBUG_TPL, "checkIfInThreshold fractionRendered=%f alpha=%f currTimeMs=%d",
-                fractionRendered, alpha, currTimeMs);
-        for (int i = 0; i < listeners.size(); i++) {
-            var trustedPresentationInfo = listeners.get(i);
-            var listener = trustedPresentationInfo.mListener;
-            boolean lastState = trustedPresentationInfo.mLastComputedTrustedPresentationState;
-            boolean newState =
-                    (alpha >= trustedPresentationInfo.mThresholds.mMinAlpha) && (fractionRendered
-                            >= trustedPresentationInfo.mThresholds.mMinFractionRendered);
-            trustedPresentationInfo.mLastComputedTrustedPresentationState = newState;
-
-            ProtoLog.v(WM_DEBUG_TPL,
-                    "lastState=%s newState=%s alpha=%f minAlpha=%f fractionRendered=%f "
-                            + "minFractionRendered=%f",
-                    lastState, newState, alpha, trustedPresentationInfo.mThresholds.mMinAlpha,
-                    fractionRendered, trustedPresentationInfo.mThresholds.mMinFractionRendered);
-
-            if (lastState && !newState) {
-                // We were in the trusted presentation state, but now we left it,
-                // emit the callback if needed
-                if (trustedPresentationInfo.mLastReportedTrustedPresentationState) {
-                    trustedPresentationInfo.mLastReportedTrustedPresentationState = false;
-                    addListenerUpdate(listenerUpdates, listener,
-                            trustedPresentationInfo.mId, /*presentationState*/ false);
-                    ProtoLog.d(WM_DEBUG_TPL, "Adding untrusted state listener=%s with id=%d",
-                            listener, trustedPresentationInfo.mId);
-                }
-                // Reset the timer
-                trustedPresentationInfo.mEnteredTrustedPresentationStateTime = -1;
-            } else if (!lastState && newState) {
-                // We were not in the trusted presentation state, but we entered it, begin the timer
-                // and make sure this gets called at least once more!
-                trustedPresentationInfo.mEnteredTrustedPresentationStateTime = currTimeMs;
-                mHandler.postDelayed(() -> {
-                    computeTpl(mLastWindowHandles);
-                }, (long) (trustedPresentationInfo.mThresholds.mStabilityRequirementMs * 1.5));
-            }
-
-            // Has the timer elapsed, but we are still in the state? Emit a callback if needed
-            if (!trustedPresentationInfo.mLastReportedTrustedPresentationState && newState && (
-                    currTimeMs - trustedPresentationInfo.mEnteredTrustedPresentationStateTime
-                            > trustedPresentationInfo.mThresholds.mStabilityRequirementMs)) {
-                trustedPresentationInfo.mLastReportedTrustedPresentationState = true;
-                addListenerUpdate(listenerUpdates, listener,
-                        trustedPresentationInfo.mId, /*presentationState*/ true);
-                ProtoLog.d(WM_DEBUG_TPL, "Adding trusted state listener=%s with id=%d",
-                        listener, trustedPresentationInfo.mId);
-            }
-        }
-    }
-
-    private float computeFractionRendered(Region visibleRegion, RectF screenBounds,
-            Size contentSize,
-            float sx, float sy) {
-        ProtoLog.v(WM_DEBUG_TPL,
-                "computeFractionRendered: visibleRegion=%s screenBounds=%s contentSize=%s "
-                        + "scale=%f,%f",
-                visibleRegion, screenBounds, contentSize, sx, sy);
-
-        if (contentSize.getWidth() == 0 || contentSize.getHeight() == 0) {
-            return -1;
-        }
-        if (screenBounds.width() == 0 || screenBounds.height() == 0) {
-            return -1;
-        }
-
-        float fractionRendered = Math.min(sx * sy, 1.0f);
-        ProtoLog.v(WM_DEBUG_TPL, "fractionRendered scale=%f", fractionRendered);
-
-        float boundsOverSourceW = screenBounds.width() / (float) contentSize.getWidth();
-        float boundsOverSourceH = screenBounds.height() / (float) contentSize.getHeight();
-        fractionRendered *= boundsOverSourceW * boundsOverSourceH;
-        ProtoLog.v(WM_DEBUG_TPL, "fractionRendered boundsOverSource=%f", fractionRendered);
-        // Compute the size of all the rects since they may be disconnected.
-        float[] visibleSize = new float[1];
-        RegionUtils.forEachRect(visibleRegion, rect -> {
-            float size = rect.width() * rect.height();
-            visibleSize[0] += size;
-        });
-
-        fractionRendered *= visibleSize[0] / (screenBounds.width() * screenBounds.height());
-        return fractionRendered;
-    }
-
-    private static class TrustedPresentationInfo {
-        boolean mLastComputedTrustedPresentationState = false;
-        boolean mLastReportedTrustedPresentationState = false;
-        long mEnteredTrustedPresentationStateTime = -1;
-        final TrustedPresentationThresholds mThresholds;
-
-        final ITrustedPresentationListener mListener;
-        final int mId;
-
-        private TrustedPresentationInfo(TrustedPresentationThresholds thresholds, int id,
-                ITrustedPresentationListener listener) {
-            mThresholds = thresholds;
-            mId = id;
-            mListener = listener;
-            checkValid(thresholds);
-        }
-
-        private void checkValid(TrustedPresentationThresholds thresholds) {
-            if (thresholds.mMinAlpha <= 0 || thresholds.mMinFractionRendered <= 0
-                    || thresholds.mStabilityRequirementMs < 1) {
-                throw new IllegalArgumentException(
-                        "TrustedPresentationThresholds values are invalid");
-            }
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 0c57036..0d2c94d 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -303,11 +303,9 @@
 import android.window.ClientWindowFrames;
 import android.window.ISurfaceSyncGroupCompletedListener;
 import android.window.ITaskFpsCallback;
-import android.window.ITrustedPresentationListener;
 import android.window.ScreenCapture;
 import android.window.SystemPerformanceHinter;
 import android.window.TaskSnapshot;
-import android.window.TrustedPresentationThresholds;
 import android.window.WindowContainerToken;
 import android.window.WindowContextInfo;
 
@@ -766,9 +764,6 @@
     private final SurfaceSyncGroupController mSurfaceSyncGroupController =
             new SurfaceSyncGroupController();
 
-    final TrustedPresentationListenerController mTrustedPresentationListenerController =
-            new TrustedPresentationListenerController();
-
     @VisibleForTesting
     final class SettingsObserver extends ContentObserver {
         private final Uri mDisplayInversionEnabledUri =
@@ -7176,7 +7171,6 @@
                 pw.println(separator);
             }
             mSystemPerformanceHinter.dump(pw, "");
-            mTrustedPresentationListenerController.dump(pw);
         }
     }
 
@@ -9777,17 +9771,4 @@
             Binder.restoreCallingIdentity(origId);
         }
     }
-
-    @Override
-    public void registerTrustedPresentationListener(IBinder window,
-            ITrustedPresentationListener listener,
-            TrustedPresentationThresholds thresholds, int id) {
-        mTrustedPresentationListenerController.registerListener(window, listener, thresholds, id);
-    }
-
-    @Override
-    public void unregisterTrustedPresentationListener(ITrustedPresentationListener listener,
-            int id) {
-        mTrustedPresentationListenerController.unregisterListener(listener, id);
-    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 7bc7e2c..e1f1f66 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1189,11 +1189,6 @@
             ProtoLog.v(WM_DEBUG_ADD_REMOVE, "Adding %s to %s", this, parentWindow);
             parentWindow.addChild(this, sWindowSubLayerComparator);
         }
-
-        if (token.mRoundedCornerOverlay) {
-            mWmService.mTrustedPresentationListenerController.addIgnoredWindowTokens(
-                    getWindowToken());
-        }
     }
 
     @Override
@@ -2398,9 +2393,6 @@
         }
 
         mWmService.postWindowRemoveCleanupLocked(this);
-
-        mWmService.mTrustedPresentationListenerController.removeIgnoredWindowTokens(
-                getWindowToken());
     }
 
     @Override
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
new file mode 100644
index 0000000..5febd02
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import android.app.UiModeManager;
+import android.app.WallpaperManager;
+import android.hardware.display.ColorDisplayManager;
+import android.os.PowerManager;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.service.notification.ZenDeviceEffects;
+import android.testing.TestableContext;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class DefaultDeviceEffectsApplierTest {
+
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+    private TestableContext mContext;
+    private DefaultDeviceEffectsApplier mApplier;
+    @Mock PowerManager mPowerManager;
+    @Mock ColorDisplayManager mColorDisplayManager;
+    @Mock UiModeManager mUiModeManager;
+    @Mock WallpaperManager mWallpaperManager;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = new TestableContext(InstrumentationRegistry.getContext(), null);
+        mContext.addMockSystemService(PowerManager.class, mPowerManager);
+        mContext.addMockSystemService(ColorDisplayManager.class, mColorDisplayManager);
+        mContext.addMockSystemService(UiModeManager.class, mUiModeManager);
+        mContext.addMockSystemService(WallpaperManager.class, mWallpaperManager);
+
+        mApplier = new DefaultDeviceEffectsApplier(mContext);
+    }
+
+    @Test
+    public void apply_appliesEffects() {
+        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+
+        ZenDeviceEffects effects = new ZenDeviceEffects.Builder()
+                .setShouldSuppressAmbientDisplay(true)
+                .setShouldDimWallpaper(true)
+                .setShouldDisplayGrayscale(true)
+                .setShouldUseNightMode(true)
+                .build();
+        mApplier.apply(effects);
+
+        verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true));
+        verify(mColorDisplayManager).setSaturationLevel(eq(0));
+        verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f));
+        verifyZeroInteractions(mUiModeManager); // Coming later; adding now so test fails then. :)
+    }
+
+    @Test
+    public void apply_removesEffects() {
+        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+
+        ZenDeviceEffects noEffects = new ZenDeviceEffects.Builder().build();
+        mApplier.apply(noEffects);
+
+        verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(false));
+        verify(mColorDisplayManager).setSaturationLevel(eq(100));
+        verify(mWallpaperManager).setWallpaperDimAmount(eq(0.0f));
+        verifyZeroInteractions(mUiModeManager);
+    }
+
+    @Test
+    public void apply_missingSomeServices_okay() {
+        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+        mContext.addMockSystemService(ColorDisplayManager.class, null);
+        mContext.addMockSystemService(WallpaperManager.class, null);
+
+        ZenDeviceEffects effects = new ZenDeviceEffects.Builder()
+                .setShouldSuppressAmbientDisplay(true)
+                .setShouldDimWallpaper(true)
+                .setShouldDisplayGrayscale(true)
+                .setShouldUseNightMode(true)
+                .build();
+        mApplier.apply(effects);
+
+        verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true));
+        // (And no crash from missing services).
+    }
+}
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 b1fdec9..ef6fced 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -77,9 +77,9 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.notNull;
 import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 import static org.mockito.Mockito.withSettings;
 
@@ -112,6 +112,7 @@
 import android.provider.Settings;
 import android.provider.Settings.Global;
 import android.service.notification.Condition;
+import android.service.notification.DeviceEffectsApplier;
 import android.service.notification.ZenDeviceEffects;
 import android.service.notification.ZenModeConfig;
 import android.service.notification.ZenModeConfig.ScheduleInfo;
@@ -188,8 +189,10 @@
             .appendPath("test")
             .build();
 
-    private static final Condition CONDITION = new Condition(CONDITION_ID, "",
+    private static final Condition CONDITION_TRUE = new Condition(CONDITION_ID, "",
             Condition.STATE_TRUE);
+    private static final Condition CONDITION_FALSE = new Condition(CONDITION_ID, "",
+            Condition.STATE_FALSE);
     private static final String TRIGGER_DESC = "Every Night, 10pm to 6am";
     private static final int TYPE = TYPE_BEDTIME;
     private static final boolean ALLOW_MANUAL = true;
@@ -201,6 +204,7 @@
             = NotificationManager.INTERRUPTION_FILTER_ALARMS;
     private static final boolean ENABLED = true;
     private static final int CREATION_TIME = 123;
+    private static final ZenDeviceEffects NO_EFFECTS = new ZenDeviceEffects.Builder().build();
 
     @Rule
     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -212,6 +216,7 @@
     private TestableLooper mTestableLooper;
     private ZenModeHelper mZenModeHelper;
     private ContentResolver mContentResolver;
+    @Mock DeviceEffectsApplier mDeviceEffectsApplier;
     @Mock AppOpsManager mAppOps;
     TestableFlagResolver mTestFlagResolver = new TestableFlagResolver();
     ZenModeEventLoggerFake mZenModeEventLogger;
@@ -238,8 +243,9 @@
         when(mPackageManager.getResourcesForApplication(anyString())).thenReturn(
                 mResources);
 
-        when(mContext.getSystemService(AppOpsManager.class)).thenReturn(mAppOps);
-        when(mContext.getSystemService(NotificationManager.class)).thenReturn(mNotificationManager);
+        mContext.addMockSystemService(AppOpsManager.class, mAppOps);
+        mContext.addMockSystemService(NotificationManager.class, mNotificationManager);
+
         mConditionProviders = new ConditionProviders(mContext, new UserProfiles(),
                 AppGlobals.getPackageManager());
         mConditionProviders.addSystemProvider(new CountdownConditionProvider());
@@ -609,7 +615,7 @@
         // and we're setting zen mode on
         Settings.Secure.putInt(mContentResolver, Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 1);
         Settings.Secure.putInt(mContentResolver, Settings.Secure.ZEN_SETTINGS_UPDATED, 0);
-        mZenModeHelper.mIsBootComplete = true;
+        mZenModeHelper.mIsSystemServicesReady = true;
         mZenModeHelper.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0);
         mZenModeHelper.setZenModeSetting(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
 
@@ -624,7 +630,7 @@
         // doesn't show upgrade notification if stored settings says don't show
         Settings.Secure.putInt(mContentResolver, Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0);
         Settings.Secure.putInt(mContentResolver, Settings.Secure.ZEN_SETTINGS_UPDATED, 0);
-        mZenModeHelper.mIsBootComplete = true;
+        mZenModeHelper.mIsSystemServicesReady = true;
         mZenModeHelper.setZenModeSetting(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
 
         verify(mNotificationManager, never()).notify(eq(ZenModeHelper.TAG),
@@ -636,7 +642,7 @@
         // doesn't show upgrade notification since zen was already updated
         Settings.Secure.putInt(mContentResolver, Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0);
         Settings.Secure.putInt(mContentResolver, Settings.Secure.ZEN_SETTINGS_UPDATED, 1);
-        mZenModeHelper.mIsBootComplete = true;
+        mZenModeHelper.mIsSystemServicesReady = true;
         mZenModeHelper.setZenModeSetting(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
 
         verify(mNotificationManager, never()).notify(eq(ZenModeHelper.TAG),
@@ -3060,7 +3066,7 @@
         rule.configurationActivity = CONFIG_ACTIVITY;
         rule.component = OWNER;
         rule.conditionId = CONDITION_ID;
-        rule.condition = CONDITION;
+        rule.condition = CONDITION_TRUE;
         rule.enabled = ENABLED;
         rule.creationTime = 123;
         rule.id = "id";
@@ -3350,6 +3356,84 @@
     }
 
     @Test
+    public void testDeviceEffects_applied() {
+        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+        mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
+
+        ZenDeviceEffects effects = new ZenDeviceEffects.Builder()
+                .setShouldSuppressAmbientDisplay(true)
+                .setShouldDimWallpaper(true)
+                .build();
+        String ruleId = addRuleWithEffects(effects);
+        verify(mDeviceEffectsApplier, never()).apply(any());
+
+        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, CUSTOM_PKG_UID, false);
+        mTestableLooper.processAllMessages();
+
+        verify(mDeviceEffectsApplier).apply(eq(effects));
+    }
+
+    @Test
+    public void testDeviceEffects_onDeactivateRule_applied() {
+        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+        mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
+
+        ZenDeviceEffects zde = new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build();
+        String ruleId = addRuleWithEffects(zde);
+        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, CUSTOM_PKG_UID, false);
+        mTestableLooper.processAllMessages();
+        verify(mDeviceEffectsApplier).apply(eq(zde));
+
+        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_FALSE, CUSTOM_PKG_UID, false);
+        mTestableLooper.processAllMessages();
+
+        verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS));
+    }
+
+    @Test
+    public void testDeviceEffects_noChangeToConsolidatedEffects_notApplied() {
+        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+        mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
+
+        ZenDeviceEffects zde = new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build();
+        String ruleId = addRuleWithEffects(zde);
+        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, CUSTOM_PKG_UID, false);
+        mTestableLooper.processAllMessages();
+        verify(mDeviceEffectsApplier).apply(eq(zde));
+
+        // Now create and activate a second rule that doesn't add any more effects.
+        String secondRuleId = addRuleWithEffects(zde);
+        mZenModeHelper.setAutomaticZenRuleState(secondRuleId, CONDITION_TRUE, CUSTOM_PKG_UID,
+                false);
+        mTestableLooper.processAllMessages();
+
+        verifyNoMoreInteractions(mDeviceEffectsApplier);
+    }
+
+    @Test
+    public void testDeviceEffects_activeBeforeApplierProvided_appliedWhenProvided() {
+        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+
+        ZenDeviceEffects zde = new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build();
+        String ruleId = addRuleWithEffects(zde);
+        verify(mDeviceEffectsApplier, never()).apply(any());
+
+        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, CUSTOM_PKG_UID, false);
+        mTestableLooper.processAllMessages();
+        verify(mDeviceEffectsApplier, never()).apply(any());
+
+        mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
+        verify(mDeviceEffectsApplier).apply(eq(zde));
+    }
+
+    private String addRuleWithEffects(ZenDeviceEffects effects) {
+        AutomaticZenRule rule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
+                .setDeviceEffects(effects)
+                .build();
+        return mZenModeHelper.addAutomaticZenRule("pkg", rule, "", CUSTOM_PKG_UID, FROM_APP);
+    }
+
+    @Test
     public void applyGlobalZenModeAsImplicitZenRule_createsImplicitRuleAndActivatesIt() {
         mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
         mZenModeHelper.mConfig.automaticRules.clear();
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index a8d3fa1..c3074bb 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -99,7 +99,7 @@
             android:theme="@style/WhiteBackgroundTheme"
             android:exported="true"/>
 
-        <activity android:name="com.android.server.wm.TrustedPresentationListenerTest$TestActivity"
+        <activity android:name="com.android.server.wm.TrustedPresentationCallbackTest$TestActivity"
             android:exported="true"
             android:showWhenLocked="true"
             android:turnScreenOn="true" />
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index afea811..4d4d397 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -242,6 +242,110 @@
     }
 
     @Test
+    public void backTypeCrossActivityInTaskFragment() {
+        final Task task = createTask(mDefaultDisplay);
+        final TaskFragment tf1 = createTaskFragmentWithActivity(task);
+        final TaskFragment tf2 = createTaskFragmentWithActivity(task);
+        final ArrayList<ActivityRecord> outPrevActivities = new ArrayList<>();
+
+        ActivityRecord prevAr = tf1.getTopMostActivity();
+        ActivityRecord topAr = tf2.getTopMostActivity();
+        boolean predictable;
+
+        // Stacked + no Companion => predict for previous activity.
+        // TF2
+        // TF1
+        predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr,
+                outPrevActivities);
+        assertTrue(outPrevActivities.contains(prevAr));
+        assertTrue(predictable);
+        outPrevActivities.clear();
+
+        // Stacked + companion => predict for previous task
+        tf2.setCompanionTaskFragment(tf1);
+        predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr,
+                outPrevActivities);
+        assertTrue(outPrevActivities.isEmpty());
+        assertTrue(predictable);
+        tf2.setCompanionTaskFragment(null);
+
+        // Adjacent + no companion => unable to predict
+        // TF1 | TF2
+        tf1.setAdjacentTaskFragment(tf2);
+        tf2.setAdjacentTaskFragment(tf1);
+        predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr,
+                outPrevActivities);
+        assertTrue(outPrevActivities.isEmpty());
+        assertFalse(predictable);
+        predictable = BackNavigationController.getAnimatablePrevActivities(task, prevAr,
+                outPrevActivities);
+        assertTrue(outPrevActivities.isEmpty());
+        assertFalse(predictable);
+
+        // Adjacent + companion => predict for previous task
+        tf1.setCompanionTaskFragment(tf2);
+        tf2.setCompanionTaskFragment(tf1);
+        predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr,
+                outPrevActivities);
+        assertTrue(outPrevActivities.isEmpty());
+        assertTrue(predictable);
+        predictable = BackNavigationController.getAnimatablePrevActivities(task, prevAr,
+                outPrevActivities);
+        assertTrue(outPrevActivities.isEmpty());
+        assertTrue(predictable);
+        // reset
+        tf1.setAdjacentTaskFragment(null);
+        tf2.setAdjacentTaskFragment(null);
+        tf1.setCompanionTaskFragment(null);
+        tf2.setCompanionTaskFragment(null);
+
+        final TaskFragment tf3 = new TaskFragmentBuilder(mAtm)
+                .createActivityCount(2)
+                .setParentTask(task)
+                .build();
+        topAr = tf3.getTopMostActivity();
+        prevAr = tf3.getBottomMostActivity();
+        // Stacked => predict for previous activity.
+        // TF3
+        // TF2
+        // TF1
+        predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr,
+                outPrevActivities);
+        assertTrue(outPrevActivities.contains(prevAr));
+        assertTrue(predictable);
+        // reset
+        outPrevActivities.clear();
+
+        // Adjacent => predict for previous activity.
+        // TF2 | TF3
+        // TF1
+        tf2.setAdjacentTaskFragment(tf3);
+        tf3.setAdjacentTaskFragment(tf2);
+        predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr,
+                outPrevActivities);
+        assertTrue(outPrevActivities.contains(prevAr));
+        assertTrue(predictable);
+        // reset
+        outPrevActivities.clear();
+        tf2.setAdjacentTaskFragment(null);
+        tf3.setAdjacentTaskFragment(null);
+
+        final TaskFragment tf4 = createTaskFragmentWithActivity(task);
+        // Stacked + companion => predict for previous activity below companion.
+        // Tf4
+        // TF3
+        // TF2
+        // TF1
+        tf4.setCompanionTaskFragment(tf3);
+        tf3.setCompanionTaskFragment(tf4);
+        topAr = tf4.getTopMostActivity();
+        predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr,
+                outPrevActivities);
+        assertTrue(outPrevActivities.contains(tf2.getTopMostActivity()));
+        assertTrue(predictable);
+    }
+
+    @Test
     public void backTypeDialogCloseWhenBackFromDialog() {
         DialogCloseTestCase testCase = createTopTaskWithActivityAndDialog();
         IOnBackInvokedCallback callback = withSystemCallback(testCase.task);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.java b/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.java
new file mode 100644
index 0000000..c5dd447
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.server.wm.ActivityManagerTestBase.createFullscreenActivityScenarioRule;
+import static android.server.wm.BuildUtils.HW_TIMEOUT_MULTIPLIER;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Activity;
+import android.platform.test.annotations.Presubmit;
+import android.server.wm.CtsWindowInfoUtils;
+import android.view.SurfaceControl;
+import android.view.SurfaceControl.TrustedPresentationThresholds;
+
+import androidx.annotation.GuardedBy;
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+
+import com.android.server.wm.utils.CommonUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+
+import java.util.function.Consumer;
+
+/**
+ * TODO (b/287076178): Move these tests to
+ * {@link android.view.surfacecontrol.cts.TrustedPresentationCallbackTest} when API is made public
+ */
+@Presubmit
+public class TrustedPresentationCallbackTest {
+    private static final String TAG = "TrustedPresentationCallbackTest";
+    private static final int STABILITY_REQUIREMENT_MS = 500;
+    private static final long WAIT_TIME_MS = HW_TIMEOUT_MULTIPLIER * 2000L;
+
+    private static final float FRACTION_VISIBLE = 0.1f;
+
+    private final Object mResultsLock = new Object();
+    @GuardedBy("mResultsLock")
+    private boolean mResult;
+    @GuardedBy("mResultsLock")
+    private boolean mReceivedResults;
+
+    @Rule
+    public TestName mName = new TestName();
+
+    @Rule
+    public ActivityScenarioRule<TestActivity> mActivityRule = createFullscreenActivityScenarioRule(
+            TestActivity.class);
+
+    private TestActivity mActivity;
+
+    @Before
+    public void setup() {
+        mActivityRule.getScenario().onActivity(activity -> mActivity = activity);
+    }
+
+    @After
+    public void tearDown() {
+        CommonUtils.waitUntilActivityRemoved(mActivity);
+    }
+
+    @Test
+    public void testAddTrustedPresentationListenerOnWindow() throws InterruptedException {
+        TrustedPresentationThresholds thresholds = new TrustedPresentationThresholds(
+                1 /* minAlpha */, FRACTION_VISIBLE, STABILITY_REQUIREMENT_MS);
+        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+        mActivity.getWindow().getRootSurfaceControl().addTrustedPresentationCallback(t, thresholds,
+                Runnable::run, inTrustedPresentationState -> {
+                    synchronized (mResultsLock) {
+                        mResult = inTrustedPresentationState;
+                        mReceivedResults = true;
+                        mResultsLock.notify();
+                    }
+                });
+        t.apply();
+        synchronized (mResultsLock) {
+            assertResults();
+        }
+    }
+
+    @Test
+    public void testRemoveTrustedPresentationListenerOnWindow() throws InterruptedException {
+        TrustedPresentationThresholds thresholds = new TrustedPresentationThresholds(
+                1 /* minAlpha */, FRACTION_VISIBLE, STABILITY_REQUIREMENT_MS);
+        Consumer<Boolean> trustedPresentationCallback = inTrustedPresentationState -> {
+            synchronized (mResultsLock) {
+                mResult = inTrustedPresentationState;
+                mReceivedResults = true;
+                mResultsLock.notify();
+            }
+        };
+        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+        mActivity.getWindow().getRootSurfaceControl().addTrustedPresentationCallback(t, thresholds,
+                Runnable::run, trustedPresentationCallback);
+        t.apply();
+
+        synchronized (mResultsLock) {
+            if (!mReceivedResults) {
+                mResultsLock.wait(WAIT_TIME_MS);
+            }
+            assertResults();
+            // reset the state
+            mReceivedResults = false;
+        }
+
+        mActivity.getWindow().getRootSurfaceControl().removeTrustedPresentationCallback(t,
+                trustedPresentationCallback);
+        t.apply();
+
+        synchronized (mResultsLock) {
+            if (!mReceivedResults) {
+                mResultsLock.wait(WAIT_TIME_MS);
+            }
+            // Ensure we waited the full time and never received a notify on the result from the
+            // callback.
+            assertFalse("Should never have received a callback", mReceivedResults);
+            // results shouldn't have changed.
+            assertTrue(mResult);
+        }
+    }
+
+    @GuardedBy("mResultsLock")
+    private void assertResults() throws InterruptedException {
+        mResultsLock.wait(WAIT_TIME_MS);
+
+        if (!mReceivedResults) {
+            CtsWindowInfoUtils.dumpWindowsOnScreen(TAG, "test " + mName.getMethodName());
+        }
+        // Make sure we received the results and not just timed out
+        assertTrue("Timed out waiting for results", mReceivedResults);
+        assertTrue(mResult);
+    }
+
+    public static class TestActivity extends Activity {
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationListenerTest.java b/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationListenerTest.java
deleted file mode 100644
index 96b66bf..0000000
--- a/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationListenerTest.java
+++ /dev/null
@@ -1,267 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static android.server.wm.ActivityManagerTestBase.createFullscreenActivityScenarioRule;
-import static android.server.wm.BuildUtils.HW_TIMEOUT_MULTIPLIER;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertThrows;
-import static org.junit.Assert.fail;
-
-import android.app.Activity;
-import android.os.SystemClock;
-import android.platform.test.annotations.Presubmit;
-import android.server.wm.CtsWindowInfoUtils;
-import android.util.AndroidRuntimeException;
-import android.util.Log;
-import android.view.SurfaceControl;
-import android.view.SurfaceControlViewHost;
-import android.view.View;
-import android.view.WindowManager;
-import android.window.TrustedPresentationThresholds;
-
-import androidx.annotation.GuardedBy;
-import androidx.annotation.NonNull;
-import androidx.test.ext.junit.rules.ActivityScenarioRule;
-
-import com.android.server.wm.utils.CommonUtils;
-
-import junit.framework.Assert;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestName;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.function.Consumer;
-
-/**
- * TODO (b/287076178): Move these tests to
- * {@link android.view.surfacecontrol.cts.TrustedPresentationListenerTest} when API is made public
- */
-@Presubmit
-public class TrustedPresentationListenerTest {
-    private static final String TAG = "TrustedPresentationListenerTest";
-    private static final int STABILITY_REQUIREMENT_MS = 500;
-    private static final long WAIT_TIME_MS = HW_TIMEOUT_MULTIPLIER * 2000L;
-
-    private static final float FRACTION_VISIBLE = 0.1f;
-
-    private final List<Boolean> mResults = Collections.synchronizedList(new ArrayList<>());
-    private CountDownLatch mReceivedResults = new CountDownLatch(1);
-
-    private TrustedPresentationThresholds mThresholds = new TrustedPresentationThresholds(
-            1 /* minAlpha */, FRACTION_VISIBLE, STABILITY_REQUIREMENT_MS);
-
-    @Rule
-    public TestName mName = new TestName();
-
-    @Rule
-    public ActivityScenarioRule<TestActivity> mActivityRule = createFullscreenActivityScenarioRule(
-            TestActivity.class);
-
-    private TestActivity mActivity;
-
-    private SurfaceControlViewHost.SurfacePackage mSurfacePackage = null;
-
-    @Before
-    public void setup() {
-        mActivityRule.getScenario().onActivity(activity -> mActivity = activity);
-        mDefaultListener = new Listener(mReceivedResults);
-    }
-
-    @After
-    public void tearDown() {
-        if (mSurfacePackage != null) {
-            new SurfaceControl.Transaction().remove(mSurfacePackage.getSurfaceControl()).apply(
-                    true);
-            mSurfacePackage.release();
-        }
-        CommonUtils.waitUntilActivityRemoved(mActivity);
-
-    }
-
-    private class Listener implements Consumer<Boolean> {
-        final CountDownLatch mLatch;
-
-        Listener(CountDownLatch latch) {
-            mLatch = latch;
-        }
-
-        @Override
-        public void accept(Boolean inTrustedPresentationState) {
-            Log.d(TAG, "onTrustedPresentationChanged " + inTrustedPresentationState);
-            mResults.add(inTrustedPresentationState);
-            mLatch.countDown();
-        }
-    }
-
-    private Consumer<Boolean> mDefaultListener;
-
-    @Test
-    public void testAddTrustedPresentationListenerOnWindow() {
-        WindowManager windowManager = mActivity.getSystemService(WindowManager.class);
-        windowManager.registerTrustedPresentationListener(
-                mActivity.getWindow().getDecorView().getWindowToken(), mThresholds, Runnable::run,
-                mDefaultListener);
-        assertResults(List.of(true));
-    }
-
-    @Test
-    public void testRemoveTrustedPresentationListenerOnWindow() throws InterruptedException {
-        WindowManager windowManager = mActivity.getSystemService(WindowManager.class);
-        windowManager.registerTrustedPresentationListener(
-                mActivity.getWindow().getDecorView().getWindowToken(), mThresholds, Runnable::run,
-                mDefaultListener);
-        assertResults(List.of(true));
-        // reset the latch
-        mReceivedResults = new CountDownLatch(1);
-
-        windowManager.unregisterTrustedPresentationListener(mDefaultListener);
-        mReceivedResults.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
-        // Ensure we waited the full time and never received a notify on the result from the
-        // callback.
-        assertEquals("Should never have received a callback", mReceivedResults.getCount(), 1);
-        // results shouldn't have changed.
-        assertEquals(mResults, List.of(true));
-    }
-
-    @Test
-    public void testRemovingUnknownListenerIsANoop() {
-        WindowManager windowManager = mActivity.getSystemService(WindowManager.class);
-        assertNotNull(windowManager);
-        windowManager.unregisterTrustedPresentationListener(mDefaultListener);
-    }
-
-    @Test
-    public void testAddDuplicateListenerThrowsException() {
-        WindowManager windowManager = mActivity.getSystemService(WindowManager.class);
-        assertNotNull(windowManager);
-        windowManager.registerTrustedPresentationListener(
-                mActivity.getWindow().getDecorView().getWindowToken(), mThresholds,
-                Runnable::run, mDefaultListener);
-        assertThrows(AndroidRuntimeException.class,
-                () -> windowManager.registerTrustedPresentationListener(
-                        mActivity.getWindow().getDecorView().getWindowToken(), mThresholds,
-                        Runnable::run, mDefaultListener));
-    }
-
-    @Test
-    public void testAddDuplicateThresholds() {
-        mReceivedResults = new CountDownLatch(2);
-        mDefaultListener = new Listener(mReceivedResults);
-        WindowManager windowManager = mActivity.getSystemService(WindowManager.class);
-        windowManager.registerTrustedPresentationListener(
-                mActivity.getWindow().getDecorView().getWindowToken(), mThresholds,
-                Runnable::run, mDefaultListener);
-
-        Consumer<Boolean> mNewListener = new Listener(mReceivedResults);
-
-        windowManager.registerTrustedPresentationListener(
-                mActivity.getWindow().getDecorView().getWindowToken(), mThresholds,
-                Runnable::run, mNewListener);
-        assertResults(List.of(true, true));
-    }
-
-    private void waitForViewAttach(View view) {
-        final CountDownLatch viewAttached = new CountDownLatch(1);
-        view.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
-            @Override
-            public void onViewAttachedToWindow(@NonNull View v) {
-                viewAttached.countDown();
-            }
-
-            @Override
-            public void onViewDetachedFromWindow(@NonNull View v) {
-
-            }
-        });
-        try {
-            viewAttached.await(2000, TimeUnit.MILLISECONDS);
-        } catch (InterruptedException e) {
-            throw new RuntimeException(e);
-        }
-        if (!wait(viewAttached, 2000 /* waitTimeMs */)) {
-            fail("Couldn't attach view=" + view);
-        }
-    }
-
-    @Test
-    public void testAddListenerToScvh() {
-        WindowManager windowManager = mActivity.getSystemService(WindowManager.class);
-
-        var embeddedView = new View(mActivity);
-        mActivityRule.getScenario().onActivity(activity -> {
-            var attachedSurfaceControl =
-                    mActivity.getWindow().getDecorView().getRootSurfaceControl();
-            var scvh = new SurfaceControlViewHost(mActivity, mActivity.getDisplay(),
-                    attachedSurfaceControl.getHostToken());
-            mSurfacePackage = scvh.getSurfacePackage();
-            scvh.setView(embeddedView, mActivity.getWindow().getDecorView().getWidth(),
-                    mActivity.getWindow().getDecorView().getHeight());
-            attachedSurfaceControl.buildReparentTransaction(
-                    mSurfacePackage.getSurfaceControl());
-        });
-
-        waitForViewAttach(embeddedView);
-        windowManager.registerTrustedPresentationListener(embeddedView.getWindowToken(),
-                mThresholds,
-                Runnable::run, mDefaultListener);
-
-        assertResults(List.of(true));
-    }
-
-    private boolean wait(CountDownLatch latch, long waitTimeMs) {
-        while (true) {
-            long now = SystemClock.uptimeMillis();
-            try {
-                return latch.await(waitTimeMs, TimeUnit.MILLISECONDS);
-            } catch (InterruptedException e) {
-                long elapsedTime = SystemClock.uptimeMillis() - now;
-                waitTimeMs = Math.max(0, waitTimeMs - elapsedTime);
-            }
-        }
-
-    }
-
-    @GuardedBy("mResultsLock")
-    private void assertResults(List<Boolean> results) {
-        if (!wait(mReceivedResults, WAIT_TIME_MS)) {
-            try {
-                CtsWindowInfoUtils.dumpWindowsOnScreen(TAG, "test " + mName.getMethodName());
-            } catch (InterruptedException e) {
-                Log.d(TAG, "Couldn't dump windows", e);
-            }
-            Assert.fail("Timed out waiting for results mReceivedResults.count="
-                    + mReceivedResults.getCount() + "mReceivedResults=" + mReceivedResults);
-        }
-
-        // Make sure we received the results
-        assertEquals(results.toArray(), mResults.toArray());
-    }
-
-    public static class TestActivity extends Activity {
-    }
-}
diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java
index b3eb285..8b44579 100644
--- a/services/usb/java/com/android/server/usb/UsbHostManager.java
+++ b/services/usb/java/com/android/server/usb/UsbHostManager.java
@@ -221,9 +221,7 @@
                 for (int line = 0; line < length / kDumpBytesPerLine; line++) {
                     StringBuilder sb = new StringBuilder();
                     for (int offset = 0; offset < kDumpBytesPerLine; offset++) {
-                        sb.append("0x")
-                            .append(String.format("0x%02X", mDescriptors[dataOffset++]))
-                            .append(" ");
+                        sb.append(String.format("0x%02X", mDescriptors[dataOffset++])).append(" ");
                     }
                     pw.println(sb.toString());
                 }
@@ -231,9 +229,7 @@
                 // remainder
                 StringBuilder sb = new StringBuilder();
                 while (dataOffset < length) {
-                    sb.append("0x")
-                        .append(String.format("0x%02X", mDescriptors[dataOffset++]))
-                        .append(" ");
+                    sb.append(String.format("0x%02X", mDescriptors[dataOffset++])).append(" ");
                 }
                 pw.println(sb.toString());
             } else {
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt
index e8389d19..0dce870 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt
@@ -43,8 +43,6 @@
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 open class RotateSplitNoChangeTest(flicker: LegacyFlickerTest) : RotationTransition(flicker) {
-
-    override val testApp = ActivityEmbeddingAppHelper(instrumentation)
     override val transition: FlickerBuilder.() -> Unit
         get() = {
             super.transition(this)
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotationTransition.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotationTransition.kt
index 1123c5b..f6d51f9 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotationTransition.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotationTransition.kt
@@ -18,17 +18,14 @@
 
 import android.platform.test.annotations.Presubmit
 import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.apphelpers.StandardAppHelper
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
-import com.android.server.wm.flicker.BaseTest
+import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
 import com.android.server.wm.flicker.helpers.setRotation
 import org.junit.Test
 
 /** Base class for app rotation tests */
-abstract class RotationTransition(flicker: LegacyFlickerTest) : BaseTest(flicker) {
-    protected abstract val testApp: StandardAppHelper
-
+abstract class RotationTransition(flicker: LegacyFlickerTest) : ActivityEmbeddingTestBase(flicker) {
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit = {
         setup { this.setRotation(flicker.scenario.startRotation) }
diff --git a/tests/InputScreenshotTest/robotests/assets/phone/light_landscape_layout-preview.png b/tests/InputScreenshotTest/robotests/assets/phone/light_landscape_layout-preview.png
index baf204a..a117599d 100644
--- a/tests/InputScreenshotTest/robotests/assets/phone/light_landscape_layout-preview.png
+++ b/tests/InputScreenshotTest/robotests/assets/phone/light_landscape_layout-preview.png
Binary files differ
diff --git a/tests/InputScreenshotTest/robotests/assets/phone/light_portrait_layout-preview.png b/tests/InputScreenshotTest/robotests/assets/phone/light_portrait_layout-preview.png
index deb3cee..538abe8 100644
--- a/tests/InputScreenshotTest/robotests/assets/phone/light_portrait_layout-preview.png
+++ b/tests/InputScreenshotTest/robotests/assets/phone/light_portrait_layout-preview.png
Binary files differ
diff --git a/tests/InputScreenshotTest/robotests/assets/tablet/dark_portrait_layout-preview.png b/tests/InputScreenshotTest/robotests/assets/tablet/dark_portrait_layout-preview.png
index 34e25f7..79a1d6b 100644
--- a/tests/InputScreenshotTest/robotests/assets/tablet/dark_portrait_layout-preview.png
+++ b/tests/InputScreenshotTest/robotests/assets/tablet/dark_portrait_layout-preview.png
Binary files differ