Merge "Replace getCurrentUser() calls with UserTracker"
diff --git a/cmds/idmap2/Android.bp b/cmds/idmap2/Android.bp
index 4f8faca..7a08cbd 100644
--- a/cmds/idmap2/Android.bp
+++ b/cmds/idmap2/Android.bp
@@ -222,6 +222,7 @@
     },
     data: [
         "tests/data/**/*.apk",
+        "tests/data/**/*.png",
     ],
     compile_multilib: "first",
     test_options: {
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp
index 4431164..10947dc 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.cpp
+++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp
@@ -39,6 +39,7 @@
 #include "idmap2/PrettyPrintVisitor.h"
 #include "idmap2/Result.h"
 #include "idmap2/SysTrace.h"
+#include <fcntl.h>
 
 using android::base::StringPrintf;
 using android::binder::Status;
@@ -238,6 +239,9 @@
     if (res.dataType == Res_value::TYPE_STRING) {
       builder.SetResourceValue(res.resourceName, res.dataType, res.stringData.value(),
             res.configuration.value_or(std::string()));
+    } else if (res.binaryData.has_value()) {
+      builder.SetResourceValue(res.resourceName, res.binaryData->get(),
+            res.configuration.value_or(std::string()));
     } else {
       builder.SetResourceValue(res.resourceName, res.dataType, res.data,
             res.configuration.value_or(std::string()));
@@ -264,6 +268,7 @@
                              file_name.c_str(), kMaxFileNameLength));
     }
   } while (std::filesystem::exists(path));
+  builder.setFrroPath(path);
 
   const uid_t uid = IPCThreadState::self()->getCallingUid();
   if (!UidHasWriteAccessToPath(uid, path)) {
diff --git a/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
index c773e11..3ad6d58 100644
--- a/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
+++ b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
@@ -24,5 +24,6 @@
     int dataType;
     int data;
     @nullable @utf8InCpp String stringData;
+    @nullable ParcelFileDescriptor binaryData;
     @nullable @utf8InCpp String configuration;
 }
\ No newline at end of file
diff --git a/cmds/idmap2/include/idmap2/FabricatedOverlay.h b/cmds/idmap2/include/idmap2/FabricatedOverlay.h
index 05b0618..9f57710 100644
--- a/cmds/idmap2/include/idmap2/FabricatedOverlay.h
+++ b/cmds/idmap2/include/idmap2/FabricatedOverlay.h
@@ -28,6 +28,7 @@
 
 #include "idmap2/ResourceContainer.h"
 #include "idmap2/Result.h"
+#include <binder/ParcelFileDescriptor.h>
 
 namespace android::idmap2 {
 
@@ -45,6 +46,15 @@
                               const std::string& data_string_value,
                               const std::string& configuration);
 
+    Builder& SetResourceValue(const std::string& resource_name,
+                              std::optional<android::base::borrowed_fd>&& binary_value,
+                              const std::string& configuration);
+
+    inline Builder& setFrroPath(std::string frro_path) {
+      frro_path_ = std::move(frro_path);
+      return *this;
+    }
+
     WARN_UNUSED Result<FabricatedOverlay> Build();
 
    private:
@@ -53,6 +63,7 @@
       DataType data_type;
       DataValue data_value;
       std::string data_string_value;
+      std::optional<android::base::borrowed_fd> data_binary_value;
       std::string configuration;
     };
 
@@ -60,6 +71,7 @@
     std::string name_;
     std::string target_package_name_;
     std::string target_overlayable_;
+    std::string frro_path_;
     std::vector<Entry> entries_;
   };
 
@@ -79,10 +91,14 @@
 
   explicit FabricatedOverlay(pb::FabricatedOverlay&& overlay,
                              std::string&& string_pool_data_,
+                             std::vector<android::base::borrowed_fd> binary_files_,
+                             off_t total_binary_bytes_,
                              std::optional<uint32_t> crc_from_disk = {});
 
   pb::FabricatedOverlay overlay_pb_;
   std::string string_pool_data_;
+  std::vector<android::base::borrowed_fd> binary_files_;
+  uint32_t total_binary_bytes_;
   std::optional<uint32_t> crc_from_disk_;
   mutable std::optional<SerializedData> data_;
 
diff --git a/cmds/idmap2/include/idmap2/ResourceUtils.h b/cmds/idmap2/include/idmap2/ResourceUtils.h
index af4dd89..2214a83 100644
--- a/cmds/idmap2/include/idmap2/ResourceUtils.h
+++ b/cmds/idmap2/include/idmap2/ResourceUtils.h
@@ -19,6 +19,7 @@
 
 #include <optional>
 #include <string>
+#include <android-base/unique_fd.h>
 
 #include "androidfw/AssetManager2.h"
 #include "idmap2/Result.h"
@@ -41,6 +42,7 @@
   DataType data_type;
   DataValue data_value;
   std::string data_string_value;
+  std::optional<android::base::borrowed_fd> data_binary_value;
 };
 
 struct TargetValueWithConfig {
diff --git a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
index bde9b0b..d517e29 100644
--- a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
+++ b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
@@ -16,6 +16,10 @@
 
 #include "idmap2/FabricatedOverlay.h"
 
+#include <sys/stat.h>   // umask
+#include <sys/types.h>  // umask
+
+#include <android-base/file.h>
 #include <androidfw/ResourceUtils.h>
 #include <androidfw/StringPool.h>
 #include <google/protobuf/io/coded_stream.h>
@@ -51,9 +55,13 @@
 
 FabricatedOverlay::FabricatedOverlay(pb::FabricatedOverlay&& overlay,
                                      std::string&& string_pool_data,
+                                     std::vector<android::base::borrowed_fd> binary_files,
+                                     off_t total_binary_bytes,
                                      std::optional<uint32_t> crc_from_disk)
     : overlay_pb_(std::forward<pb::FabricatedOverlay>(overlay)),
     string_pool_data_(std::move(string_pool_data)),
+    binary_files_(std::move(binary_files)),
+    total_binary_bytes_(total_binary_bytes),
     crc_from_disk_(crc_from_disk) {
 }
 
@@ -72,14 +80,23 @@
 FabricatedOverlay::Builder& FabricatedOverlay::Builder::SetResourceValue(
     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, "", configuration});
+  entries_.emplace_back(
+      Entry{resource_name, data_type, data_value, "", std::nullopt, configuration});
   return *this;
 }
 
 FabricatedOverlay::Builder& FabricatedOverlay::Builder::SetResourceValue(
     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, configuration});
+  entries_.emplace_back(
+      Entry{resource_name, data_type, 0, data_string_value, std::nullopt, configuration});
+  return *this;
+}
+
+FabricatedOverlay::Builder& FabricatedOverlay::Builder::SetResourceValue(
+    const std::string& resource_name, std::optional<android::base::borrowed_fd>&& binary_value,
+    const std::string& configuration) {
+  entries_.emplace_back(Entry{resource_name, 0, 0, "", binary_value, configuration});
   return *this;
 }
 
@@ -135,7 +152,7 @@
     }
 
     value->second = TargetValue{res_entry.data_type, res_entry.data_value,
-        res_entry.data_string_value};
+        res_entry.data_string_value, res_entry.data_binary_value};
   }
 
   pb::FabricatedOverlay overlay_pb;
@@ -144,6 +161,11 @@
   overlay_pb.set_target_package_name(target_package_name_);
   overlay_pb.set_target_overlayable(target_overlayable_);
 
+  std::vector<android::base::borrowed_fd> binary_files;
+  size_t total_binary_bytes = 0;
+  // 16 for the number of bytes in the frro file before the binary data
+  const size_t FRRO_HEADER_SIZE = 16;
+
   for (auto& package : package_map) {
     auto package_pb = overlay_pb.add_packages();
     package_pb->set_name(package.first);
@@ -162,6 +184,20 @@
           if (value.second.data_type == Res_value::TYPE_STRING) {
             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);
+              struct stat s;
+              if (fstat(value.second.data_binary_value->get(), &s) == -1) {
+                return Error("unable to get size of binary file: %d", errno);
+              }
+              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> (s.st_size));
+              total_binary_bytes += s.st_size;
+              binary_files.emplace_back(value.second.data_binary_value->get());
+              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);
           }
@@ -169,10 +205,10 @@
       }
     }
   }
-
   android::BigBuffer string_buffer(kBufferSize);
   android::StringPool::FlattenUtf8(&string_buffer, string_pool, nullptr);
-  return FabricatedOverlay(std::move(overlay_pb), string_buffer.to_string());
+  return FabricatedOverlay(std::move(overlay_pb), string_buffer.to_string(),
+      std::move(binary_files), total_binary_bytes);
 }
 
 Result<FabricatedOverlay> FabricatedOverlay::FromBinaryStream(std::istream& stream) {
@@ -190,7 +226,7 @@
     return Error("Failed to read fabricated overlay version.");
   }
 
-  if (version != 1 && version != 2) {
+  if (version < 1 || version > 3) {
     return Error("Invalid fabricated overlay version '%u'.", version);
   }
 
@@ -201,7 +237,14 @@
 
   pb::FabricatedOverlay overlay{};
   std::string sp_data;
-  if (version == 2) {
+  uint32_t total_binary_bytes;
+  if (version == 3) {
+    if (!Read32(stream, &total_binary_bytes)) {
+      return Error("Failed read total binary bytes.");
+    }
+    stream.seekg(total_binary_bytes, std::istream::cur);
+  }
+  if (version >= 2) {
     uint32_t sp_size;
     if (!Read32(stream, &sp_size)) {
       return Error("Failed read string pool size.");
@@ -211,20 +254,15 @@
       return Error("Failed to read string pool.");
     }
     sp_data = buf;
-
-    if (!overlay.ParseFromIstream(&stream)) {
-      return Error("Failed read fabricated overlay proto.");
-    }
-  } else {
-    if (!overlay.ParseFromIstream(&stream)) {
-      return Error("Failed read fabricated overlay proto.");
-    }
+  }
+  if (!overlay.ParseFromIstream(&stream)) {
+    return Error("Failed read fabricated overlay proto.");
   }
 
   // If the proto version is the latest version, then the contents of the proto must be the same
   // when the proto is re-serialized; otherwise, the crc must be calculated because migrating the
   // proto to the latest version will likely change the contents of the fabricated overlay.
-  return FabricatedOverlay(std::move(overlay), std::move(sp_data),
+  return FabricatedOverlay(std::move(overlay), std::move(sp_data), {}, total_binary_bytes,
                            version == kFabricatedOverlayCurrentVersion
                                                    ? std::optional<uint32_t>(crc)
                                                    : std::nullopt);
@@ -274,6 +312,14 @@
   Write32(stream, kFabricatedOverlayMagic);
   Write32(stream, kFabricatedOverlayCurrentVersion);
   Write32(stream, (*data)->pb_crc);
+  Write32(stream, total_binary_bytes_);
+  std::string file_contents;
+  for (const android::base::borrowed_fd fd : binary_files_) {
+    if (!ReadFdToString(fd, &file_contents)) {
+      return Error("Failed to read binary file data.");
+    }
+    stream.write(file_contents.data(), file_contents.length());
+  }
   Write32(stream, (*data)->sp_data.length());
   stream.write((*data)->sp_data.data(), (*data)->sp_data.length());
   if (stream.bad()) {
diff --git a/cmds/idmap2/tests/FabricatedOverlayTests.cpp b/cmds/idmap2/tests/FabricatedOverlayTests.cpp
index e804c87..e13a0eb 100644
--- a/cmds/idmap2/tests/FabricatedOverlayTests.cpp
+++ b/cmds/idmap2/tests/FabricatedOverlayTests.cpp
@@ -17,6 +17,7 @@
 #include <android-base/file.h>
 #include <gtest/gtest.h>
 #include <idmap2/FabricatedOverlay.h>
+#include "TestHelpers.h"
 
 #include <fstream>
 #include <utility>
@@ -41,6 +42,10 @@
 }
 
 TEST(FabricatedOverlayTests, SetResourceValue) {
+  auto path = GetTestDataPath() + "/overlay/res/drawable/android.png";
+  auto fd = android::base::unique_fd(::open(path.c_str(), O_RDONLY | O_CLOEXEC));
+  ASSERT_TRUE(fd > 0) << "errno " << errno << " for path " << path;
+
   auto overlay =
       FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "com.example.target")
           .SetResourceValue(
@@ -54,6 +59,8 @@
               Res_value::TYPE_STRING,
               "foobar",
               "en-rUS-normal-xxhdpi-v21")
+          .SetResourceValue("com.example.target:drawable/dr1", fd, "port-xxhdpi-v7")
+          .setFrroPath("/foo/bar/biz.frro")
           .Build();
   ASSERT_TRUE(overlay);
   auto container = FabricatedOverlayContainer::FromOverlay(std::move(*overlay));
@@ -67,19 +74,28 @@
 
   auto pairs = container->GetOverlayData(*info);
   ASSERT_TRUE(pairs);
-  ASSERT_EQ(4U, pairs->pairs.size());
+  ASSERT_EQ(5U, pairs->pairs.size());
   auto string_pool = ResStringPool(pairs->string_pool_data->data.get(),
                                         pairs->string_pool_data->data_length, false);
 
   auto& it = pairs->pairs[0];
-  ASSERT_EQ("com.example.target:integer/int1", it.resource_name);
+  ASSERT_EQ("com.example.target:drawable/dr1", it.resource_name);
   auto entry = std::get_if<TargetValueWithConfig>(&it.value);
   ASSERT_NE(nullptr, entry);
+  ASSERT_EQ(std::string("frro://foo/bar/biz.frro?offset=16&size=8341"),
+      string_pool.string8At(entry->value.data_value).value_or(""));
+  ASSERT_EQ(Res_value::TYPE_STRING, entry->value.data_type);
+  ASSERT_EQ("port-xxhdpi-v7", entry->config);
+
+  it = pairs->pairs[1];
+  ASSERT_EQ("com.example.target:integer/int1", it.resource_name);
+  entry = std::get_if<TargetValueWithConfig>(&it.value);
+  ASSERT_NE(nullptr, entry);
   ASSERT_EQ(1U, entry->value.data_value);
   ASSERT_EQ(Res_value::TYPE_INT_DEC, entry->value.data_type);
   ASSERT_EQ("port", entry->config);
 
-  it = pairs->pairs[1];
+  it = pairs->pairs[2];
   ASSERT_EQ("com.example.target:string/int3", it.resource_name);
   entry = std::get_if<TargetValueWithConfig>(&it.value);
   ASSERT_NE(nullptr, entry);
@@ -87,7 +103,7 @@
   ASSERT_EQ(Res_value::TYPE_REFERENCE, entry->value.data_type);
   ASSERT_EQ("xxhdpi-v7", entry->config);
 
-  it = pairs->pairs[2];
+  it = pairs->pairs[3];
   ASSERT_EQ("com.example.target:string/string1", it.resource_name);
   entry = std::get_if<TargetValueWithConfig>(&it.value);
   ASSERT_NE(nullptr, entry);
@@ -95,7 +111,7 @@
   ASSERT_EQ(std::string("foobar"), string_pool.string8At(entry->value.data_value).value_or(""));
   ASSERT_EQ("en-rUS-normal-xxhdpi-v21", entry->config);
 
-  it = pairs->pairs[3];
+  it = pairs->pairs[4];
   ASSERT_EQ("com.example.target.split:integer/int2", it.resource_name);
   entry = std::get_if<TargetValueWithConfig>(&it.value);
   ASSERT_NE(nullptr, entry);
diff --git a/cmds/idmap2/tests/IdmapTests.cpp b/cmds/idmap2/tests/IdmapTests.cpp
index 7b7dc17..b473f26 100644
--- a/cmds/idmap2/tests/IdmapTests.cpp
+++ b/cmds/idmap2/tests/IdmapTests.cpp
@@ -260,11 +260,17 @@
   auto target = TargetResourceContainer::FromPath(target_apk_path);
   ASSERT_TRUE(target);
 
+  auto path = GetTestDataPath() + "/overlay/res/drawable/android.png";
+  auto fd = android::base::unique_fd(::open(path.c_str(), O_RDONLY | O_CLOEXEC));
+  ASSERT_TRUE(fd > 0) << "errno " << errno << " for path " << path;
+
   auto frro = FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "test.target")
                   .SetOverlayable("TestResources")
                   .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, "port-xxhdpi-v7")
+                  .setFrroPath("/foo/bar/biz.frro")
                   .Build();
 
   ASSERT_TRUE(frro);
@@ -293,14 +299,19 @@
   auto string_pool_data = data->GetStringPoolData();
   auto string_pool = ResStringPool(string_pool_data.data(), string_pool_data.size(), false);
 
+  std::u16string expected_uri = u"frro://foo/bar/biz.frro?offset=16&size=8341";
+  uint32_t uri_index
+      = string_pool.indexOfString(expected_uri.data(), expected_uri.length()).value_or(-1);
 
   const auto& target_inline_entries = data->GetTargetInlineEntries();
-  ASSERT_EQ(target_inline_entries.size(), 3U);
-  ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[0], R::target::integer::int1, "land-xxhdpi-v7",
+  ASSERT_EQ(target_inline_entries.size(), 4U);
+  ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[0], R::target::drawable::dr1, "port-xxhdpi-v7",
+                             Res_value::TYPE_STRING, uri_index);
+  ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[1], R::target::integer::int1, "land-xxhdpi-v7",
                              Res_value::TYPE_INT_DEC, 2U);
-  ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[1], R::target::string::str1, "land",
+  ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[2], R::target::string::str1, "land",
                              Res_value::TYPE_REFERENCE, 0x7f010000);
-  ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[2], R::target::string::str2, "xxhdpi-v7",
+  ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[3], R::target::string::str2, "xxhdpi-v7",
                              Res_value::TYPE_STRING,
                              (uint32_t) (string_pool.indexOfString(u"foobar", 6)).value_or(-1));
 }
diff --git a/cmds/idmap2/tests/R.h b/cmds/idmap2/tests/R.h
index ad998b9..80c062d 100644
--- a/cmds/idmap2/tests/R.h
+++ b/cmds/idmap2/tests/R.h
@@ -26,24 +26,27 @@
 // clang-format off
 namespace R::target {
   namespace integer {  // NOLINT(runtime/indentation_namespace)
-    constexpr ResourceId int1 = 0x7f010000;
+    constexpr ResourceId int1 = 0x7f020000;
+  }
+  namespace drawable {
+    constexpr ResourceId dr1 = 0x7f010000;
   }
   namespace string {  // NOLINT(runtime/indentation_namespace)
-    constexpr ResourceId not_overlayable = 0x7f020003;
-    constexpr ResourceId other = 0x7f020004;
-    constexpr ResourceId policy_actor = 0x7f020005;
-    constexpr ResourceId policy_config_signature = 0x7f020006;
-    constexpr ResourceId policy_odm = 0x7f020007;
-    constexpr ResourceId policy_oem = 0x7f020008;
-    constexpr ResourceId policy_product = 0x7f020009;
-    constexpr ResourceId policy_public = 0x7f02000a;
-    constexpr ResourceId policy_signature = 0x7f02000b;
-    constexpr ResourceId policy_system = 0x7f02000c;
-    constexpr ResourceId policy_system_vendor = 0x7f02000d;
-    constexpr ResourceId str1 = 0x7f02000e;
-    constexpr ResourceId str2 = 0x7f02000f;
-    constexpr ResourceId str3 = 0x7f020010;
-    constexpr ResourceId str4 = 0x7f020011;
+    constexpr ResourceId not_overlayable = 0x7f030003;
+    constexpr ResourceId other = 0x7f030004;
+    constexpr ResourceId policy_actor = 0x7f030005;
+    constexpr ResourceId policy_config_signature = 0x7f030006;
+    constexpr ResourceId policy_odm = 0x7f030007;
+    constexpr ResourceId policy_oem = 0x7f030008;
+    constexpr ResourceId policy_product = 0x7f030009;
+    constexpr ResourceId policy_public = 0x7f03000a;
+    constexpr ResourceId policy_signature = 0x7f03000b;
+    constexpr ResourceId policy_system = 0x7f03000c;
+    constexpr ResourceId policy_system_vendor = 0x7f03000d;
+    constexpr ResourceId str1 = 0x7f03000e;
+    constexpr ResourceId str2 = 0x7f03000f;
+    constexpr ResourceId str3 = 0x7f030010;
+    constexpr ResourceId str4 = 0x7f030011;
   }  // namespace string
 }  // namespace R::target
 
diff --git a/cmds/idmap2/tests/RawPrintVisitorTests.cpp b/cmds/idmap2/tests/RawPrintVisitorTests.cpp
index 7112eeb..68164e2 100644
--- a/cmds/idmap2/tests/RawPrintVisitorTests.cpp
+++ b/cmds/idmap2/tests/RawPrintVisitorTests.cpp
@@ -79,22 +79,22 @@
   ASSERT_CONTAINS_REGEX(ADDRESS "00000000  config count", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "00000004  overlay entry count", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "0000000a  string pool index offset", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "7f010000  target id: integer/int1", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f020000  target id: integer/int1", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "7f010000  overlay id: integer/int1", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "7f02000e  target id: string/str1", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f03000e  target id: string/str1", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "7f02000b  overlay id: string/str1", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "7f020010  target id: string/str3", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f030010  target id: string/str3", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "7f02000c  overlay id: string/str3", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "7f020011  target id: string/str4", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f030011  target id: string/str4", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "7f02000d  overlay id: string/str4", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "7f010000  overlay id: integer/int1", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "7f010000  target id: integer/int1", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f020000  target id: integer/int1", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "7f02000b  overlay id: string/str1", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "7f02000e  target id: string/str1", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f03000e  target id: string/str1", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "7f02000c  overlay id: string/str3", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "7f020010  target id: string/str3", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f030010  target id: string/str3", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "7f02000d  overlay id: string/str4", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "7f020011  target id: string/str4", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f030011  target id: string/str4", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "000000b4  string pool size", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "........  string pool", stream.str());
 }
diff --git a/cmds/idmap2/tests/ResourceMappingTests.cpp b/cmds/idmap2/tests/ResourceMappingTests.cpp
index 016d427..380e462 100644
--- a/cmds/idmap2/tests/ResourceMappingTests.cpp
+++ b/cmds/idmap2/tests/ResourceMappingTests.cpp
@@ -23,6 +23,7 @@
 #include <memory>
 #include <string>
 
+#include <fcntl.h>
 #include "R.h"
 #include "TestConstants.h"
 #include "TestHelpers.h"
@@ -76,7 +77,12 @@
   auto target_map = mapping.GetTargetToOverlayMap();
   auto entry_map = target_map.find(target_resource);
   if (entry_map == target_map.end()) {
-    return Error("Failed to find mapping for target resource");
+    std::string keys;
+    for (const auto &pair : target_map) {
+      keys.append(fmt::format("0x{:x}", pair.first)).append(" ");
+    }
+    return Error(R"(Failed to find mapping for target resource "0x%02x": "%s")",
+        target_resource, keys.c_str());
   }
 
   auto actual_overlay_resource = std::get_if<ResourceId>(&entry_map->second);
@@ -108,7 +114,12 @@
   auto target_map = mapping.GetTargetToOverlayMap();
   auto entry_map = target_map.find(target_resource);
   if (entry_map == target_map.end()) {
-    return Error("Failed to find mapping for target resource");
+    std::string keys;
+    for (const auto &pair : target_map) {
+      keys.append(fmt::format("{:x}", pair.first)).append(" ");
+    }
+    return Error(R"(Failed to find mapping for target resource "0x%02x": "%s")",
+        target_resource, keys.c_str());
   }
 
   auto config_map = std::get_if<ConfigMap>(&entry_map->second);
@@ -193,11 +204,16 @@
 }
 
 TEST(ResourceMappingTests, FabricatedOverlay) {
+  auto path = GetTestDataPath() + "/overlay/res/drawable/android.png";
+  auto fd = android::base::unique_fd(::open(path.c_str(), O_RDONLY | O_CLOEXEC));
+  ASSERT_TRUE(fd > 0) << "errno " << errno << " for path " << path;
   auto frro = FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "test.target")
                   .SetOverlayable("TestResources")
                   .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, "")
+                  .setFrroPath("/foo/bar/biz.frro")
                   .Build();
 
   ASSERT_TRUE(frro);
@@ -214,11 +230,16 @@
   auto string_pool_data = res.GetStringPoolData();
   auto string_pool = ResStringPool(string_pool_data.data(), string_pool_data.size(), false);
 
-  ASSERT_EQ(res.GetTargetToOverlayMap().size(), 3U);
+  std::u16string expected_uri = u"frro://foo/bar/biz.frro?offset=16&size=8341";
+  uint32_t uri_index
+      = string_pool.indexOfString(expected_uri.data(), expected_uri.length()).value_or(-1);
+
+  ASSERT_EQ(res.GetTargetToOverlayMap().size(), 4U);
   ASSERT_EQ(res.GetOverlayToTargetMap().size(), 0U);
   ASSERT_RESULT(MappingExists(res, R::target::string::str1, Res_value::TYPE_REFERENCE, 0x7f010000));
   ASSERT_RESULT(MappingExists(res, R::target::string::str2, Res_value::TYPE_STRING,
                               (uint32_t) (string_pool.indexOfString(u"foobar", 6)).value_or(-1)));
+  ASSERT_RESULT(MappingExists(res, R::target::drawable::dr1, Res_value::TYPE_STRING, uri_index));
   ASSERT_RESULT(MappingExists(res, R::target::integer::int1, Res_value::TYPE_INT_DEC, 2U));
 }
 
diff --git a/cmds/idmap2/tests/TestConstants.h b/cmds/idmap2/tests/TestConstants.h
index d5799ad..794d622 100644
--- a/cmds/idmap2/tests/TestConstants.h
+++ b/cmds/idmap2/tests/TestConstants.h
@@ -19,8 +19,8 @@
 
 namespace android::idmap2::TestConstants {
 
-constexpr const auto TARGET_CRC = 0x7c2d4719;
-constexpr const auto TARGET_CRC_STRING = "7c2d4719";
+constexpr const auto TARGET_CRC = 0xa960a69;
+constexpr const auto TARGET_CRC_STRING = "0a960a69";
 
 constexpr const auto OVERLAY_CRC = 0xb71095cf;
 constexpr const auto OVERLAY_CRC_STRING = "b71095cf";
diff --git a/cmds/idmap2/tests/data/overlay/res/drawable/android.png b/cmds/idmap2/tests/data/overlay/res/drawable/android.png
new file mode 100644
index 0000000..b7317b0
--- /dev/null
+++ b/cmds/idmap2/tests/data/overlay/res/drawable/android.png
Binary files differ
diff --git a/cmds/idmap2/tests/data/target/build b/cmds/idmap2/tests/data/target/build
index e6df742..cd13a7e 100755
--- a/cmds/idmap2/tests/data/target/build
+++ b/cmds/idmap2/tests/data/target/build
@@ -17,5 +17,7 @@
 rm compiled.flata
 
 aapt2 compile res/values/values.xml -o .
-aapt2 link --manifest AndroidManifest.xml -A assets -o target-no-overlayable.apk values_values.arsc.flat
-rm values_values.arsc.flat
\ No newline at end of file
+aapt2 compile res/drawable/dr1.png -o .
+aapt2 link --manifest AndroidManifest.xml -A assets -o target-no-overlayable.apk values_values.arsc.flat drawable_dr1.png.flat
+rm values_values.arsc.flat
+rm drawable_dr1.png.flat
diff --git a/cmds/idmap2/tests/data/target/res/drawable/dr1.png b/cmds/idmap2/tests/data/target/res/drawable/dr1.png
new file mode 100644
index 0000000..1a56e68
--- /dev/null
+++ b/cmds/idmap2/tests/data/target/res/drawable/dr1.png
Binary files differ
diff --git a/cmds/idmap2/tests/data/target/res/values/overlayable.xml b/cmds/idmap2/tests/data/target/res/values/overlayable.xml
index 57e6c43..aac9081 100644
--- a/cmds/idmap2/tests/data/target/res/values/overlayable.xml
+++ b/cmds/idmap2/tests/data/target/res/values/overlayable.xml
@@ -63,6 +63,7 @@
         <item type="string" name="y" />
         <item type="string" name="z" />
         <item type="integer" name="int1" />
+        <item type="drawable" name="dr1" />
     </policy>
 </overlayable>
 
diff --git a/cmds/idmap2/tests/data/target/target-no-overlayable.apk b/cmds/idmap2/tests/data/target/target-no-overlayable.apk
index cc3491d..680eeb6 100644
--- a/cmds/idmap2/tests/data/target/target-no-overlayable.apk
+++ b/cmds/idmap2/tests/data/target/target-no-overlayable.apk
Binary files differ
diff --git a/cmds/idmap2/tests/data/target/target.apk b/cmds/idmap2/tests/data/target/target.apk
index 4a58c5e..145e737 100644
--- a/cmds/idmap2/tests/data/target/target.apk
+++ b/cmds/idmap2/tests/data/target/target.apk
Binary files differ
diff --git a/core/api/current.txt b/core/api/current.txt
index b64afea..b89c3f7 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -3112,10 +3112,13 @@
     method public final void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo);
     method public void setTouchExplorationPassthroughRegion(int, @NonNull android.graphics.Region);
     method public void takeScreenshot(int, @NonNull java.util.concurrent.Executor, @NonNull android.accessibilityservice.AccessibilityService.TakeScreenshotCallback);
+    method public void takeScreenshotOfWindow(int, @NonNull java.util.concurrent.Executor, @NonNull android.accessibilityservice.AccessibilityService.TakeScreenshotCallback);
     field public static final int ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR = 1; // 0x1
     field public static final int ERROR_TAKE_SCREENSHOT_INTERVAL_TIME_SHORT = 3; // 0x3
     field public static final int ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY = 4; // 0x4
+    field public static final int ERROR_TAKE_SCREENSHOT_INVALID_WINDOW = 5; // 0x5
     field public static final int ERROR_TAKE_SCREENSHOT_NO_ACCESSIBILITY_ACCESS = 2; // 0x2
+    field public static final int ERROR_TAKE_SCREENSHOT_SECURE_WINDOW = 6; // 0x6
     field public static final int GESTURE_2_FINGER_DOUBLE_TAP = 20; // 0x14
     field public static final int GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD = 40; // 0x28
     field public static final int GESTURE_2_FINGER_SINGLE_TAP = 19; // 0x13
@@ -5275,6 +5278,13 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.app.ForegroundServiceStartNotAllowedException> CREATOR;
   }
 
+  public final class ForegroundServiceTypeNotAllowedException extends android.app.ServiceStartNotAllowedException implements android.os.Parcelable {
+    ctor public ForegroundServiceTypeNotAllowedException(@NonNull String);
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.ForegroundServiceTypeNotAllowedException> CREATOR;
+  }
+
   @Deprecated public class Fragment implements android.content.ComponentCallbacks2 android.view.View.OnCreateContextMenuListener {
     ctor @Deprecated public Fragment();
     method @Deprecated public void dump(String, java.io.FileDescriptor, java.io.PrintWriter, String[]);
@@ -39928,7 +39938,7 @@
   public abstract class WallpaperService extends android.app.Service {
     ctor public WallpaperService();
     method public final android.os.IBinder onBind(android.content.Intent);
-    method public abstract android.service.wallpaper.WallpaperService.Engine onCreateEngine();
+    method @MainThread public abstract android.service.wallpaper.WallpaperService.Engine onCreateEngine();
     field public static final String SERVICE_INTERFACE = "android.service.wallpaper.WallpaperService";
     field public static final String SERVICE_META_DATA = "android.service.wallpaper";
   }
@@ -39943,20 +39953,20 @@
     method public boolean isPreview();
     method public boolean isVisible();
     method public void notifyColorsChanged();
-    method public void onApplyWindowInsets(android.view.WindowInsets);
-    method public android.os.Bundle onCommand(String, int, int, int, android.os.Bundle, boolean);
-    method @Nullable public android.app.WallpaperColors onComputeColors();
-    method public void onCreate(android.view.SurfaceHolder);
-    method public void onDesiredSizeChanged(int, int);
-    method public void onDestroy();
-    method public void onOffsetsChanged(float, float, float, float, int, int);
-    method public void onSurfaceChanged(android.view.SurfaceHolder, int, int, int);
-    method public void onSurfaceCreated(android.view.SurfaceHolder);
-    method public void onSurfaceDestroyed(android.view.SurfaceHolder);
-    method public void onSurfaceRedrawNeeded(android.view.SurfaceHolder);
-    method public void onTouchEvent(android.view.MotionEvent);
-    method public void onVisibilityChanged(boolean);
-    method public void onZoomChanged(@FloatRange(from=0.0f, to=1.0f) float);
+    method @MainThread public void onApplyWindowInsets(android.view.WindowInsets);
+    method @MainThread public android.os.Bundle onCommand(String, int, int, int, android.os.Bundle, boolean);
+    method @MainThread @Nullable public android.app.WallpaperColors onComputeColors();
+    method @MainThread public void onCreate(android.view.SurfaceHolder);
+    method @MainThread public void onDesiredSizeChanged(int, int);
+    method @MainThread public void onDestroy();
+    method @MainThread public void onOffsetsChanged(float, float, float, float, int, int);
+    method @MainThread public void onSurfaceChanged(android.view.SurfaceHolder, int, int, int);
+    method @MainThread public void onSurfaceCreated(android.view.SurfaceHolder);
+    method @MainThread public void onSurfaceDestroyed(android.view.SurfaceHolder);
+    method @MainThread public void onSurfaceRedrawNeeded(android.view.SurfaceHolder);
+    method @MainThread public void onTouchEvent(android.view.MotionEvent);
+    method @MainThread public void onVisibilityChanged(boolean);
+    method @MainThread public void onZoomChanged(@FloatRange(from=0.0f, to=1.0f) float);
     method public void setOffsetNotificationsEnabled(boolean);
     method public void setTouchEventsEnabled(boolean);
   }
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index ce18745..e928eb5 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -349,6 +349,7 @@
   }
 
   public final class ServiceManager {
+    method @NonNull public static String[] getDeclaredInstances(@NonNull String);
     method public static boolean isDeclared(@NonNull String);
     method @Nullable public static android.os.IBinder waitForDeclaredService(@NonNull String);
     method @Nullable public static android.os.IBinder waitForService(@NonNull String);
@@ -384,6 +385,7 @@
     method public static void traceBegin(long, @NonNull String);
     method public static void traceCounter(long, @NonNull String, int);
     method public static void traceEnd(long);
+    field public static final long TRACE_TAG_AIDL = 16777216L; // 0x1000000L
     field public static final long TRACE_TAG_NETWORK = 2097152L; // 0x200000L
   }
 
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 8385a29..4eeb42f 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -588,6 +588,7 @@
     field public static final String OPSTR_READ_MEDIA_IMAGES = "android:read_media_images";
     field public static final String OPSTR_READ_MEDIA_VIDEO = "android:read_media_video";
     field public static final String OPSTR_READ_MEDIA_VISUAL_USER_SELECTED = "android:read_media_visual_user_selected";
+    field public static final String OPSTR_READ_WRITE_HEALTH_DATA = "android:read_write_health_data";
     field public static final String OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO = "android:receive_ambient_trigger_audio";
     field public static final String OPSTR_RECEIVE_EMERGENCY_BROADCAST = "android:receive_emergency_broadcast";
     field public static final String OPSTR_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO = "android:receive_explicit_user_interaction_audio";
@@ -11924,11 +11925,39 @@
     method public abstract void onStopUpdates();
     method public final void reportPermanentFailure(@NonNull Throwable);
     method public final void reportSuggestion(@NonNull android.service.timezone.TimeZoneProviderSuggestion);
+    method public final void reportSuggestion(@NonNull android.service.timezone.TimeZoneProviderSuggestion, @NonNull android.service.timezone.TimeZoneProviderStatus);
     method public final void reportUncertain();
+    method public final void reportUncertain(@NonNull android.service.timezone.TimeZoneProviderStatus);
     field public static final String PRIMARY_LOCATION_TIME_ZONE_PROVIDER_SERVICE_INTERFACE = "android.service.timezone.PrimaryLocationTimeZoneProviderService";
     field public static final String SECONDARY_LOCATION_TIME_ZONE_PROVIDER_SERVICE_INTERFACE = "android.service.timezone.SecondaryLocationTimeZoneProviderService";
   }
 
+  public final class TimeZoneProviderStatus implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getConnectivityDependencyStatus();
+    method public int getLocationDetectionDependencyStatus();
+    method public int getTimeZoneResolutionOperationStatus();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.timezone.TimeZoneProviderStatus> CREATOR;
+    field public static final int DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT = 4; // 0x4
+    field public static final int DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS = 6; // 0x6
+    field public static final int DEPENDENCY_STATUS_DEGRADED_BY_SETTINGS = 5; // 0x5
+    field public static final int DEPENDENCY_STATUS_NOT_APPLICABLE = 1; // 0x1
+    field public static final int DEPENDENCY_STATUS_OK = 2; // 0x2
+    field public static final int DEPENDENCY_STATUS_TEMPORARILY_UNAVAILABLE = 3; // 0x3
+    field public static final int OPERATION_STATUS_FAILED = 3; // 0x3
+    field public static final int OPERATION_STATUS_NOT_APPLICABLE = 1; // 0x1
+    field public static final int OPERATION_STATUS_OK = 2; // 0x2
+  }
+
+  public static final class TimeZoneProviderStatus.Builder {
+    ctor public TimeZoneProviderStatus.Builder();
+    method @NonNull public android.service.timezone.TimeZoneProviderStatus build();
+    method @NonNull public android.service.timezone.TimeZoneProviderStatus.Builder setConnectivityDependencyStatus(int);
+    method @NonNull public android.service.timezone.TimeZoneProviderStatus.Builder setLocationDetectionDependencyStatus(int);
+    method @NonNull public android.service.timezone.TimeZoneProviderStatus.Builder setTimeZoneResolutionOperationStatus(int);
+  }
+
   public final class TimeZoneProviderSuggestion implements android.os.Parcelable {
     method public int describeContents();
     method public long getElapsedRealtimeMillis();
@@ -12120,6 +12149,7 @@
     method public static int getMaxScore();
     method @Nullable public android.media.MediaSyncEvent getMediaSyncEvent();
     method public int getPersonalizedScore();
+    method public int getProximity();
     method public int getScore();
     method public boolean isHotwordDetectionPersonalized();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -12133,6 +12163,9 @@
     field public static final int CONFIDENCE_LEVEL_VERY_HIGH = 6; // 0x6
     field @NonNull public static final android.os.Parcelable.Creator<android.service.voice.HotwordDetectedResult> CREATOR;
     field public static final int HOTWORD_OFFSET_UNSET = -1; // 0xffffffff
+    field public static final int PROXIMITY_FAR = 2; // 0x2
+    field public static final int PROXIMITY_NEAR = 1; // 0x1
+    field public static final int PROXIMITY_UNKNOWN = -1; // 0xffffffff
   }
 
   public static final class HotwordDetectedResult.Builder {
@@ -12221,7 +12254,7 @@
 
   public class WallpaperService.Engine {
     method public boolean isInAmbientMode();
-    method public void onAmbientModeChanged(boolean, long);
+    method @MainThread public void onAmbientModeChanged(boolean, long);
   }
 
 }
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index d8419a8..d1c1a17 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -758,6 +758,7 @@
     method public int getUserId();
     method public void setAutofillOptions(@Nullable android.content.AutofillOptions);
     method public void setContentCaptureOptions(@Nullable android.content.ContentCaptureOptions);
+    field public static final String ATTENTION_SERVICE = "attention";
     field public static final String CONTENT_CAPTURE_MANAGER_SERVICE = "content_capture";
     field public static final String DEVICE_IDLE_CONTROLLER = "deviceidle";
     field public static final String DREAM_SERVICE = "dream";
@@ -1900,6 +1901,7 @@
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.content.pm.UserInfo> getUsers(boolean, boolean, boolean);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean hasBaseUserRestriction(@NonNull String, @NonNull android.os.UserHandle);
     method public static boolean isSplitSystemUser();
+    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean isUserTypeEnabled(@NonNull String);
     method public boolean isUsersOnSecondaryDisplaysSupported();
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo preCreateUser(@NonNull String) throws android.os.UserManager.UserOperationException;
   }
@@ -2499,6 +2501,10 @@
     method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload.Builder setKeyphraseRecognitionExtras(@NonNull java.util.List<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra>);
   }
 
+  public abstract class HotwordDetectionService extends android.app.Service {
+    field public static final boolean ENABLE_PROXIMITY_RESULT = true;
+  }
+
   public final class VisibleActivityInfo implements android.os.Parcelable {
     ctor public VisibleActivityInfo(int, @NonNull android.os.IBinder);
   }
@@ -2622,13 +2628,23 @@
     method public int getCarrierIdListVersion();
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.List<java.lang.String> getCertsFromCarrierPrivilegeAccessRules();
     method @NonNull public java.util.List<android.telephony.data.ApnSetting> getDevicePolicyOverrideApns(@NonNull android.content.Context);
+    method @NonNull public android.util.Pair<java.lang.Integer,java.lang.Integer> getHalVersion(int);
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getLine1AlphaTag();
-    method public android.util.Pair<java.lang.Integer,java.lang.Integer> getRadioHalVersion();
+    method @Deprecated public android.util.Pair<java.lang.Integer,java.lang.Integer> getRadioHalVersion();
     method public boolean modifyDevicePolicyOverrideApn(@NonNull android.content.Context, int, @NonNull android.telephony.data.ApnSetting);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void refreshUiccProfile();
     method @Deprecated public void setCarrierTestOverride(String, String, String, String, String, String, String);
     method public void setCarrierTestOverride(String, String, String, String, String, String, String, String, String);
     method @RequiresPermission(android.Manifest.permission.BIND_TELECOM_CONNECTION_SERVICE) public void setVoiceServiceStateOverride(boolean);
+    field public static final int HAL_SERVICE_DATA = 1; // 0x1
+    field public static final int HAL_SERVICE_IMS = 7; // 0x7
+    field public static final int HAL_SERVICE_MESSAGING = 2; // 0x2
+    field public static final int HAL_SERVICE_MODEM = 3; // 0x3
+    field public static final int HAL_SERVICE_NETWORK = 4; // 0x4
+    field public static final int HAL_SERVICE_SIM = 5; // 0x5
+    field public static final int HAL_SERVICE_VOICE = 6; // 0x6
+    field public static final android.util.Pair HAL_VERSION_UNKNOWN;
+    field public static final android.util.Pair HAL_VERSION_UNSUPPORTED;
     field public static final int UNKNOWN_CARRIER_ID_LIST_VERSION = -1; // 0xffffffff
   }
 
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index f9b8a30..8e21d8c 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -567,6 +567,10 @@
     Missing nullability on parameter `destAddress` in method `checkSmsShortCodeDestination`
 MissingNullability: android.telephony.SmsManager#checkSmsShortCodeDestination(String, String) parameter #1:
     Missing nullability on parameter `countryIso` in method `checkSmsShortCodeDestination`
+MissingNullability: android.telephony.TelephonyManager#HAL_VERSION_UNKNOWN:
+    Missing nullability on field `HAL_VERSION_UNKNOWN` in class `class android.telephony.TelephonyManager`
+MissingNullability: android.telephony.TelephonyManager#HAL_VERSION_UNSUPPORTED:
+    Missing nullability on field `HAL_VERSION_UNSUPPORTED` in class `class android.telephony.TelephonyManager`
 MissingNullability: android.telephony.TelephonyManager#getLine1AlphaTag():
     Missing nullability on method `getLine1AlphaTag` return
 MissingNullability: android.telephony.TelephonyManager#getRadioHalVersion():
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index e86d2f3..2fe5d51 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -696,7 +696,8 @@
             ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR,
             ERROR_TAKE_SCREENSHOT_NO_ACCESSIBILITY_ACCESS,
             ERROR_TAKE_SCREENSHOT_INTERVAL_TIME_SHORT,
-            ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY
+            ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY,
+            ERROR_TAKE_SCREENSHOT_INVALID_WINDOW
     })
     public @interface ScreenshotErrorCode {}
 
@@ -728,6 +729,18 @@
     public static final int ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY = 4;
 
     /**
+     * The status of taking screenshot is failure and the reason is invalid accessibility window Id.
+     */
+    public static final int ERROR_TAKE_SCREENSHOT_INVALID_WINDOW = 5;
+
+    /**
+     * The status of taking screenshot is failure and the reason is the window contains secure
+     * content.
+     * @see WindowManager.LayoutParams#FLAG_SECURE
+     */
+    public static final int ERROR_TAKE_SCREENSHOT_SECURE_WINDOW = 6;
+
+    /**
      * The interval time of calling
      * {@link AccessibilityService#takeScreenshot(int, Executor, Consumer)} API.
      * @hide
@@ -2568,6 +2581,7 @@
      * @param executor Executor on which to run the callback.
      * @param callback The callback invoked when taking screenshot has succeeded or failed.
      *                 See {@link TakeScreenshotCallback} for details.
+     * @see #takeScreenshotOfWindow
      */
     public void takeScreenshot(int displayId, @NonNull @CallbackExecutor Executor executor,
             @NonNull TakeScreenshotCallback callback) {
@@ -2589,7 +2603,8 @@
                 final HardwareBuffer hardwareBuffer =
                         result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER, android.hardware.HardwareBuffer.class);
                 final ParcelableColorSpace colorSpace =
-                        result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE, android.graphics.ParcelableColorSpace.class);
+                        result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE,
+                                android.graphics.ParcelableColorSpace.class);
                 final ScreenshotResult screenshot = new ScreenshotResult(hardwareBuffer,
                         colorSpace.getColorSpace(),
                         result.getLong(KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP));
@@ -2601,6 +2616,37 @@
     }
 
     /**
+     * Takes a screenshot of the specified window and returns it via an
+     * {@link AccessibilityService.ScreenshotResult}. You can use {@link Bitmap#wrapHardwareBuffer}
+     * to construct the bitmap from the ScreenshotResult's payload.
+     * <p>
+     * <strong>Note:</strong> In order to take screenshots your service has
+     * to declare the capability to take screenshot by setting the
+     * {@link android.R.styleable#AccessibilityService_canTakeScreenshot}
+     * property in its meta-data. For details refer to {@link #SERVICE_META_DATA}.
+     * </p>
+     * <p>
+     * Both this method and {@link #takeScreenshot} can be used for machine learning-based visual
+     * screen understanding. Use <code>takeScreenshotOfWindow</code> if your target window might be
+     * visually underneath an accessibility overlay (from your or another accessibility service) in
+     * order to capture the window contents without the screenshot being covered by the overlay
+     * contents drawn on the screen.
+     * </p>
+     *
+     * @param accessibilityWindowId The window id, from {@link AccessibilityWindowInfo#getId()}.
+     * @param executor Executor on which to run the callback.
+     * @param callback The callback invoked when taking screenshot has succeeded or failed.
+     *                 See {@link TakeScreenshotCallback} for details.
+     * @see #takeScreenshot
+     */
+    public void takeScreenshotOfWindow(int accessibilityWindowId,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull TakeScreenshotCallback callback) {
+        AccessibilityInteractionClient.getInstance(this).takeScreenshotOfWindow(
+                        mConnectionId, accessibilityWindowId, executor, callback);
+    }
+
+    /**
      * Sets the strokeWidth and color of the accessibility focus rectangle.
      * <p>
      * <strong>Note:</strong> This setting persists until this or another active
@@ -3113,7 +3159,8 @@
         private final @NonNull ColorSpace mColorSpace;
         private final long mTimestamp;
 
-        private ScreenshotResult(@NonNull HardwareBuffer hardwareBuffer,
+        /** @hide */
+        public ScreenshotResult(@NonNull HardwareBuffer hardwareBuffer,
                 @NonNull ColorSpace colorSpace, long timestamp) {
             Preconditions.checkNotNull(hardwareBuffer, "hardwareBuffer cannot be null");
             Preconditions.checkNotNull(colorSpace, "colorSpace cannot be null");
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index 9abce3a..da14b50 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -30,6 +30,7 @@
 import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
 import android.view.accessibility.AccessibilityWindowInfo;
 import java.util.List;
+import android.window.ScreenCapture;
 
 /**
  * Interface given to an AccessibilitySerivce to talk to the AccessibilityManagerService.
@@ -122,6 +123,10 @@
 
     void takeScreenshot(int displayId, in RemoteCallback callback);
 
+    void takeScreenshotOfWindow(int accessibilityWindowId, int interactionId,
+        in ScreenCapture.ScreenCaptureListener listener,
+        IAccessibilityInteractionConnectionCallback callback);
+
     void setGestureDetectionPassthroughRegion(int displayId, in Region region);
 
     void setTouchExplorationPassthroughRegion(int displayId, in Region region);
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 860bfc5..0f4644c 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -42,6 +42,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
 import android.database.DatabaseUtils;
+import android.healthconnect.HealthConnectManager;
 import android.media.AudioAttributes.AttributeUsage;
 import android.os.Binder;
 import android.os.Build;
@@ -1390,9 +1391,16 @@
     public static final int OP_SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY =
             AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY;
 
+    /**
+     * An app op for reading/writing health connect data.
+     *
+     * @hide
+     */
+    public static final int OP_READ_WRITE_HEALTH_DATA = AppProtoEnums.APP_OP_READ_WRITE_HEALTH_DATA;
+
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public static final int _NUM_OP = 126;
+    public static final int _NUM_OP = 127;
 
     /** Access to coarse location information. */
     public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -1873,6 +1881,15 @@
             "android:read_media_visual_user_selected";
 
     /**
+     * An app op for reading/writing health connect data.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String OPSTR_READ_WRITE_HEALTH_DATA =
+            "android:read_write_health_data";
+
+    /**
      * Record audio from near-field microphone (ie. TV remote)
      * Allows audio recording regardless of sensor privacy state,
      *  as it is an intentional user interaction: hold-to-talk
@@ -2400,7 +2417,9 @@
                 "SYSTEM_EXEMPT_FROM_APP_STANDBY").build(),
         new AppOpInfo.Builder(OP_SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY,
                 OPSTR_SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY,
-                "SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY").build()
+                "SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY").build(),
+        new AppOpInfo.Builder(OP_READ_WRITE_HEALTH_DATA, OPSTR_READ_WRITE_HEALTH_DATA,
+                "READ_WRITE_HEALTH_DATA").setDefaultMode(AppOpsManager.MODE_ALLOWED).build()
     };
 
     // The number of longs needed to form a full bitmask of app ops
@@ -2560,7 +2579,14 @@
     @TestApi
     public static int permissionToOpCode(String permission) {
         Integer boxedOpCode = sPermToOp.get(permission);
-        return boxedOpCode != null ? boxedOpCode : OP_NONE;
+        if (boxedOpCode != null) {
+            return boxedOpCode;
+        }
+        if (HealthConnectManager.isHealthPermission(ActivityThread.currentApplication(),
+                permission)) {
+            return OP_READ_WRITE_HEALTH_DATA;
+        }
+        return OP_NONE;
     }
 
     /**
@@ -7224,10 +7250,14 @@
      */
     public static @Nullable String permissionToOp(@NonNull String permission) {
         final Integer opCode = sPermToOp.get(permission);
-        if (opCode == null) {
-            return null;
+        if (opCode != null) {
+            return sAppOpInfos[opCode].name;
         }
-        return sAppOpInfos[opCode].name;
+        if (HealthConnectManager.isHealthPermission(ActivityThread.currentApplication(),
+                permission)) {
+            return sAppOpInfos[OP_READ_WRITE_HEALTH_DATA].name;
+        }
+        return null;
     }
 
     /**
diff --git a/core/java/android/app/ForegroundServiceTypeNotAllowedException.java b/core/java/android/app/ForegroundServiceTypeNotAllowedException.java
new file mode 100644
index 0000000..c258242
--- /dev/null
+++ b/core/java/android/app/ForegroundServiceTypeNotAllowedException.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Exception thrown when an app tries to start a foreground {@link Service} without a valid type.
+ */
+public final class ForegroundServiceTypeNotAllowedException
+        extends ServiceStartNotAllowedException implements Parcelable {
+    /**
+     * Constructor.
+     */
+    public ForegroundServiceTypeNotAllowedException(@NonNull String message) {
+        super(message);
+    }
+
+    ForegroundServiceTypeNotAllowedException(@NonNull Parcel source) {
+        super(source.readString());
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(getMessage());
+    }
+
+    public static final @NonNull Creator<android.app.ForegroundServiceTypeNotAllowedException>
+            CREATOR = new Creator<android.app.ForegroundServiceTypeNotAllowedException>() {
+                @NonNull
+                public android.app.ForegroundServiceTypeNotAllowedException createFromParcel(
+                        Parcel source) {
+                    return new android.app.ForegroundServiceTypeNotAllowedException(source);
+                }
+
+                @NonNull
+                public android.app.ForegroundServiceTypeNotAllowedException[] newArray(int size) {
+                    return new android.app.ForegroundServiceTypeNotAllowedException[size];
+                }
+            };
+}
diff --git a/core/java/android/app/backup/BackupRestoreEventLogger.java b/core/java/android/app/backup/BackupRestoreEventLogger.java
index 760c6f0..6f62c8a 100644
--- a/core/java/android/app/backup/BackupRestoreEventLogger.java
+++ b/core/java/android/app/backup/BackupRestoreEventLogger.java
@@ -53,6 +53,8 @@
 
     /**
      * Operation types for which this logger can be used.
+     *
+     * @hide
      */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({
@@ -87,6 +89,8 @@
      *                      {@link OperationType}. Attempts to use logging methods that don't match
      *                      the specified operation type will be rejected (e.g. use backup methods
      *                      for a restore logger and vice versa).
+     *
+     * @hide
      */
     public BackupRestoreEventLogger(@OperationType int operationType) {
         mOperationType = operationType;
@@ -111,11 +115,9 @@
      *
      * @param dataType the type of data being backed.
      * @param count number of items of the given type that have been successfully backed up.
-     *
-     * @return boolean, indicating whether the log has been accepted.
      */
-    public boolean logItemsBackedUp(@NonNull @BackupRestoreDataType String dataType, int count) {
-        return logSuccess(OperationType.BACKUP, dataType, count);
+    public void logItemsBackedUp(@NonNull @BackupRestoreDataType String dataType, int count) {
+        logSuccess(OperationType.BACKUP, dataType, count);
     }
 
     /**
@@ -130,12 +132,10 @@
      * @param dataType the type of data being backed.
      * @param count number of items of the given type that have failed to back up.
      * @param error optional, the error that has caused the failure.
-     *
-     * @return boolean, indicating whether the log has been accepted.
      */
-    public boolean logItemsBackupFailed(@NonNull @BackupRestoreDataType String dataType, int count,
+    public void logItemsBackupFailed(@NonNull @BackupRestoreDataType String dataType, int count,
             @Nullable @BackupRestoreError String error) {
-        return logFailure(OperationType.BACKUP, dataType, count, error);
+        logFailure(OperationType.BACKUP, dataType, count, error);
     }
 
     /**
@@ -151,12 +151,10 @@
      *
      * @param dataType the type of data being backed up.
      * @param metaData the metadata associated with the data type.
-     *
-     * @return boolean, indicating whether the log has been accepted.
      */
-    public boolean logBackupMetaData(@NonNull @BackupRestoreDataType String dataType,
+    public void logBackupMetaData(@NonNull @BackupRestoreDataType String dataType,
             @NonNull String metaData) {
-        return logMetaData(OperationType.BACKUP, dataType, metaData);
+        logMetaData(OperationType.BACKUP, dataType, metaData);
     }
 
     /**
@@ -172,11 +170,9 @@
      *
      * @param dataType the type of data being restored.
      * @param count number of items of the given type that have been successfully restored.
-     *
-     * @return boolean, indicating whether the log has been accepted.
      */
-    public boolean logItemsRestored(@NonNull @BackupRestoreDataType String dataType, int count) {
-        return logSuccess(OperationType.RESTORE, dataType, count);
+    public void logItemsRestored(@NonNull @BackupRestoreDataType String dataType, int count) {
+        logSuccess(OperationType.RESTORE, dataType, count);
     }
 
     /**
@@ -193,12 +189,10 @@
      * @param dataType the type of data being restored.
      * @param count number of items of the given type that have failed to restore.
      * @param error optional, the error that has caused the failure.
-     *
-     * @return boolean, indicating whether the log has been accepted.
      */
-    public boolean logItemsRestoreFailed(@NonNull @BackupRestoreDataType String dataType, int count,
+    public void logItemsRestoreFailed(@NonNull @BackupRestoreDataType String dataType, int count,
             @Nullable @BackupRestoreError String error) {
-        return logFailure(OperationType.RESTORE, dataType, count, error);
+        logFailure(OperationType.RESTORE, dataType, count, error);
     }
 
     /**
@@ -216,12 +210,10 @@
      *
      * @param dataType the type of data being restored.
      * @param metadata the metadata associated with the data type.
-     *
-     * @return boolean, indicating whether the log has been accepted.
      */
-    public boolean logRestoreMetadata(@NonNull @BackupRestoreDataType String dataType,
+    public void logRestoreMetadata(@NonNull @BackupRestoreDataType String dataType,
             @NonNull  String metadata) {
-        return logMetaData(OperationType.RESTORE, dataType, metadata);
+        logMetaData(OperationType.RESTORE, dataType, metadata);
     }
 
     /**
@@ -240,52 +232,47 @@
      *
      * @hide
      */
-    public @OperationType int getOperationType() {
+    @OperationType
+    public int getOperationType() {
         return mOperationType;
     }
 
-    private boolean logSuccess(@OperationType int operationType,
+    private void logSuccess(@OperationType int operationType,
             @BackupRestoreDataType String dataType, int count) {
         DataTypeResult dataTypeResult = getDataTypeResult(operationType, dataType);
         if (dataTypeResult == null) {
-            return false;
+            return;
         }
 
         dataTypeResult.mSuccessCount += count;
         mResults.put(dataType, dataTypeResult);
-
-        return true;
     }
 
-    private boolean logFailure(@OperationType int operationType,
+    private void logFailure(@OperationType int operationType,
             @NonNull @BackupRestoreDataType String dataType, int count,
             @Nullable @BackupRestoreError String error) {
         DataTypeResult dataTypeResult = getDataTypeResult(operationType, dataType);
         if (dataTypeResult == null) {
-            return false;
+            return;
         }
 
         dataTypeResult.mFailCount += count;
         if (error != null) {
             dataTypeResult.mErrors.merge(error, count, Integer::sum);
         }
-
-        return true;
     }
 
-    private boolean logMetaData(@OperationType int operationType,
+    private void logMetaData(@OperationType int operationType,
             @NonNull @BackupRestoreDataType String dataType, @NonNull String metaData) {
         if (mHashDigest == null) {
-            return false;
+            return;
         }
         DataTypeResult dataTypeResult = getDataTypeResult(operationType, dataType);
         if (dataTypeResult == null) {
-            return false;
+            return;
         }
 
         dataTypeResult.mMetadataHash = getMetaDataHash(metaData);
-
-        return true;
     }
 
     /**
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 1df0fa8..ae1f689 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4917,6 +4917,7 @@
      * @see android.server.attention.AttentionManagerService
      * @hide
      */
+    @TestApi
     public static final String ATTENTION_SERVICE = "attention";
 
     /**
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index b3435b1..8b6c4dd 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -28,6 +28,7 @@
 import android.os.PatternMatcher;
 import android.text.TextUtils;
 import android.util.AndroidException;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.Printer;
 import android.util.proto.ProtoOutputStream;
@@ -302,7 +303,7 @@
     @UnsupportedAppUsage
     private int mOrder;
     @UnsupportedAppUsage
-    private final ArrayList<String> mActions;
+    private final ArraySet<String> mActions;
     private ArrayList<String> mCategories = null;
     private ArrayList<String> mDataSchemes = null;
     private ArrayList<PatternMatcher> mDataSchemeSpecificParts = null;
@@ -433,7 +434,7 @@
      */
     public IntentFilter() {
         mPriority = 0;
-        mActions = new ArrayList<String>();
+        mActions = new ArraySet<>();
     }
 
     /**
@@ -445,7 +446,7 @@
      */
     public IntentFilter(String action) {
         mPriority = 0;
-        mActions = new ArrayList<String>();
+        mActions = new ArraySet<>();
         addAction(action);
     }
 
@@ -468,7 +469,7 @@
     public IntentFilter(String action, String dataType)
         throws MalformedMimeTypeException {
         mPriority = 0;
-        mActions = new ArrayList<String>();
+        mActions = new ArraySet<>();
         addAction(action);
         addDataType(dataType);
     }
@@ -481,7 +482,7 @@
     public IntentFilter(IntentFilter o) {
         mPriority = o.mPriority;
         mOrder = o.mOrder;
-        mActions = new ArrayList<String>(o.mActions);
+        mActions = new ArraySet<>(o.mActions);
         if (o.mCategories != null) {
             mCategories = new ArrayList<String>(o.mCategories);
         }
@@ -742,9 +743,7 @@
      * @param action Name of the action to match, such as Intent.ACTION_VIEW.
      */
     public final void addAction(String action) {
-        if (!mActions.contains(action)) {
-            mActions.add(action.intern());
-        }
+        mActions.add(action.intern());
     }
 
     /**
@@ -758,7 +757,7 @@
      * Return an action in the filter.
      */
     public final String getAction(int index) {
-        return mActions.get(index);
+        return mActions.valueAt(index);
     }
 
     /**
@@ -797,8 +796,11 @@
             if (ignoreActions == null) {
                 return !mActions.isEmpty();
             }
+            if (mActions.size() > ignoreActions.size()) {
+                return true;    // some actions are definitely not ignored
+            }
             for (int i = mActions.size() - 1; i >= 0; i--) {
-                if (!ignoreActions.contains(mActions.get(i))) {
+                if (!ignoreActions.contains(mActions.valueAt(i))) {
                     return true;
                 }
             }
@@ -1918,7 +1920,7 @@
         int N = countActions();
         for (int i=0; i<N; i++) {
             serializer.startTag(null, ACTION_STR);
-            serializer.attribute(null, NAME_STR, mActions.get(i));
+            serializer.attribute(null, NAME_STR, mActions.valueAt(i));
             serializer.endTag(null, ACTION_STR);
         }
         N = countCategories();
@@ -2313,7 +2315,7 @@
     }
 
     public final void writeToParcel(Parcel dest, int flags) {
-        dest.writeStringList(mActions);
+        dest.writeStringArray(mActions.toArray(new String[mActions.size()]));
         if (mCategories != null) {
             dest.writeInt(1);
             dest.writeStringList(mCategories);
@@ -2407,8 +2409,9 @@
 
     /** @hide */
     public IntentFilter(Parcel source) {
-        mActions = new ArrayList<String>();
-        source.readStringList(mActions);
+        List<String> actions = new ArrayList<>();
+        source.readStringList(actions);
+        mActions = new ArraySet<>(actions);
         if (source.readInt() != 0) {
             mCategories = new ArrayList<String>();
             source.readStringList(mCategories);
diff --git a/core/java/android/content/om/FabricatedOverlay.java b/core/java/android/content/om/FabricatedOverlay.java
index 3ca0560..dbefa65 100644
--- a/core/java/android/content/om/FabricatedOverlay.java
+++ b/core/java/android/content/om/FabricatedOverlay.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.os.FabricatedOverlayInternal;
 import android.os.FabricatedOverlayInternalEntry;
+import android.os.ParcelFileDescriptor;
 import android.text.TextUtils;
 
 import com.android.internal.util.Preconditions;
@@ -169,6 +170,24 @@
             return this;
         }
 
+        /**
+         * Sets the value of the fabricated overlay
+         *
+         * @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
+         */
+        public Builder setResourceValue(@NonNull String resourceName, ParcelFileDescriptor value,
+                String configuration) {
+            final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
+            entry.resourceName = resourceName;
+            entry.binaryData = value;
+            entry.configuration = configuration;
+            mEntries.add(entry);
+            return this;
+        }
+
         /** Builds an immutable fabricated overlay. */
         public FabricatedOverlay build() {
             final FabricatedOverlayInternal overlay = new FabricatedOverlayInternal();
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index ff07291..09d24d4 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -40,8 +40,10 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.DrawableContainer;
 import android.icu.text.PluralRules;
+import android.net.Uri;
 import android.os.Build;
 import android.os.LocaleList;
+import android.os.ParcelFileDescriptor;
 import android.os.Trace;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
@@ -59,6 +61,8 @@
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
+import java.io.File;
+import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.PrintWriter;
@@ -799,7 +803,21 @@
     private Drawable decodeImageDrawable(@NonNull AssetInputStream ais,
             @NonNull Resources wrapper, @NonNull TypedValue value) {
         ImageDecoder.Source src = new ImageDecoder.AssetInputStreamSource(ais,
-                            wrapper, value);
+                wrapper, value);
+        try {
+            return ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
+                decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+            });
+        } catch (IOException ioe) {
+            // This is okay. This may be something that ImageDecoder does not
+            // support, like SVG.
+            return null;
+        }
+    }
+
+    @Nullable
+    private Drawable decodeImageDrawable(@NonNull FileInputStream fis, @NonNull Resources wrapper) {
+        ImageDecoder.Source src = ImageDecoder.createSource(wrapper, fis);
         try {
             return ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
                 decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
@@ -860,6 +878,17 @@
                     } else {
                         dr = loadXmlDrawable(wrapper, value, id, density, file);
                     }
+                } else if (file.startsWith("frro://")) {
+                    Uri uri = Uri.parse(file);
+                    File f = new File('/' + uri.getHost() + uri.getPath());
+                    ParcelFileDescriptor pfd = ParcelFileDescriptor.open(f,
+                            ParcelFileDescriptor.MODE_READ_ONLY);
+                    AssetFileDescriptor afd = new AssetFileDescriptor(
+                            pfd,
+                            Long.parseLong(uri.getQueryParameter("offset")),
+                            Long.parseLong(uri.getQueryParameter("size")));
+                    FileInputStream is = afd.createInputStream();
+                    dr = decodeImageDrawable(is, wrapper);
                 } else {
                     final InputStream is = mAssets.openNonAsset(
                             value.assetCookie, file, AssetManager.ACCESS_STREAMING);
diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl
index 5a44244..51236fe3 100644
--- a/core/java/android/hardware/usb/IUsbManager.aidl
+++ b/core/java/android/hardware/usb/IUsbManager.aidl
@@ -79,9 +79,20 @@
     /* Returns true if the caller has permission to access the device. */
     boolean hasDevicePermission(in UsbDevice device, String packageName);
 
+    /* Returns true if the given package/pid/uid has permission to access the device. */
+    @JavaPassthrough(annotation=
+            "@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_USB)")
+    boolean hasDevicePermissionWithIdentity(in UsbDevice device, String packageName,
+            int pid, int uid);
+
     /* Returns true if the caller has permission to access the accessory. */
     boolean hasAccessoryPermission(in UsbAccessory accessory);
 
+    /* Returns true if the given pid/uid has permission to access the accessory. */
+    @JavaPassthrough(annotation=
+            "@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_USB)")
+    boolean hasAccessoryPermissionWithIdentity(in UsbAccessory accessory, int pid, int uid);
+
     /* Requests permission for the given package to access the device.
      * Will display a system dialog to query the user if permission
      * had not already been given.
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index 2c38f70..50dd0064 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -838,6 +838,28 @@
     }
 
     /**
+     * Returns true if the caller has permission to access the device. It's similar to the
+     * {@link #hasPermission(UsbDevice)} but allows to specify a different package/uid/pid.
+     *
+     * <p>Not for third-party apps.</p>
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MANAGE_USB)
+    @RequiresFeature(PackageManager.FEATURE_USB_HOST)
+    public boolean hasPermission(@NonNull UsbDevice device, @NonNull String packageName,
+            int pid, int uid) {
+        if (mService == null) {
+            return false;
+        }
+        try {
+            return mService.hasDevicePermissionWithIdentity(device, packageName, pid, uid);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Returns true if the caller has permission to access the accessory.
      * Permission might have been granted temporarily via
      * {@link #requestPermission(UsbAccessory, PendingIntent)} or
@@ -859,6 +881,27 @@
     }
 
     /**
+     * Returns true if the caller has permission to access the accessory. It's similar to the
+     * {@link #hasPermission(UsbAccessory)} but allows to specify a different uid/pid.
+     *
+     * <p>Not for third-party apps.</p>
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MANAGE_USB)
+    @RequiresFeature(PackageManager.FEATURE_USB_ACCESSORY)
+    public boolean hasPermission(@NonNull UsbAccessory accessory, int pid, int uid) {
+        if (mService == null) {
+            return false;
+        }
+        try {
+            return mService.hasAccessoryPermissionWithIdentity(accessory, pid, uid);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+   /**
      * Requests temporary permission for the given package to access the device.
      * This may result in a system dialog being displayed to the user
      * if permission had not already been granted.
diff --git a/core/java/android/os/ServiceManager.java b/core/java/android/os/ServiceManager.java
index e321a66..b6ff102 100644
--- a/core/java/android/os/ServiceManager.java
+++ b/core/java/android/os/ServiceManager.java
@@ -258,12 +258,14 @@
      * waitForService should always be able to return the service.
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    @NonNull
     public static String[] getDeclaredInstances(@NonNull String iface) {
         try {
             return getIServiceManager().getDeclaredInstances(iface);
         } catch (RemoteException e) {
             Log.e(TAG, "error in getDeclaredInstances", e);
-            return null;
+            throw e.rethrowFromSystemServer();
         }
     }
 
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index 8bfa0e9..fb197f5 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -100,6 +100,7 @@
     /** @hide */
     public static final long TRACE_TAG_VIBRATOR = 1L << 23;
     /** @hide */
+    @SystemApi(client = MODULE_LIBRARIES)
     public static final long TRACE_TAG_AIDL = 1L << 24;
     /** @hide */
     public static final long TRACE_TAG_NNAPI = 1L << 25;
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index bda2a35..fbfe548 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1637,6 +1637,16 @@
     /** @hide */
     public static final String SYSTEM_USER_MODE_EMULATION_HEADLESS = "headless";
 
+    /**
+     * System Property used to override whether users can be created even if their type is disabled
+     * or their limit is reached. Set value to 1 to enable.
+     *
+     * <p>Only used on non-user builds.
+     *
+     * @hide
+     */
+    public static final String DEV_CREATE_OVERRIDE_PROPERTY = "debug.user.creation_override";
+
     private static final String ACTION_CREATE_USER = "android.os.action.CREATE_USER";
 
     /**
@@ -4382,6 +4392,7 @@
      * @return true if the creation of users of the given user type is enabled on this device.
      * @hide
      */
+    @TestApi
     @RequiresPermission(anyOf = {
             android.Manifest.permission.MANAGE_USERS,
             android.Manifest.permission.CREATE_USERS
diff --git a/core/java/android/service/credentials/CreateCredentialResponse.java b/core/java/android/service/credentials/CreateCredentialResponse.java
index e330d1e..f69dca8 100644
--- a/core/java/android/service/credentials/CreateCredentialResponse.java
+++ b/core/java/android/service/credentials/CreateCredentialResponse.java
@@ -33,13 +33,11 @@
  * @hide
  */
 public final class CreateCredentialResponse implements Parcelable {
-    private final @Nullable CharSequence mHeader;
     private final @NonNull List<SaveEntry> mSaveEntries;
     private final @Nullable Action mRemoteSaveEntry;
     //TODO : Add actions if needed
 
     private CreateCredentialResponse(@NonNull Parcel in) {
-        mHeader = in.readCharSequence();
         List<SaveEntry> saveEntries = new ArrayList<>();
         in.readTypedList(saveEntries, SaveEntry.CREATOR);
         mSaveEntries = saveEntries;
@@ -48,7 +46,6 @@
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeCharSequence(mHeader);
         dest.writeTypedList(mSaveEntries);
         dest.writeTypedObject(mRemoteSaveEntry, flags);
     }
@@ -72,21 +69,14 @@
             };
 
     /* package-private */ CreateCredentialResponse(
-            @Nullable CharSequence header,
             @NonNull List<SaveEntry> saveEntries,
             @Nullable Action remoteSaveEntry) {
-        this.mHeader = header;
         this.mSaveEntries = saveEntries;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mSaveEntries);
         this.mRemoteSaveEntry = remoteSaveEntry;
     }
 
-    /** Returns the header to be displayed on the UI. */
-    public @Nullable CharSequence getHeader() {
-        return mHeader;
-    }
-
     /** Returns the list of save entries to be displayed on the UI. */
     public @NonNull List<SaveEntry> getSaveEntries() {
         return mSaveEntries;
@@ -102,17 +92,9 @@
      */
     @SuppressWarnings("WeakerAccess")
     public static final class Builder {
-
-        private @Nullable CharSequence mHeader;
         private @NonNull List<SaveEntry> mSaveEntries = new ArrayList<>();
         private @Nullable Action mRemoteSaveEntry;
 
-        /** Sets the header to be displayed on the UI. */
-        public @NonNull Builder setHeader(@Nullable CharSequence header) {
-            mHeader = header;
-            return this;
-        }
-
         /**
          * Sets the list of save entries to be shown on the UI.
          *
@@ -154,7 +136,6 @@
             Preconditions.checkCollectionNotEmpty(mSaveEntries, "saveEntries must "
                     + "not be empty");
             return new CreateCredentialResponse(
-                    mHeader,
                     mSaveEntries,
                     mRemoteSaveEntry);
         }
diff --git a/core/java/android/service/credentials/CredentialsDisplayContent.java b/core/java/android/service/credentials/CredentialsDisplayContent.java
index ab5b524..4b23800 100644
--- a/core/java/android/service/credentials/CredentialsDisplayContent.java
+++ b/core/java/android/service/credentials/CredentialsDisplayContent.java
@@ -34,9 +34,6 @@
  * @hide
  */
 public final class CredentialsDisplayContent implements Parcelable {
-    /** Header to be displayed on the UI. */
-    private final @Nullable CharSequence mHeader;
-
     /** List of credential entries to be displayed on the UI. */
     private final @NonNull List<CredentialEntry> mCredentialEntries;
 
@@ -46,18 +43,15 @@
     /** Remote credential entry to get the response from a different device. */
     private final @Nullable Action mRemoteCredentialEntry;
 
-    private CredentialsDisplayContent(@Nullable CharSequence header,
-            @NonNull List<CredentialEntry> credentialEntries,
+    private CredentialsDisplayContent(@NonNull List<CredentialEntry> credentialEntries,
             @NonNull List<Action> actions,
             @Nullable Action remoteCredentialEntry) {
-        mHeader = header;
         mCredentialEntries = credentialEntries;
         mActions = actions;
         mRemoteCredentialEntry = remoteCredentialEntry;
     }
 
     private CredentialsDisplayContent(@NonNull Parcel in) {
-        mHeader = in.readCharSequence();
         List<CredentialEntry> credentialEntries = new ArrayList<>();
         in.readTypedList(credentialEntries, CredentialEntry.CREATOR);
         mCredentialEntries = credentialEntries;
@@ -87,20 +81,12 @@
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeCharSequence(mHeader);
         dest.writeTypedList(mCredentialEntries, flags);
         dest.writeTypedList(mActions, flags);
         dest.writeTypedObject(mRemoteCredentialEntry, flags);
     }
 
     /**
-     * Returns the header to be displayed on the UI.
-     */
-    public @Nullable CharSequence getHeader() {
-        return mHeader;
-    }
-
-    /**
      * Returns the list of credential entries to be displayed on the UI.
      */
     public @NonNull List<CredentialEntry> getCredentialEntries() {
@@ -125,20 +111,11 @@
      * Builds an instance of {@link CredentialsDisplayContent}.
      */
     public static final class Builder {
-        private CharSequence mHeader;
         private List<CredentialEntry> mCredentialEntries = new ArrayList<>();
         private List<Action> mActions = new ArrayList<>();
         private Action mRemoteCredentialEntry;
 
         /**
-         * Sets the header to be displayed on the UI.
-         */
-        public @NonNull Builder setHeader(@Nullable CharSequence header) {
-            mHeader = header;
-            return this;
-        }
-
-        /**
          * Sets the remote credential entry to be displayed on the UI.
          */
         public @NonNull Builder setRemoteCredentialEntry(@Nullable Action remoteCredentialEntry) {
@@ -208,7 +185,7 @@
                 throw new IllegalStateException("credentialEntries and actions must not both "
                         + "be empty");
             }
-            return new CredentialsDisplayContent(mHeader, mCredentialEntries, mActions,
+            return new CredentialsDisplayContent(mCredentialEntries, mActions,
                     mRemoteCredentialEntry);
         }
     }
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 32bdf79..bb22920 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -1294,7 +1294,7 @@
         if (!mWindowless) {
             Intent i = new Intent(this, DreamActivity.class);
             i.setPackage(getApplicationContext().getPackageName());
-            i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_USER_ACTION);
             i.putExtra(DreamActivity.EXTRA_CALLBACK, new DreamActivityCallbacks(mDreamToken));
             final ServiceInfo serviceInfo = fetchServiceInfo(this,
                     new ComponentName(this, getClass()));
diff --git a/core/java/android/service/timezone/TimeZoneProviderService.java b/core/java/android/service/timezone/TimeZoneProviderService.java
index 2cea95a..41ca94b 100644
--- a/core/java/android/service/timezone/TimeZoneProviderService.java
+++ b/core/java/android/service/timezone/TimeZoneProviderService.java
@@ -44,8 +44,8 @@
  *
  * <p>Once started, providers are expected to detect the time zone if possible, and report the
  * result via {@link #reportSuggestion(TimeZoneProviderSuggestion)} or {@link
- * #reportUncertain()}. Providers may also report that they have permanently failed
- * by calling {@link #reportPermanentFailure(Throwable)}. See the javadocs for each
+ * #reportUncertain(TimeZoneProviderStatus)}. Providers may also report that they have permanently
+ * failed by calling {@link #reportPermanentFailure(Throwable)}. See the javadocs for each
  * method for details.
  *
  * <p>After starting, providers are expected to issue their first callback within the timeout
@@ -213,8 +213,6 @@
      *
      * @param providerStatus provider status information that can influence detector service
      *   behavior and/or be reported via the device UI
-     *
-     * @hide
      */
     public final void reportSuggestion(@NonNull TimeZoneProviderSuggestion suggestion,
             @NonNull TimeZoneProviderStatus providerStatus) {
@@ -248,8 +246,9 @@
 
     /**
      * Indicates the time zone is not known because of an expected runtime state or error, e.g. when
-     * the provider is unable to detect location, or there was a problem when resolving the location
-     * to a time zone.
+     * the provider is unable to detect location, or there was connectivity issue.
+     *
+     * <p>See {@link #reportUncertain(TimeZoneProviderStatus)} for a more expressive version
      */
     public final void reportUncertain() {
         TimeZoneProviderStatus providerStatus = null;
@@ -264,8 +263,6 @@
      *
      * @param providerStatus provider status information that can influence detector service
      *   behavior and/or be reported via the device UI
-     *
-     * @hide
      */
     public final void reportUncertain(@NonNull TimeZoneProviderStatus providerStatus) {
         Objects.requireNonNull(providerStatus);
@@ -362,8 +359,8 @@
      * <p>Between {@link #onStartUpdates(long)} and {@link #onStopUpdates()} calls, the Android
      * system server holds the latest report from the provider in memory. After an initial report,
      * provider implementations are only required to send a report via {@link
-     * #reportSuggestion(TimeZoneProviderSuggestion)} or via {@link #reportUncertain()} when it
-     * differs from the previous report.
+     * #reportSuggestion(TimeZoneProviderSuggestion, TimeZoneProviderStatus)} or via {@link
+     * #reportUncertain(TimeZoneProviderStatus)} when it differs from the previous report.
      *
      * <p>{@link #reportPermanentFailure(Throwable)} can also be called by provider implementations
      * in rare cases, after which the provider should consider itself stopped and not make any
@@ -375,7 +372,8 @@
      * Android system server may move on to use other providers or detection methods. Providers
      * should therefore make best efforts during this time to generate a report, which could involve
      * increased power usage. Providers should preferably report an explicit {@link
-     * #reportUncertain()} if the time zone(s) cannot be detected within the initialization timeout.
+     * #reportUncertain(TimeZoneProviderStatus)} if the time zone(s) cannot be detected within the
+     * initialization timeout.
      *
      * @see #onStopUpdates() for the signal from the system server to stop sending reports
      */
diff --git a/core/java/android/service/timezone/TimeZoneProviderStatus.java b/core/java/android/service/timezone/TimeZoneProviderStatus.java
index 513068f..e0b78e9 100644
--- a/core/java/android/service/timezone/TimeZoneProviderStatus.java
+++ b/core/java/android/service/timezone/TimeZoneProviderStatus.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
@@ -65,6 +66,7 @@
  *
  * @hide
  */
+@SystemApi
 public final class TimeZoneProviderStatus implements Parcelable {
 
     /**
diff --git a/core/java/android/service/voice/HotwordDetectedResult.java b/core/java/android/service/voice/HotwordDetectedResult.java
index b4f20c9..dee560b 100644
--- a/core/java/android/service/voice/HotwordDetectedResult.java
+++ b/core/java/android/service/voice/HotwordDetectedResult.java
@@ -31,6 +31,8 @@
 import com.android.internal.util.DataClass;
 import com.android.internal.util.Preconditions;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -99,14 +101,30 @@
     private static final int LIMIT_AUDIO_CHANNEL_MAX_VALUE = 63;
 
     /**
-     * The bundle key for proximity value
+     * The bundle key for proximity
      *
      * TODO(b/238896013): Move the proximity logic out of bundle to proper API.
-     *
-     * @hide
      */
-    public static final String EXTRA_PROXIMITY_METERS =
-            "android.service.voice.extra.PROXIMITY_METERS";
+    private static final String EXTRA_PROXIMITY =
+            "android.service.voice.extra.PROXIMITY";
+
+    /** Users’ proximity is unknown (proximity sensing was inconclusive and is unsupported). */
+    public static final int PROXIMITY_UNKNOWN = -1;
+
+    /** Proximity value that represents that the object is near. */
+    public static final int PROXIMITY_NEAR = 1;
+
+    /** Proximity value that represents that the object is far. */
+    public static final int PROXIMITY_FAR = 2;
+
+    /** @hide */
+    @IntDef(prefix = {"PROXIMITY"}, value = {
+            PROXIMITY_UNKNOWN,
+            PROXIMITY_NEAR,
+            PROXIMITY_FAR
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ProximityValue {}
 
     /** Confidence level in the trigger outcome. */
     @HotwordConfidenceLevelValue
@@ -220,12 +238,14 @@
      * versions of Android.
      *
      * <p>After the trigger happens, a special case of proximity-related extra, with the key of
-     * 'android.service.voice.extra.PROXIMITY_METERS' and the value of distance in meters (double),
-     * will be stored to enable proximity logic. The proximity meters is provided by the system,
-     * on devices that support detecting proximity of nearby users, to help disambiguate which
-     * nearby device should respond. When the proximity is unknown, the proximity value will not
-     * be stored. This mapping will be excluded from the max bundle size calculation because this
-     * mapping is included after the result is returned from the hotword detector service.
+     * 'android.service.voice.extra.PROXIMITY_VALUE' and the value of proximity value (integer)
+     * will be stored to enable proximity logic. {@link HotwordDetectedResult#PROXIMITY_NEAR} will
+     * indicate 'NEAR' proximity and {@link HotwordDetectedResult#PROXIMITY_FAR} will indicate 'FAR'
+     * proximity. The proximity value is provided by the system, on devices that support detecting
+     * proximity of nearby users, to help disambiguate which nearby device should respond. When the
+     * proximity is unknown, the proximity value will not be stored. This mapping will be excluded
+     * from the max bundle size calculation because this mapping is included after the result is
+     * returned from the hotword detector service.
      *
      * <p>This is a PersistableBundle so it doesn't allow any remotable objects or other contents
      * that can be used to communicate with other processes.
@@ -348,16 +368,16 @@
             // Remove the proximity key from the bundle before checking the bundle size. The
             // proximity value is added after the privileged module and can avoid the
             // maxBundleSize limitation.
-            if (mExtras.containsKey(EXTRA_PROXIMITY_METERS)) {
-                double proximityMeters = mExtras.getDouble(EXTRA_PROXIMITY_METERS);
-                mExtras.remove(EXTRA_PROXIMITY_METERS);
+            if (mExtras.containsKey(EXTRA_PROXIMITY)) {
+                int proximityValue = mExtras.getInt(EXTRA_PROXIMITY);
+                mExtras.remove(EXTRA_PROXIMITY);
                 // Skip checking parcelable size if the new bundle size is 0. Newly empty bundle
                 // has parcelable size of 4, but the default bundle has parcelable size of 0.
                 if (mExtras.size() > 0) {
                     Preconditions.checkArgumentInRange(getParcelableSize(mExtras), 0,
                             getMaxBundleSize(), "extras");
                 }
-                mExtras.putDouble(EXTRA_PROXIMITY_METERS, proximityMeters);
+                mExtras.putInt(EXTRA_PROXIMITY, proximityValue);
             } else {
                 Preconditions.checkArgumentInRange(getParcelableSize(mExtras), 0,
                         getMaxBundleSize(), "extras");
@@ -372,6 +392,52 @@
         return List.copyOf(mAudioStreams);
     }
 
+    /**
+     * Adds proximity level, either near or far, that is mapped for the given distance into
+     * the bundle. The proximity value is provided by the system, on devices that support detecting
+     * proximity of nearby users, to help disambiguate which nearby device should respond.
+     * This mapping will be excluded from the max bundle size calculation because this mapping is
+     * included after the result is returned from the hotword detector service. The value will not
+     * be included if the proximity was unknown.
+     *
+     * @hide
+     */
+    public void setProximity(double distance) {
+        int proximityLevel = convertToProximityLevel(distance);
+        if (proximityLevel != PROXIMITY_UNKNOWN) {
+            mExtras.putInt(EXTRA_PROXIMITY, proximityLevel);
+        }
+    }
+
+    /**
+     * Returns proximity level, which can be either of {@link HotwordDetectedResult#PROXIMITY_NEAR}
+     * or {@link HotwordDetectedResult#PROXIMITY_FAR}. If the proximity is unknown, it will
+     * return {@link HotwordDetectedResult#PROXIMITY_UNKNOWN}.
+     */
+    @ProximityValue
+    public int getProximity() {
+        return mExtras.getInt(EXTRA_PROXIMITY, PROXIMITY_UNKNOWN);
+    }
+
+    /**
+     * Mapping of the proximity distance (meters) to proximity values, unknown, near, and far.
+     * Currently, this mapping is handled by HotwordDetectedResult because it handles just
+     * HotwordDetectionConnection which we know the mapping of. However, the mapping will need to
+     * move to a more centralized place once there are more clients.
+     *
+     * TODO(b/258531144): Move the proximity mapping to a central location
+     */
+    @ProximityValue
+    private int convertToProximityLevel(double distance) {
+        if (distance < 0) {
+            return PROXIMITY_UNKNOWN;
+        } else if (distance <= 3) {
+            return PROXIMITY_NEAR;
+        } else {
+            return PROXIMITY_FAR;
+        }
+    }
+
     @DataClass.Suppress("addAudioStreams")
     abstract static class BaseBuilder {
         /**
@@ -432,7 +498,7 @@
         CONFIDENCE_LEVEL_HIGH,
         CONFIDENCE_LEVEL_VERY_HIGH
     })
-    @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+    @Retention(RetentionPolicy.SOURCE)
     @DataClass.Generated.Member
     public @interface ConfidenceLevel {}
 
@@ -463,7 +529,7 @@
         LIMIT_HOTWORD_OFFSET_MAX_VALUE,
         LIMIT_AUDIO_CHANNEL_MAX_VALUE
     })
-    @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+    @Retention(RetentionPolicy.SOURCE)
     @DataClass.Generated.Member
     /* package-private */ @interface Limit {}
 
@@ -479,6 +545,30 @@
         }
     }
 
+    /** @hide */
+    @IntDef(prefix = "PROXIMITY_", value = {
+        PROXIMITY_UNKNOWN,
+        PROXIMITY_NEAR,
+        PROXIMITY_FAR
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @DataClass.Generated.Member
+    public @interface Proximity {}
+
+    /** @hide */
+    @DataClass.Generated.Member
+    public static String proximityToString(@Proximity int value) {
+        switch (value) {
+            case PROXIMITY_UNKNOWN:
+                    return "PROXIMITY_UNKNOWN";
+            case PROXIMITY_NEAR:
+                    return "PROXIMITY_NEAR";
+            case PROXIMITY_FAR:
+                    return "PROXIMITY_FAR";
+            default: return Integer.toHexString(value);
+        }
+    }
+
     @DataClass.Generated.Member
     /* package-private */ HotwordDetectedResult(
             @HotwordConfidenceLevelValue int confidenceLevel,
@@ -605,12 +695,14 @@
      * versions of Android.
      *
      * <p>After the trigger happens, a special case of proximity-related extra, with the key of
-     * 'android.service.voice.extra.PROXIMITY_METERS' and the value of distance in meters (double),
-     * will be stored to enable proximity logic. The proximity meters is provided by the system,
-     * on devices that support detecting proximity of nearby users, to help disambiguate which
-     * nearby device should respond. When the proximity is unknown, the proximity value will not
-     * be stored. This mapping will be excluded from the max bundle size calculation because this
-     * mapping is included after the result is returned from the hotword detector service.
+     * 'android.service.voice.extra.PROXIMITY_VALUE' and the value of proximity value (integer)
+     * will be stored to enable proximity logic. {@link HotwordDetectedResult#PROXIMITY_NEAR} will
+     * indicate 'NEAR' proximity and {@link HotwordDetectedResult#PROXIMITY_FAR} will indicate 'FAR'
+     * proximity. The proximity value is provided by the system, on devices that support detecting
+     * proximity of nearby users, to help disambiguate which nearby device should respond. When the
+     * proximity is unknown, the proximity value will not be stored. This mapping will be excluded
+     * from the max bundle size calculation because this mapping is included after the result is
+     * returned from the hotword detector service.
      *
      * <p>This is a PersistableBundle so it doesn't allow any remotable objects or other contents
      * that can be used to communicate with other processes.
@@ -923,12 +1015,14 @@
          * versions of Android.
          *
          * <p>After the trigger happens, a special case of proximity-related extra, with the key of
-         * 'android.service.voice.extra.PROXIMITY_METERS' and the value of distance in meters (double),
-         * will be stored to enable proximity logic. The proximity meters is provided by the system,
-         * on devices that support detecting proximity of nearby users, to help disambiguate which
-         * nearby device should respond. When the proximity is unknown, the proximity value will not
-         * be stored. This mapping will be excluded from the max bundle size calculation because this
-         * mapping is included after the result is returned from the hotword detector service.
+         * 'android.service.voice.extra.PROXIMITY_VALUE' and the value of proximity value (integer)
+         * will be stored to enable proximity logic. {@link HotwordDetectedResult#PROXIMITY_NEAR} will
+         * indicate 'NEAR' proximity and {@link HotwordDetectedResult#PROXIMITY_FAR} will indicate 'FAR'
+         * proximity. The proximity value is provided by the system, on devices that support detecting
+         * proximity of nearby users, to help disambiguate which nearby device should respond. When the
+         * proximity is unknown, the proximity value will not be stored. This mapping will be excluded
+         * from the max bundle size calculation because this mapping is included after the result is
+         * returned from the hotword detector service.
          *
          * <p>This is a PersistableBundle so it doesn't allow any remotable objects or other contents
          * that can be used to communicate with other processes.
@@ -1003,10 +1097,10 @@
     }
 
     @DataClass.Generated(
-            time = 1666342044844L,
+            time = 1668385264834L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/service/voice/HotwordDetectedResult.java",
-            inputSignatures = "public static final  int CONFIDENCE_LEVEL_NONE\npublic static final  int CONFIDENCE_LEVEL_LOW\npublic static final  int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final  int CONFIDENCE_LEVEL_HIGH\npublic static final  int CONFIDENCE_LEVEL_VERY_HIGH\npublic static final  int HOTWORD_OFFSET_UNSET\npublic static final  int AUDIO_CHANNEL_UNSET\nprivate static final  int LIMIT_HOTWORD_OFFSET_MAX_VALUE\nprivate static final  int LIMIT_AUDIO_CHANNEL_MAX_VALUE\npublic static final  java.lang.String EXTRA_PROXIMITY_METERS\nprivate final @android.service.voice.HotwordDetectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate  int mHotwordOffsetMillis\nprivate  int mHotwordDurationMillis\nprivate  int mAudioChannel\nprivate  boolean mHotwordDetectionPersonalized\nprivate final  int mScore\nprivate final  int mPersonalizedScore\nprivate final  int mHotwordPhraseId\nprivate final @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> mAudioStreams\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static  int sMaxBundleSize\nprivate static  int defaultConfidenceLevel()\nprivate static  int defaultScore()\nprivate static  int defaultPersonalizedScore()\npublic static  int getMaxScore()\nprivate static  int defaultHotwordPhraseId()\npublic static  int getMaxHotwordPhraseId()\nprivate static  java.util.List<android.service.voice.HotwordAudioStream> defaultAudioStreams()\nprivate static  android.os.PersistableBundle defaultExtras()\npublic static  int getMaxBundleSize()\npublic @android.annotation.Nullable android.media.MediaSyncEvent getMediaSyncEvent()\npublic static  int getParcelableSize(android.os.Parcelable)\npublic static  int getUsageSize(android.service.voice.HotwordDetectedResult)\nprivate static  int bitCount(long)\nprivate  void onConstructed()\npublic @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> getAudioStreams()\npublic  android.service.voice.HotwordDetectedResult.Builder buildUpon()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []")
+            inputSignatures = "public static final  int CONFIDENCE_LEVEL_NONE\npublic static final  int CONFIDENCE_LEVEL_LOW\npublic static final  int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final  int CONFIDENCE_LEVEL_HIGH\npublic static final  int CONFIDENCE_LEVEL_VERY_HIGH\npublic static final  int HOTWORD_OFFSET_UNSET\npublic static final  int AUDIO_CHANNEL_UNSET\nprivate static final  int LIMIT_HOTWORD_OFFSET_MAX_VALUE\nprivate static final  int LIMIT_AUDIO_CHANNEL_MAX_VALUE\nprivate static final  java.lang.String EXTRA_PROXIMITY\npublic static final  int PROXIMITY_UNKNOWN\npublic static final  int PROXIMITY_NEAR\npublic static final  int PROXIMITY_FAR\nprivate final @android.service.voice.HotwordDetectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate  int mHotwordOffsetMillis\nprivate  int mHotwordDurationMillis\nprivate  int mAudioChannel\nprivate  boolean mHotwordDetectionPersonalized\nprivate final  int mScore\nprivate final  int mPersonalizedScore\nprivate final  int mHotwordPhraseId\nprivate final @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> mAudioStreams\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static  int sMaxBundleSize\nprivate static  int defaultConfidenceLevel()\nprivate static  int defaultScore()\nprivate static  int defaultPersonalizedScore()\npublic static  int getMaxScore()\nprivate static  int defaultHotwordPhraseId()\npublic static  int getMaxHotwordPhraseId()\nprivate static  java.util.List<android.service.voice.HotwordAudioStream> defaultAudioStreams()\nprivate static  android.os.PersistableBundle defaultExtras()\npublic static  int getMaxBundleSize()\npublic @android.annotation.Nullable android.media.MediaSyncEvent getMediaSyncEvent()\npublic static  int getParcelableSize(android.os.Parcelable)\npublic static  int getUsageSize(android.service.voice.HotwordDetectedResult)\nprivate static  int bitCount(long)\nprivate  void onConstructed()\npublic @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> getAudioStreams()\npublic  void setProximity(double)\npublic @android.service.voice.HotwordDetectedResult.ProximityValue int getProximity()\nprivate @android.service.voice.HotwordDetectedResult.ProximityValue int convertToProximityLevel(double)\npublic  android.service.voice.HotwordDetectedResult.Builder buildUpon()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/service/voice/HotwordDetectionService.java b/core/java/android/service/voice/HotwordDetectionService.java
index df69cc0..552a793 100644
--- a/core/java/android/service/voice/HotwordDetectionService.java
+++ b/core/java/android/service/voice/HotwordDetectionService.java
@@ -25,6 +25,7 @@
 import android.annotation.SdkConstant;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.app.Service;
 import android.content.ContentCaptureOptions;
 import android.content.Context;
@@ -90,10 +91,10 @@
     /**
      * Feature flag for Attention Service.
      *
-     * TODO(b/247920386): Add TestApi annotation
      * @hide
      */
-    public static final boolean ENABLE_PROXIMITY_RESULT = false;
+    @TestApi
+    public static final boolean ENABLE_PROXIMITY_RESULT = true;
 
     /**
      * Indicates that the updated status is successful.
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 104570d0..007478a 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -31,6 +31,7 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.annotation.FloatRange;
+import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
@@ -657,6 +658,7 @@
          * Called once to initialize the engine.  After returning, the
          * engine's surface will be created by the framework.
          */
+        @MainThread
         public void onCreate(SurfaceHolder surfaceHolder) {
         }
 
@@ -665,6 +667,7 @@
          * surface will be destroyed and this Engine object is no longer
          * valid.
          */
+        @MainThread
         public void onDestroy() {
         }
 
@@ -673,6 +676,7 @@
          * hidden.  <em>It is very important that a wallpaper only use
          * CPU while it is visible.</em>.
          */
+        @MainThread
         public void onVisibilityChanged(boolean visible) {
         }
 
@@ -683,6 +687,7 @@
          *
          * @param insets Insets to apply.
          */
+        @MainThread
         public void onApplyWindowInsets(WindowInsets insets) {
         }
 
@@ -693,6 +698,7 @@
          * user is interacting with, so if it is slow you will get fewer
          * move events.
          */
+        @MainThread
         public void onTouchEvent(MotionEvent event) {
         }
 
@@ -702,6 +708,7 @@
          * call to {@link WallpaperManager#setWallpaperOffsets(IBinder, float, float)
          * WallpaperManager.setWallpaperOffsets()}.
          */
+        @MainThread
         public void onOffsetsChanged(float xOffset, float yOffset,
                 float xOffsetStep, float yOffsetStep,
                 int xPixelOffset, int yPixelOffset) {
@@ -724,6 +731,7 @@
          * @return If returning a result, create a Bundle and place the
          * result data in to it.  Otherwise return null.
          */
+        @MainThread
         public Bundle onCommand(String action, int x, int y, int z,
                 Bundle extras, boolean resultRequested) {
             return null;
@@ -742,6 +750,7 @@
          * @hide
          */
         @SystemApi
+        @MainThread
         public void onAmbientModeChanged(boolean inAmbientMode, long animationDuration) {
         }
 
@@ -749,6 +758,7 @@
          * Called when an application has changed the desired virtual size of
          * the wallpaper.
          */
+        @MainThread
         public void onDesiredSizeChanged(int desiredWidth, int desiredHeight) {
         }
 
@@ -756,6 +766,7 @@
          * Convenience for {@link SurfaceHolder.Callback#surfaceChanged
          * SurfaceHolder.Callback.surfaceChanged()}.
          */
+        @MainThread
         public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
         }
 
@@ -763,6 +774,7 @@
          * Convenience for {@link SurfaceHolder.Callback2#surfaceRedrawNeeded
          * SurfaceHolder.Callback.surfaceRedrawNeeded()}.
          */
+        @MainThread
         public void onSurfaceRedrawNeeded(SurfaceHolder holder) {
         }
 
@@ -770,6 +782,7 @@
          * Convenience for {@link SurfaceHolder.Callback#surfaceCreated
          * SurfaceHolder.Callback.surfaceCreated()}.
          */
+        @MainThread
         public void onSurfaceCreated(SurfaceHolder holder) {
         }
 
@@ -777,6 +790,7 @@
          * Convenience for {@link SurfaceHolder.Callback#surfaceDestroyed
          * SurfaceHolder.Callback.surfaceDestroyed()}.
          */
+        @MainThread
         public void onSurfaceDestroyed(SurfaceHolder holder) {
         }
 
@@ -787,6 +801,7 @@
          * @param zoom the zoom level, between 0 indicating fully zoomed in and 1 indicating fully
          *             zoomed out.
          */
+        @MainThread
         public void onZoomChanged(@FloatRange(from = 0f, to = 1f) float zoom) {
         }
 
@@ -836,6 +851,7 @@
          *
          * @return Wallpaper colors.
          */
+        @MainThread
         public @Nullable WallpaperColors onComputeColors() {
             return null;
         }
@@ -2510,6 +2526,7 @@
      * when the wallpaper is currently set as the active wallpaper and the user
      * is in the wallpaper picker viewing a preview of it as well.
      */
+    @MainThread
     public abstract Engine onCreateEngine();
 
     @Override
diff --git a/core/java/android/util/OWNERS b/core/java/android/util/OWNERS
index d4cf6e6..3772006 100644
--- a/core/java/android/util/OWNERS
+++ b/core/java/android/util/OWNERS
@@ -1,6 +1,6 @@
 per-file Dump* = file:/core/java/com/android/internal/util/dump/OWNERS
 per-file FeatureFlagUtils.java = sbasi@google.com
-per-file FeatureFlagUtils.java = tmfang@google.com
+per-file FeatureFlagUtils.java = edgarwang@google.com
 
 per-file AttributeSet.java = file:/core/java/android/content/res/OWNERS
 per-file TypedValue.java = file:/core/java/android/content/res/OWNERS
diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java
index 510bde1..44168ca 100644
--- a/core/java/android/view/AccessibilityInteractionController.java
+++ b/core/java/android/view/AccessibilityInteractionController.java
@@ -21,6 +21,7 @@
 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_REQUESTED_KEY;
 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
 
+import android.accessibilityservice.AccessibilityService;
 import android.annotation.NonNull;
 import android.graphics.Matrix;
 import android.graphics.Rect;
@@ -46,11 +47,13 @@
 import android.view.accessibility.AccessibilityNodeProvider;
 import android.view.accessibility.AccessibilityRequestPreparer;
 import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
+import android.window.ScreenCapture;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.SomeArgs;
+import com.android.internal.util.function.pooled.PooledLambda;
 
 import java.util.ArrayDeque;
 import java.util.ArrayList;
@@ -588,6 +591,43 @@
         }
     }
 
+    /**
+     * Take a screenshot using {@link ScreenCapture} of this {@link ViewRootImpl}'s {@link
+     * SurfaceControl}.
+     */
+    public void takeScreenshotOfWindowClientThread(int interactionId,
+            ScreenCapture.ScreenCaptureListener listener,
+            IAccessibilityInteractionConnectionCallback callback) {
+        Message message = PooledLambda.obtainMessage(
+                AccessibilityInteractionController::takeScreenshotOfWindowUiThread,
+                this, interactionId, listener, callback);
+
+        // Screenshot results are returned to the service asynchronously, so the same-thread
+        // message wait logic from #scheduleMessage() is not needed.
+        mHandler.sendMessage(message);
+    }
+
+    private void takeScreenshotOfWindowUiThread(int interactionId,
+            ScreenCapture.ScreenCaptureListener listener,
+            IAccessibilityInteractionConnectionCallback callback) {
+        try {
+            if ((mViewRootImpl.getWindowFlags() & WindowManager.LayoutParams.FLAG_SECURE) != 0) {
+                callback.sendTakeScreenshotOfWindowError(
+                        AccessibilityService.ERROR_TAKE_SCREENSHOT_SECURE_WINDOW, interactionId);
+                return;
+            }
+            final ScreenCapture.LayerCaptureArgs captureArgs =
+                    new ScreenCapture.LayerCaptureArgs.Builder(mViewRootImpl.getSurfaceControl())
+                            .setChildrenOnly(false).setUid(Process.myUid()).build();
+            if (ScreenCapture.captureLayers(captureArgs, listener) != 0) {
+                callback.sendTakeScreenshotOfWindowError(
+                        AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR, interactionId);
+            }
+        } catch (RemoteException re) {
+            /* ignore - the other side will time out */
+        }
+    }
+
     public void findFocusClientThread(long accessibilityNodeId, int focusType,
             Region interactiveRegion, int interactionId,
             IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java
index 6d25523..00170cb 100644
--- a/core/java/android/view/VelocityTracker.java
+++ b/core/java/android/view/VelocityTracker.java
@@ -28,7 +28,7 @@
 import java.util.Map;
 
 /**
- * Helper for tracking the velocity of touch events, for implementing
+ * Helper for tracking the velocity of motion events, for implementing
  * flinging and other such gestures.
  *
  * Use {@link #obtain} to retrieve a new instance of the class when you are going
@@ -43,6 +43,15 @@
 
     private static final int ACTIVE_POINTER_ID = -1;
 
+    /** @hide */
+    @IntDef(value = {
+            MotionEvent.AXIS_X,
+            MotionEvent.AXIS_Y,
+            MotionEvent.AXIS_SCROLL
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface VelocityTrackableMotionEventAxis {}
+
     /**
      * Velocity Tracker Strategy: Invalid.
      *
@@ -306,10 +315,12 @@
     }
 
     /**
-     * Checks whether a given motion axis is supported for velocity tracking.
+     * Checks whether a given velocity-trackable {@link MotionEvent} axis is supported for velocity
+     * tracking by this {@link VelocityTracker} instance (refer to
+     * {@link #getAxisVelocity(int, int)} for a list of potentially velocity-trackable axes).
      *
-     * <p>The axis values that would make sense to use for this method are the ones defined in the
-     * {@link MotionEvent} class.
+     * <p>Note that the value returned from this method will stay the same for a given instance, so
+     * a single check for axis support is enough per a {@link VelocityTracker} instance.
      *
      * @param axis The axis to check for velocity support.
      * @return {@code true} if {@code axis} is supported for velocity tracking, or {@code false}
@@ -317,7 +328,7 @@
      * @see #getAxisVelocity(int, int)
      * @see #getAxisVelocity(int)
      */
-    public boolean isAxisSupported(int axis) {
+    public boolean isAxisSupported(@VelocityTrackableMotionEventAxis int axis) {
         return nativeIsAxisSupported(axis);
     }
 
@@ -421,13 +432,16 @@
      * calling this function.
      *
      * <p>In addition to {@link MotionEvent#AXIS_X} and {@link MotionEvent#AXIS_Y} which have been
-     * supported since the introduction of this class, the following axes are supported for this
+     * supported since the introduction of this class, the following axes can be candidates for this
      * method:
      * <ul>
      *   <li> {@link MotionEvent#AXIS_SCROLL}: supported starting
      *        {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}
      * </ul>
      *
+     * <p>Before accessing velocities of an axis using this method, check that your
+     * {@link VelocityTracker} instance supports the axis by using {@link #isAxisSupported(int)}.
+     *
      * @param axis Which axis' velocity to return.
      * @param id Which pointer's velocity to return.
      * @return The previously computed velocity for {@code axis} for pointer ID of {@code id} if
@@ -435,7 +449,7 @@
      * for the axis.
      * @see #isAxisSupported(int)
      */
-    public float getAxisVelocity(int axis, int id) {
+    public float getAxisVelocity(@VelocityTrackableMotionEventAxis int axis, int id) {
         return nativeGetVelocity(mPtr, axis, id);
     }
 
@@ -450,7 +464,7 @@
      * @see #isAxisSupported(int)
      * @see #getAxisVelocity(int, int)
      */
-    public float getAxisVelocity(int axis) {
+    public float getAxisVelocity(@VelocityTrackableMotionEventAxis int axis) {
         return nativeGetVelocity(mPtr, axis, ACTIVE_POINTER_ID);
     }
 
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 63602393..7e8ebd7 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -88,6 +88,7 @@
 import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER;
 
 import android.Manifest;
+import android.accessibilityservice.AccessibilityService;
 import android.animation.AnimationHandler;
 import android.animation.LayoutTransition;
 import android.annotation.AnyThread;
@@ -199,6 +200,7 @@
 import android.window.CompatOnBackInvokedCallback;
 import android.window.OnBackInvokedCallback;
 import android.window.OnBackInvokedDispatcher;
+import android.window.ScreenCapture;
 import android.window.SurfaceSyncGroup;
 import android.window.WindowOnBackInvokedDispatcher;
 
@@ -10707,6 +10709,25 @@
                         .notifyOutsideTouchClientThread();
             }
         }
+
+        @Override
+        public void takeScreenshotOfWindow(int interactionId,
+                ScreenCapture.ScreenCaptureListener listener,
+                IAccessibilityInteractionConnectionCallback callback) {
+            ViewRootImpl viewRootImpl = mViewRootImpl.get();
+            if (viewRootImpl != null && viewRootImpl.mView != null) {
+                viewRootImpl.getAccessibilityInteractionController()
+                        .takeScreenshotOfWindowClientThread(interactionId, listener, callback);
+            } else {
+                try {
+                    callback.sendTakeScreenshotOfWindowError(
+                            AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR,
+                            interactionId);
+                } catch (RemoteException re) {
+                    /* best effort - ignore */
+                }
+            }
+        }
     }
 
     /**
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index e3ffc9d..7030ab5 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -22,7 +22,9 @@
 import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_MASK;
 import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_MASK;
 
+import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
@@ -31,17 +33,21 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.Message;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.util.Log;
 import android.util.LongSparseArray;
+import android.util.Pair;
 import android.util.SparseArray;
 import android.util.SparseLongArray;
 import android.view.Display;
 import android.view.ViewConfiguration;
+import android.window.ScreenCapture;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
@@ -53,6 +59,7 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Queue;
+import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
@@ -146,6 +153,10 @@
 
     private boolean mPerformAccessibilityActionResult;
 
+    // SparseArray of interaction ID -> screenshot executor+callback.
+    private final SparseArray<Pair<Executor, AccessibilityService.TakeScreenshotCallback>>
+            mTakeScreenshotOfWindowCallbacks = new SparseArray<>();
+
     private Message mSameThreadMessage;
 
     private int mInteractionIdWaitingForPrefetchResult = -1;
@@ -779,6 +790,59 @@
     }
 
     /**
+     * Takes a screenshot of the window with the provided {@code accessibilityWindowId} and
+     * returns the answer asynchronously. This async behavior is similar to {@link
+     * AccessibilityService#takeScreenshot} but unlike other methods in this class which perform
+     * synchronous waiting in the AccessibilityService client.
+     *
+     * @see AccessibilityService#takeScreenshotOfWindow
+     */
+    public void takeScreenshotOfWindow(int connectionId, int accessibilityWindowId,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull AccessibilityService.TakeScreenshotCallback callback) {
+        synchronized (mInstanceLock) {
+            try {
+                IAccessibilityServiceConnection connection = getConnection(connectionId);
+                if (connection == null) {
+                    executor.execute(() -> callback.onFailure(
+                            AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR));
+                    return;
+                }
+                final long identityToken = Binder.clearCallingIdentity();
+                try {
+                    final int interactionId = mInteractionIdCounter.getAndIncrement();
+                    mTakeScreenshotOfWindowCallbacks.put(interactionId,
+                            Pair.create(executor, callback));
+                    // Create a ScreenCaptureListener to receive the screenshot directly from
+                    // SurfaceFlinger instead of requiring an extra IPC from the app:
+                    //   A11yService -> App -> SurfaceFlinger -> A11yService
+                    ScreenCapture.ScreenCaptureListener listener =
+                            new ScreenCapture.ScreenCaptureListener(
+                                    screenshot -> sendWindowScreenshotSuccess(screenshot,
+                                            interactionId));
+                    connection.takeScreenshotOfWindow(accessibilityWindowId, interactionId,
+                            listener, this);
+                    new Handler(Looper.getMainLooper()).postDelayed(() -> {
+                        synchronized (mInstanceLock) {
+                            // Notify failure if we still haven't sent a response after timeout.
+                            if (mTakeScreenshotOfWindowCallbacks.contains(interactionId)) {
+                                sendTakeScreenshotOfWindowError(
+                                        AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR,
+                                        interactionId);
+                            }
+                        }
+                    }, TIMEOUT_INTERACTION_MILLIS);
+                } finally {
+                    Binder.restoreCallingIdentity(identityToken);
+                }
+            } catch (RemoteException re) {
+                executor.execute(() -> callback.onFailure(
+                        AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR));
+            }
+        }
+    }
+
+    /**
      * Finds {@link AccessibilityNodeInfo}s by View text. The match is case
      * insensitive containment. The search is performed in the window whose
      * id is specified and starts from the node whose accessibility id is
@@ -1254,6 +1318,55 @@
     }
 
     /**
+     * Sends the result of a window screenshot request to the requesting client.
+     *
+     * {@link #takeScreenshotOfWindow} does not perform synchronous waiting, so this method
+     * does not notify any wait lock.
+     */
+    private void sendWindowScreenshotSuccess(ScreenCapture.ScreenshotHardwareBuffer screenshot,
+            int interactionId) {
+        if (screenshot == null) {
+            sendTakeScreenshotOfWindowError(
+                    AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR, interactionId);
+            return;
+        }
+        synchronized (mInstanceLock) {
+            if (mTakeScreenshotOfWindowCallbacks.contains(interactionId)) {
+                final AccessibilityService.ScreenshotResult result =
+                        new AccessibilityService.ScreenshotResult(screenshot.getHardwareBuffer(),
+                                screenshot.getColorSpace(), SystemClock.uptimeMillis());
+                final Pair<Executor, AccessibilityService.TakeScreenshotCallback> pair =
+                        mTakeScreenshotOfWindowCallbacks.get(interactionId);
+                final Executor executor = pair.first;
+                final AccessibilityService.TakeScreenshotCallback callback = pair.second;
+                executor.execute(() -> callback.onSuccess(result));
+                mTakeScreenshotOfWindowCallbacks.remove(interactionId);
+            }
+        }
+    }
+
+    /**
+     * Sends an error code for a window screenshot request to the requesting client.
+     *
+     * @param errorCode The error code from {@link AccessibilityService.ScreenshotErrorCode}.
+     * @param interactionId The interaction id of the request.
+     */
+    @Override
+    public void sendTakeScreenshotOfWindowError(
+            @AccessibilityService.ScreenshotErrorCode int errorCode, int interactionId) {
+        synchronized (mInstanceLock) {
+            if (mTakeScreenshotOfWindowCallbacks.contains(interactionId)) {
+                final Pair<Executor, AccessibilityService.TakeScreenshotCallback> pair =
+                        mTakeScreenshotOfWindowCallbacks.get(interactionId);
+                final Executor executor = pair.first;
+                final AccessibilityService.TakeScreenshotCallback callback = pair.second;
+                executor.execute(() -> callback.onFailure(errorCode));
+                mTakeScreenshotOfWindowCallbacks.remove(interactionId);
+            }
+        }
+    }
+
+    /**
      * Clears the result state.
      */
     private void clearResultLocked() {
diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
index 472a363..fb01921 100644
--- a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
@@ -22,6 +22,7 @@
 import android.view.MagnificationSpec;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
+import android.window.ScreenCapture;
 
 /**
  * Interface for interaction between the AccessibilityManagerService
@@ -60,4 +61,8 @@
     void clearAccessibilityFocus();
 
     void notifyOutsideTouch();
+
+    void takeScreenshotOfWindow(int interactionId,
+        in ScreenCapture.ScreenCaptureListener listener,
+        IAccessibilityInteractionConnectionCallback callback);
 }
diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
index 231e75a..456bf58 100644
--- a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
@@ -63,4 +63,9 @@
      */
     @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     void setPerformAccessibilityActionResult(boolean succeeded, int interactionId);
+
+    /**
+    * Sends an error code for a window screenshot request to the requesting client.
+    */
+    void sendTakeScreenshotOfWindowError(int errorCode, int interactionId);
 }
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 2d29c59..c7a2d24 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -454,6 +454,23 @@
     }
 
     /**
+     * Sets whether a container is being drag-resized.
+     * When {@code true}, the client will reuse a single (larger) surface size to avoid
+     * continuous allocations on every size change.
+     *
+     * @param container WindowContainerToken of the task that changed its drag resizing state
+     * @hide
+     */
+    @NonNull
+    public WindowContainerTransaction setDragResizing(@NonNull WindowContainerToken container,
+            boolean dragResizing) {
+        final Change change = getOrCreateChange(container.asBinder());
+        change.mChangeMask |= Change.CHANGE_DRAG_RESIZING;
+        change.mDragResizing = dragResizing;
+        return this;
+    }
+
+    /**
      * Sends a pending intent in sync.
      * @param sender The PendingIntent sender.
      * @param intent The fillIn intent to patch over the sender's base intent.
@@ -894,12 +911,14 @@
         public static final int CHANGE_IGNORE_ORIENTATION_REQUEST = 1 << 5;
         public static final int CHANGE_FORCE_NO_PIP = 1 << 6;
         public static final int CHANGE_FORCE_TRANSLUCENT = 1 << 7;
+        public static final int CHANGE_DRAG_RESIZING = 1 << 8;
 
         private final Configuration mConfiguration = new Configuration();
         private boolean mFocusable = true;
         private boolean mHidden = false;
         private boolean mIgnoreOrientationRequest = false;
         private boolean mForceTranslucent = false;
+        private boolean mDragResizing = false;
 
         private int mChangeMask = 0;
         private @ActivityInfo.Config int mConfigSetMask = 0;
@@ -920,6 +939,7 @@
             mHidden = in.readBoolean();
             mIgnoreOrientationRequest = in.readBoolean();
             mForceTranslucent = in.readBoolean();
+            mDragResizing = in.readBoolean();
             mChangeMask = in.readInt();
             mConfigSetMask = in.readInt();
             mWindowSetMask = in.readInt();
@@ -968,6 +988,9 @@
             if ((other.mChangeMask & CHANGE_FORCE_TRANSLUCENT) != 0) {
                 mForceTranslucent = other.mForceTranslucent;
             }
+            if ((other.mChangeMask & CHANGE_DRAG_RESIZING) != 0) {
+                mDragResizing = other.mDragResizing;
+            }
             mChangeMask |= other.mChangeMask;
             if (other.mActivityWindowingMode >= 0) {
                 mActivityWindowingMode = other.mActivityWindowingMode;
@@ -1027,6 +1050,15 @@
             return mForceTranslucent;
         }
 
+        /** Gets the requested drag resizing state. */
+        public boolean getDragResizing() {
+            if ((mChangeMask & CHANGE_DRAG_RESIZING) == 0) {
+                throw new RuntimeException("Drag resizing not set. "
+                        + "Check CHANGE_DRAG_RESIZING first");
+            }
+            return mDragResizing;
+        }
+
         public int getChangeMask() {
             return mChangeMask;
         }
@@ -1088,6 +1120,9 @@
             if ((mChangeMask & CHANGE_FOCUSABLE) != 0) {
                 sb.append("focusable:" + mFocusable + ",");
             }
+            if ((mChangeMask & CHANGE_DRAG_RESIZING) != 0) {
+                sb.append("dragResizing:" + mDragResizing + ",");
+            }
             if (mBoundsChangeTransaction != null) {
                 sb.append("hasBoundsTransaction,");
             }
@@ -1105,6 +1140,7 @@
             dest.writeBoolean(mHidden);
             dest.writeBoolean(mIgnoreOrientationRequest);
             dest.writeBoolean(mForceTranslucent);
+            dest.writeBoolean(mDragResizing);
             dest.writeInt(mChangeMask);
             dest.writeInt(mConfigSetMask);
             dest.writeInt(mWindowSetMask);
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 3e988e6..145aeaf 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -2402,7 +2402,7 @@
             return;
         }
         final ThreadedRenderer renderer = getThreadedRenderer();
-        if (renderer != null) {
+        if (renderer != null && !CAPTION_ON_SHELL) {
             loadBackgroundDrawablesIfNeeded();
             WindowInsets rootInsets = getRootWindowInsets();
             mBackdropFrameRenderer = new BackdropFrameRenderer(this, renderer,
diff --git a/core/java/com/android/internal/widget/LockSettingsInternal.java b/core/java/com/android/internal/widget/LockSettingsInternal.java
index 5b08bb1..6063c90 100644
--- a/core/java/com/android/internal/widget/LockSettingsInternal.java
+++ b/core/java/com/android/internal/widget/LockSettingsInternal.java
@@ -54,6 +54,12 @@
     // TODO(b/183140900) split store escrow key errors into detailed ones.
 
     /**
+     * This is called when Weaver is guaranteed to be available (if the device supports Weaver).
+     * It does any synthetic password related work that was delayed from earlier in the boot.
+     */
+    public abstract void onThirdPartyAppsStarted();
+
+    /**
      * Unlocks the credential-encrypted storage for the given user if the user is not secured, i.e.
      * doesn't have an LSKF.
      * <p>
diff --git a/core/res/res/xml/config_user_types.xml b/core/res/res/xml/config_user_types.xml
index 7663150..df6b7b2 100644
--- a/core/res/res/xml/config_user_types.xml
+++ b/core/res/res/xml/config_user_types.xml
@@ -83,6 +83,8 @@
 For profile and full users:
     default-restrictions (with values defined in UserRestrictionUtils.USER_RESTRICTIONS)
     enabled
+    user-properties
+    max-allowed
 For profile users only:
     max-allowed-per-parent
     icon-badge
diff --git a/core/tests/coretests/src/android/app/activity/LocalReceiver.java b/core/tests/coretests/src/android/app/activity/LocalReceiver.java
index 7f81339..5ac84f8 100644
--- a/core/tests/coretests/src/android/app/activity/LocalReceiver.java
+++ b/core/tests/coretests/src/android/app/activity/LocalReceiver.java
@@ -36,7 +36,8 @@
         if (BroadcastTest.BROADCAST_FAIL_REGISTER.equals(intent.getAction())) {
             resultString = "Successfully registered, but expected it to fail";
             try {
-                context.registerReceiver(this, new IntentFilter("foo.bar"));
+                context.registerReceiver(this, new IntentFilter("foo.bar"),
+                        Context.RECEIVER_EXPORTED_UNAUDITED);
                 context.unregisterReceiver(this);
             } catch (ReceiverCallNotAllowedException e) {
                 //resultString = "This is the correct behavior but not yet implemented";
diff --git a/core/tests/coretests/src/android/app/activity/ServiceTest.java b/core/tests/coretests/src/android/app/activity/ServiceTest.java
index c89f37d..3f3d6a3 100644
--- a/core/tests/coretests/src/android/app/activity/ServiceTest.java
+++ b/core/tests/coretests/src/android/app/activity/ServiceTest.java
@@ -172,7 +172,7 @@
                 pidResult.complete(intent.getIntExtra(EXTRA_PID, NOT_STARTED));
                 mContext.unregisterReceiver(this);
             }
-        }, new IntentFilter(ACTION_SERVICE_STARTED));
+        }, new IntentFilter(ACTION_SERVICE_STARTED), Context.RECEIVER_EXPORTED_UNAUDITED);
 
         serviceTrigger.run();
         try {
diff --git a/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java b/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
index 67b24ec..bbd2ef3 100644
--- a/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
+++ b/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
@@ -63,18 +63,22 @@
     public void testBackupLogger_rejectsRestoreLogs() {
         mLogger = new BackupRestoreEventLogger(BACKUP);
 
-        assertThat(mLogger.logItemsRestored(DATA_TYPE_1, /* count */ 5)).isFalse();
-        assertThat(mLogger.logItemsRestoreFailed(DATA_TYPE_1, /* count */ 5, ERROR_1)).isFalse();
-        assertThat(mLogger.logRestoreMetadata(DATA_TYPE_1, /* metadata */ "metadata")).isFalse();
+        mLogger.logItemsRestored(DATA_TYPE_1, /* count */ 5);
+        mLogger.logItemsRestoreFailed(DATA_TYPE_1, /* count */ 5, ERROR_1);
+        mLogger.logRestoreMetadata(DATA_TYPE_1, /* metadata */ "metadata");
+
+        assertThat(getResultForDataTypeIfPresent(mLogger, DATA_TYPE_1)).isEqualTo(Optional.empty());
     }
 
     @Test
     public void testRestoreLogger_rejectsBackupLogs() {
         mLogger = new BackupRestoreEventLogger(RESTORE);
 
-        assertThat(mLogger.logItemsBackedUp(DATA_TYPE_1, /* count */ 5)).isFalse();
-        assertThat(mLogger.logItemsBackupFailed(DATA_TYPE_1, /* count */ 5, ERROR_1)).isFalse();
-        assertThat(mLogger.logBackupMetaData(DATA_TYPE_1, /* metadata */ "metadata")).isFalse();
+        mLogger.logItemsBackedUp(DATA_TYPE_1, /* count */ 5);
+        mLogger.logItemsBackupFailed(DATA_TYPE_1, /* count */ 5, ERROR_1);
+        mLogger.logBackupMetaData(DATA_TYPE_1, /* metadata */ "metadata");
+
+        assertThat(getResultForDataTypeIfPresent(mLogger, DATA_TYPE_1)).isEqualTo(Optional.empty());
     }
 
     @Test
@@ -83,16 +87,17 @@
 
         for (int i = 0; i < DATA_TYPES_ALLOWED; i++) {
             String dataType = DATA_TYPE_1 + i;
-            assertThat(mLogger.logItemsBackedUp(dataType, /* count */ 5)).isTrue();
-            assertThat(mLogger.logItemsBackupFailed(dataType, /* count */ 5, /* error */ null))
-                    .isTrue();
-            assertThat(mLogger.logBackupMetaData(dataType, METADATA_1)).isTrue();
+            mLogger.logItemsBackedUp(dataType, /* count */ 5);
+            mLogger.logItemsBackupFailed(dataType, /* count */ 5, /* error */ null);
+            mLogger.logBackupMetaData(dataType, METADATA_1);
+
+            assertThat(getResultForDataTypeIfPresent(mLogger, dataType)).isNotEqualTo(
+                    Optional.empty());
         }
 
-        assertThat(mLogger.logItemsBackedUp(DATA_TYPE_2, /* count */ 5)).isFalse();
-        assertThat(mLogger.logItemsBackupFailed(DATA_TYPE_2, /* count */ 5, /* error */ null))
-                .isFalse();
-        assertThat(mLogger.logRestoreMetadata(DATA_TYPE_2, METADATA_1)).isFalse();
+        mLogger.logItemsBackedUp(DATA_TYPE_2, /* count */ 5);
+        mLogger.logItemsBackupFailed(DATA_TYPE_2, /* count */ 5, /* error */ null);
+        mLogger.logRestoreMetadata(DATA_TYPE_2, METADATA_1);
         assertThat(getResultForDataTypeIfPresent(mLogger, DATA_TYPE_2)).isEqualTo(Optional.empty());
     }
 
@@ -102,16 +107,17 @@
 
         for (int i = 0; i < DATA_TYPES_ALLOWED; i++) {
             String dataType = DATA_TYPE_1 + i;
-            assertThat(mLogger.logItemsRestored(dataType, /* count */ 5)).isTrue();
-            assertThat(mLogger.logItemsRestoreFailed(dataType, /* count */ 5, /* error */ null))
-                    .isTrue();
-            assertThat(mLogger.logRestoreMetadata(dataType, METADATA_1)).isTrue();
+            mLogger.logItemsRestored(dataType, /* count */ 5);
+            mLogger.logItemsRestoreFailed(dataType, /* count */ 5, /* error */ null);
+            mLogger.logRestoreMetadata(dataType, METADATA_1);
+
+            assertThat(getResultForDataTypeIfPresent(mLogger, dataType)).isNotEqualTo(
+                    Optional.empty());
         }
 
-        assertThat(mLogger.logItemsRestored(DATA_TYPE_2, /* count */ 5)).isFalse();
-        assertThat(mLogger.logItemsRestoreFailed(DATA_TYPE_2, /* count */ 5, /* error */ null))
-                .isFalse();
-        assertThat(mLogger.logRestoreMetadata(DATA_TYPE_2, METADATA_1)).isFalse();
+        mLogger.logItemsRestored(DATA_TYPE_2, /* count */ 5);
+        mLogger.logItemsRestoreFailed(DATA_TYPE_2, /* count */ 5, /* error */ null);
+        mLogger.logRestoreMetadata(DATA_TYPE_2, METADATA_1);
         assertThat(getResultForDataTypeIfPresent(mLogger, DATA_TYPE_2)).isEqualTo(Optional.empty());
     }
 
diff --git a/core/tests/coretests/src/android/app/timezonedetector/ParcelableTestSupport.java b/core/tests/coretests/src/android/app/time/ParcelableTestSupport.java
similarity index 82%
rename from core/tests/coretests/src/android/app/timezonedetector/ParcelableTestSupport.java
rename to core/tests/coretests/src/android/app/time/ParcelableTestSupport.java
index 0073d86..13e5e14 100644
--- a/core/tests/coretests/src/android/app/timezonedetector/ParcelableTestSupport.java
+++ b/core/tests/coretests/src/android/app/time/ParcelableTestSupport.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.app.timezonedetector;
+package android.app.time;
 
 import static org.junit.Assert.assertEquals;
 
@@ -48,6 +48,13 @@
     }
 
     public static <T extends Parcelable> void assertRoundTripParcelable(T instance) {
-        assertEquals(instance, roundTripParcelable(instance));
+        assertEqualsAndHashCode(instance, roundTripParcelable(instance));
+    }
+
+    /** Asserts that the objects are equal and return identical hash codes. */
+    public static void assertEqualsAndHashCode(Object one, Object two) {
+        assertEquals(one, two);
+        assertEquals(two, one);
+        assertEquals(one.hashCode(), two.hashCode());
     }
 }
diff --git a/core/tests/coretests/src/android/app/time/TimeCapabilitiesTest.java b/core/tests/coretests/src/android/app/time/TimeCapabilitiesTest.java
index c9b96c6..1a276ad 100644
--- a/core/tests/coretests/src/android/app/time/TimeCapabilitiesTest.java
+++ b/core/tests/coretests/src/android/app/time/TimeCapabilitiesTest.java
@@ -21,7 +21,8 @@
 import static android.app.time.Capabilities.CAPABILITY_NOT_APPLICABLE;
 import static android.app.time.Capabilities.CAPABILITY_NOT_SUPPORTED;
 import static android.app.time.Capabilities.CAPABILITY_POSSESSED;
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.time.ParcelableTestSupport.assertEqualsAndHashCode;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -55,7 +56,7 @@
         {
             TimeCapabilities one = builder1.build();
             TimeCapabilities two = builder2.build();
-            assertEquals(one, two);
+            assertEqualsAndHashCode(one, two);
         }
 
         builder2.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED);
@@ -69,7 +70,7 @@
         {
             TimeCapabilities one = builder1.build();
             TimeCapabilities two = builder2.build();
-            assertEquals(one, two);
+            assertEqualsAndHashCode(one, two);
         }
 
         builder2.setSetManualTimeCapability(CAPABILITY_NOT_ALLOWED);
@@ -83,7 +84,7 @@
         {
             TimeCapabilities one = builder1.build();
             TimeCapabilities two = builder2.build();
-            assertEquals(one, two);
+            assertEqualsAndHashCode(one, two);
         }
     }
 
diff --git a/core/tests/coretests/src/android/app/time/TimeStateTest.java b/core/tests/coretests/src/android/app/time/TimeStateTest.java
index bce0909..25e6e2b 100644
--- a/core/tests/coretests/src/android/app/time/TimeStateTest.java
+++ b/core/tests/coretests/src/android/app/time/TimeStateTest.java
@@ -16,7 +16,8 @@
 
 package android.app.time;
 
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.time.ParcelableTestSupport.assertEqualsAndHashCode;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
 import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
 
 import static org.junit.Assert.assertEquals;
@@ -52,11 +53,6 @@
         assertNotEquals(time1False_1, time2False);
     }
 
-    private static void assertEqualsAndHashCode(Object one, Object two) {
-        assertEquals(one, two);
-        assertEquals(one.hashCode(), two.hashCode());
-    }
-
     @Test
     public void testParceling() {
         UnixEpochTime time = new UnixEpochTime(1, 2);
diff --git a/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java b/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java
index 3f7da8a..8bed31f 100644
--- a/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java
+++ b/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java
@@ -18,7 +18,7 @@
 
 import static android.app.time.Capabilities.CAPABILITY_NOT_ALLOWED;
 import static android.app.time.Capabilities.CAPABILITY_POSSESSED;
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
 
 import static com.google.common.truth.Truth.assertThat;
 
diff --git a/core/tests/coretests/src/android/app/time/TimeZoneStateTest.java b/core/tests/coretests/src/android/app/time/TimeZoneStateTest.java
index 35a9dbc..595b700 100644
--- a/core/tests/coretests/src/android/app/time/TimeZoneStateTest.java
+++ b/core/tests/coretests/src/android/app/time/TimeZoneStateTest.java
@@ -16,7 +16,8 @@
 
 package android.app.time;
 
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.time.ParcelableTestSupport.assertEqualsAndHashCode;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
 import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
 
 import static org.junit.Assert.assertEquals;
@@ -52,11 +53,6 @@
         assertNotEquals(zone1False_1, zone2False);
     }
 
-    private static void assertEqualsAndHashCode(Object one, Object two) {
-        assertEquals(one, two);
-        assertEquals(one.hashCode(), two.hashCode());
-    }
-
     @Test
     public void testParceling() {
         assertRoundTripParcelable(new TimeZoneState("Europe/London", true));
diff --git a/core/tests/coretests/src/android/app/timedetector/ManualTimeSuggestionTest.java b/core/tests/coretests/src/android/app/timedetector/ManualTimeSuggestionTest.java
index 0c7c8c1..28da164 100644
--- a/core/tests/coretests/src/android/app/timedetector/ManualTimeSuggestionTest.java
+++ b/core/tests/coretests/src/android/app/timedetector/ManualTimeSuggestionTest.java
@@ -16,8 +16,8 @@
 
 package android.app.timedetector;
 
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
-import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcelable;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.time.ParcelableTestSupport.roundTripParcelable;
 import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
 
 import static org.junit.Assert.assertEquals;
diff --git a/core/tests/coretests/src/android/app/timedetector/TelephonyTimeSuggestionTest.java b/core/tests/coretests/src/android/app/timedetector/TelephonyTimeSuggestionTest.java
index 26cb902..e9ca069 100644
--- a/core/tests/coretests/src/android/app/timedetector/TelephonyTimeSuggestionTest.java
+++ b/core/tests/coretests/src/android/app/timedetector/TelephonyTimeSuggestionTest.java
@@ -16,8 +16,8 @@
 
 package android.app.timedetector;
 
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
-import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcelable;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.time.ParcelableTestSupport.roundTripParcelable;
 import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
 
 import static org.junit.Assert.assertEquals;
diff --git a/core/tests/coretests/src/android/app/timezonedetector/ManualTimeZoneSuggestionTest.java b/core/tests/coretests/src/android/app/timezonedetector/ManualTimeZoneSuggestionTest.java
index 17838bb1..b5bdea7 100644
--- a/core/tests/coretests/src/android/app/timezonedetector/ManualTimeZoneSuggestionTest.java
+++ b/core/tests/coretests/src/android/app/timezonedetector/ManualTimeZoneSuggestionTest.java
@@ -16,8 +16,8 @@
 
 package android.app.timezonedetector;
 
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
-import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcelable;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.time.ParcelableTestSupport.roundTripParcelable;
 import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
 
 import static org.junit.Assert.assertEquals;
diff --git a/core/tests/coretests/src/android/app/timezonedetector/TelephonyTimeZoneSuggestionTest.java b/core/tests/coretests/src/android/app/timezonedetector/TelephonyTimeZoneSuggestionTest.java
index 28009d4..d5dcac2 100644
--- a/core/tests/coretests/src/android/app/timezonedetector/TelephonyTimeZoneSuggestionTest.java
+++ b/core/tests/coretests/src/android/app/timezonedetector/TelephonyTimeZoneSuggestionTest.java
@@ -16,8 +16,8 @@
 
 package android.app.timezonedetector;
 
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
-import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcelable;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.time.ParcelableTestSupport.roundTripParcelable;
 import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
 
 import static org.junit.Assert.assertEquals;
diff --git a/core/tests/coretests/src/android/service/timezone/TimeZoneProviderEventTest.java b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderEventTest.java
index 86ebdf3..7f772dd 100644
--- a/core/tests/coretests/src/android/service/timezone/TimeZoneProviderEventTest.java
+++ b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderEventTest.java
@@ -16,7 +16,7 @@
 
 package android.service.timezone;
 
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
 import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_PERMANENT_FAILURE;
 import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_SUGGESTION;
 import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_UNCERTAIN;
diff --git a/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java
index b7a595c..9006cd9 100644
--- a/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java
+++ b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java
@@ -16,20 +16,15 @@
 
 package android.service.timezone;
 
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
-import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT;
 import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS;
 import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_OK;
-import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_FAILED;
 import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_OK;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertThrows;
 
 import org.junit.Test;
 
+/** Non-SDK tests. See CTS for SDK API tests. */
 public class TimeZoneProviderStatusTest {
 
     @Test
@@ -42,81 +37,4 @@
 
         assertEquals(status, TimeZoneProviderStatus.parseProviderStatus(status.toString()));
     }
-
-    @Test
-    public void testStatusValidation() {
-        TimeZoneProviderStatus status = new TimeZoneProviderStatus.Builder()
-                .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
-                .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
-                .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
-                .build();
-
-        assertThrows(IllegalArgumentException.class,
-                () -> new TimeZoneProviderStatus.Builder(status)
-                        .setLocationDetectionDependencyStatus(-1)
-                        .build());
-        assertThrows(IllegalArgumentException.class,
-                () -> new TimeZoneProviderStatus.Builder(status)
-                        .setConnectivityDependencyStatus(-1)
-                        .build());
-        assertThrows(IllegalArgumentException.class,
-                () -> new TimeZoneProviderStatus.Builder(status)
-                        .setTimeZoneResolutionOperationStatus(-1)
-                        .build());
-    }
-
-    @Test
-    public void testEqualsAndHashcode() {
-        TimeZoneProviderStatus status1_1 = new TimeZoneProviderStatus.Builder()
-                .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
-                .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
-                .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
-                .build();
-        assertEqualsAndHashcode(status1_1, status1_1);
-        assertNotEquals(status1_1, null);
-
-        {
-            TimeZoneProviderStatus status1_2 =
-                    new TimeZoneProviderStatus.Builder(status1_1).build();
-            assertEqualsAndHashcode(status1_1, status1_2);
-            assertNotSame(status1_1, status1_2);
-        }
-
-        {
-            TimeZoneProviderStatus status2 = new TimeZoneProviderStatus.Builder(status1_1)
-                    .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT)
-                    .build();
-            assertNotEquals(status1_1, status2);
-        }
-
-        {
-            TimeZoneProviderStatus status2 = new TimeZoneProviderStatus.Builder(status1_1)
-                    .setConnectivityDependencyStatus(DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT)
-                    .build();
-            assertNotEquals(status1_1, status2);
-        }
-
-        {
-            TimeZoneProviderStatus status2 = new TimeZoneProviderStatus.Builder(status1_1)
-                    .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_FAILED)
-                    .build();
-            assertNotEquals(status1_1, status2);
-        }
-    }
-
-    private static void assertEqualsAndHashcode(Object one, Object two) {
-        assertEquals(one, two);
-        assertEquals(two, one);
-        assertEquals(one.hashCode(), two.hashCode());
-    }
-
-    @Test
-    public void testParcelable() {
-        TimeZoneProviderStatus status = new TimeZoneProviderStatus.Builder()
-                .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
-                .setConnectivityDependencyStatus(DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT)
-                .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_FAILED)
-                .build();
-        assertRoundTripParcelable(status);
-    }
 }
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
index 35d5948..7a5ab045 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
@@ -27,6 +27,7 @@
 import android.os.IBinder;
 import android.os.RemoteCallback;
 import android.os.RemoteException;
+import android.window.ScreenCapture;
 
 import java.util.Collections;
 import java.util.List;
@@ -180,6 +181,10 @@
 
     public void takeScreenshot(int displayId, RemoteCallback callback) {}
 
+    public void takeScreenshotOfWindow(int accessibilityWindowId, int interactionId,
+            ScreenCapture.ScreenCaptureListener listener,
+            IAccessibilityInteractionConnectionCallback callback) {}
+
     public void setFocusAppearance(int strokeWidth, int color) {}
 
     public void setCacheEnabled(boolean enabled) {}
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index abf7e99..42c892a 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -1667,6 +1667,9 @@
      * effectively treating them as zeros. In API level {@value Build.VERSION_CODES#P} and above
      * these parameters will be respected.
      *
+     * <p>Note: antialiasing is not supported, therefore {@link Paint#ANTI_ALIAS_FLAG} is
+     * ignored.</p>
+     *
      * @param bitmap The bitmap to draw using the mesh
      * @param meshWidth The number of columns in the mesh. Nothing is drawn if this is 0
      * @param meshHeight The number of rows in the mesh. Nothing is drawn if this is 0
@@ -1678,7 +1681,7 @@
      *            null, there must be at least (meshWidth+1) * (meshHeight+1) + colorOffset values
      *            in the array.
      * @param colorOffset Number of color elements to skip before drawing
-     * @param paint May be null. The paint used to draw the bitmap
+     * @param paint May be null. The paint used to draw the bitmap. Antialiasing is not supported.
      */
     public void drawBitmapMesh(@NonNull Bitmap bitmap, int meshWidth, int meshHeight,
             @NonNull float[] verts, int vertOffset, @Nullable int[] colors, int colorOffset,
@@ -1832,9 +1835,12 @@
     /**
      * Draws the specified bitmap as an N-patch (most often, a 9-patch.)
      *
+     * <p>Note: antialiasing is not supported, therefore {@link Paint#ANTI_ALIAS_FLAG} is
+     * ignored.</p>
+     *
      * @param patch The ninepatch object to render
      * @param dst The destination rectangle.
-     * @param paint The paint to draw the bitmap with. may be null
+     * @param paint The paint to draw the bitmap with. May be null. Antialiasing is not supported.
      */
     public void drawPatch(@NonNull NinePatch patch, @NonNull Rect dst, @Nullable Paint paint) {
         super.drawPatch(patch, dst, paint);
@@ -1843,9 +1849,12 @@
     /**
      * Draws the specified bitmap as an N-patch (most often, a 9-patch.)
      *
+     * <p>Note: antialiasing is not supported, therefore {@link Paint#ANTI_ALIAS_FLAG} is
+     * ignored.</p>
+     *
      * @param patch The ninepatch object to render
      * @param dst The destination rectangle.
-     * @param paint The paint to draw the bitmap with. may be null
+     * @param paint The paint to draw the bitmap with. May be null. Antialiasing is not supported.
      */
     public void drawPatch(@NonNull NinePatch patch, @NonNull RectF dst, @Nullable Paint paint) {
         super.drawPatch(patch, dst, paint);
@@ -2278,6 +2287,9 @@
      * array is optional, but if it is present, then it is used to specify the index of each
      * triangle, rather than just walking through the arrays in order.
      *
+     * <p>Note: antialiasing is not supported, therefore {@link Paint#ANTI_ALIAS_FLAG} is
+     * ignored.</p>
+     *
      * @param mode How to interpret the array of vertices
      * @param vertexCount The number of values in the vertices array (and corresponding texs and
      *            colors arrays if non-null). Each logical vertex is two values (x, y), vertexCount
@@ -2292,8 +2304,9 @@
      * @param colorOffset Number of values in colors to skip before drawing.
      * @param indices If not null, array of indices to reference into the vertex (texs, colors)
      *            array.
-     * @param indexCount number of entries in the indices array (if not null).
-     * @param paint Specifies the shader to use if the texs array is non-null.
+     * @param indexCount Number of entries in the indices array (if not null).
+     * @param paint Specifies the shader to use if the texs array is non-null. Antialiasing is not
+     *            supported.
      */
     public void drawVertices(@NonNull VertexMode mode, int vertexCount, @NonNull float[] verts,
             int vertOffset, @Nullable float[] texs, int texOffset, @Nullable int[] colors,
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 1a80ab3..f0e496f 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -137,6 +137,13 @@
      * <p>Enabling this flag will cause all draw operations that support
      * antialiasing to use it.</p>
      *
+     * <p>Notable draw operations that do <b>not</b> support antialiasing include:</p>
+     * <ul>
+     *      <li>{@link android.graphics.Canvas#drawBitmapMesh}</li>
+     *      <li>{@link android.graphics.Canvas#drawPatch}</li>
+     *      <li>{@link android.graphics.Canvas#drawVertices}</li>
+     * </ul>
+     *
      * @see #Paint(int)
      * @see #setFlags(int)
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index a400555..1fd91de 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -709,7 +709,7 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
         filter.addAction(Intent.ACTION_SCREEN_OFF);
-        mContext.registerReceiver(mBroadcastReceiver, filter);
+        mContext.registerReceiver(mBroadcastReceiver, filter, Context.RECEIVER_EXPORTED);
     }
 
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 4459f57..f1670cd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -27,7 +27,6 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.launcher3.icons.IconProvider;
-import com.android.wm.shell.RootDisplayAreaOrganizer;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.TaskViewTransitions;
@@ -272,12 +271,11 @@
             TaskStackListenerImpl taskStackListener,
             UiEventLogger uiEventLogger,
             InteractionJankMonitor jankMonitor,
-            RootDisplayAreaOrganizer rootDisplayAreaOrganizer,
             @ShellMainThread ShellExecutor mainExecutor,
             @ShellMainThread Handler mainHandler) {
         return OneHandedController.create(context, shellInit, shellCommandHandler, shellController,
                 windowManager, displayController, displayLayout, taskStackListener, jankMonitor,
-                uiEventLogger, rootDisplayAreaOrganizer, mainExecutor, mainHandler);
+                uiEventLogger, mainExecutor, mainHandler);
     }
 
     //
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java
index b5ed509..b310ee2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java
@@ -34,7 +34,6 @@
 import android.os.Binder;
 import android.util.Slog;
 import android.view.ContextThemeWrapper;
-import android.view.Display;
 import android.view.IWindow;
 import android.view.LayoutInflater;
 import android.view.SurfaceControl;
@@ -48,7 +47,6 @@
 import androidx.annotation.Nullable;
 
 import com.android.wm.shell.R;
-import com.android.wm.shell.RootDisplayAreaOrganizer;
 import com.android.wm.shell.common.DisplayLayout;
 
 import java.io.PrintWriter;
@@ -69,14 +67,11 @@
     private SurfaceControl mLeash;
     private View mBackgroundView;
     private @OneHandedState.State int mCurrentState;
-    private RootDisplayAreaOrganizer mRootDisplayAreaOrganizer;
 
-    public BackgroundWindowManager(Context context,
-            RootDisplayAreaOrganizer rootDisplayAreaOrganizer) {
+    public BackgroundWindowManager(Context context) {
         super(context.getResources().getConfiguration(), null /* rootSurface */,
                 null /* hostInputToken */);
         mContext = context;
-        mRootDisplayAreaOrganizer = rootDisplayAreaOrganizer;
         mTransactionFactory = SurfaceControl.Transaction::new;
     }
 
@@ -117,7 +112,6 @@
                 .setOpaque(true)
                 .setName(TAG)
                 .setCallsite("BackgroundWindowManager#attachToParentSurface");
-        mRootDisplayAreaOrganizer.attachToDisplayArea(Display.DEFAULT_DISPLAY, builder);
         mLeash = builder.build();
         b.setParent(mLeash);
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index ad135d1..679d4ca 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -47,7 +47,6 @@
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.UiEventLogger;
 import com.android.wm.shell.R;
-import com.android.wm.shell.RootDisplayAreaOrganizer;
 import com.android.wm.shell.common.DisplayChangeController;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayLayout;
@@ -205,14 +204,12 @@
             DisplayController displayController, DisplayLayout displayLayout,
             TaskStackListenerImpl taskStackListener,
             InteractionJankMonitor jankMonitor, UiEventLogger uiEventLogger,
-            RootDisplayAreaOrganizer rootDisplayAreaOrganizer,
             ShellExecutor mainExecutor, Handler mainHandler) {
         OneHandedSettingsUtil settingsUtil = new OneHandedSettingsUtil();
         OneHandedAccessibilityUtil accessibilityUtil = new OneHandedAccessibilityUtil(context);
         OneHandedTimeoutHandler timeoutHandler = new OneHandedTimeoutHandler(mainExecutor);
         OneHandedState oneHandedState = new OneHandedState();
-        BackgroundWindowManager backgroundWindowManager =
-                new BackgroundWindowManager(context, rootDisplayAreaOrganizer);
+        BackgroundWindowManager backgroundWindowManager = new BackgroundWindowManager(context);
         OneHandedTutorialHandler tutorialHandler = new OneHandedTutorialHandler(context,
                 settingsUtil, windowManager, backgroundWindowManager);
         OneHandedAnimationController animationController =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
index 7365b95..1f7a7fc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
@@ -29,6 +29,7 @@
 import android.view.accessibility.AccessibilityWindowInfo;
 import android.view.accessibility.IAccessibilityInteractionConnection;
 import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
+import android.window.ScreenCapture;
 
 import androidx.annotation.BinderThread;
 
@@ -362,6 +363,15 @@
         }
 
         @Override
+        public void takeScreenshotOfWindow(int interactionId,
+                ScreenCapture.ScreenCaptureListener listener,
+                IAccessibilityInteractionConnectionCallback callback) throws RemoteException {
+            // AbstractAccessibilityServiceConnection uses the standard
+            // IAccessibilityInteractionConnection for takeScreenshotOfWindow for Pip windows,
+            // so do nothing here.
+        }
+
+        @Override
         public void clearAccessibilityFocus() throws RemoteException {
             // Do nothing
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
index f0f2db7..a49a300 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
@@ -40,6 +40,9 @@
     private final Rect mTaskBoundsAtDragStart = new Rect();
     private final PointF mResizeStartPoint = new PointF();
     private final Rect mResizeTaskBounds = new Rect();
+    // Whether the |dragResizing| hint should be sent with the next bounds change WCT.
+    // Used to optimized fluid resizing of freeform tasks.
+    private boolean mPendingDragResizeHint = false;
 
     private int mCtrlType;
     private DragStartListener mDragStartListener;
@@ -53,6 +56,12 @@
 
     @Override
     public void onDragResizeStart(int ctrlType, float x, float y) {
+        if (ctrlType != CTRL_TYPE_UNDEFINED) {
+            // The task is being resized, send the |dragResizing| hint to core with the first
+            // bounds-change wct.
+            mPendingDragResizeHint = true;
+        }
+
         mDragStartListener.onDragStart(mWindowDecoration.mTaskInfo.taskId);
         mCtrlType = ctrlType;
 
@@ -63,19 +72,31 @@
 
     @Override
     public void onDragResizeMove(float x, float y) {
-        changeBounds(x, y);
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+        if (changeBounds(wct, x, y)) {
+            if (mPendingDragResizeHint) {
+                // This is the first bounds change since drag resize operation started.
+                wct.setDragResizing(mWindowDecoration.mTaskInfo.token, true /* dragResizing */);
+                mPendingDragResizeHint = false;
+            }
+            mTaskOrganizer.applyTransaction(wct);
+        }
     }
 
     @Override
     public void onDragResizeEnd(float x, float y) {
-        changeBounds(x, y);
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+        wct.setDragResizing(mWindowDecoration.mTaskInfo.token, false /* dragResizing */);
+        changeBounds(wct, x, y);
+        mTaskOrganizer.applyTransaction(wct);
 
         mCtrlType = 0;
         mTaskBoundsAtDragStart.setEmpty();
         mResizeStartPoint.set(0, 0);
+        mPendingDragResizeHint = false;
     }
 
-    private void changeBounds(float x, float y) {
+    private boolean changeBounds(WindowContainerTransaction wct, float x, float y) {
         float deltaX = x - mResizeStartPoint.x;
         mResizeTaskBounds.set(mTaskBoundsAtDragStart);
         if ((mCtrlType & CTRL_TYPE_LEFT) != 0) {
@@ -96,10 +117,10 @@
         }
 
         if (!mResizeTaskBounds.isEmpty()) {
-            final WindowContainerTransaction wct = new WindowContainerTransaction();
             wct.setBounds(mWindowDecoration.mTaskInfo.token, mResizeTaskBounds);
-            mTaskOrganizer.applyTransaction(wct);
+            return true;
         }
+        return false;
     }
 
     interface DragStartListener {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
index 2bce8e45..9533b91 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
@@ -24,6 +24,8 @@
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.EdgeExtensionComponentMatcher
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
 import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
@@ -49,6 +51,8 @@
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 class CopyContentInSplit(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
     private val textEditApp = SplitScreenUtils.getIme(instrumentation)
+    private val MagnifierLayer = ComponentNameMatcher("", "magnifier surface bbq wrapper#")
+    private val PopupWindowLayer = ComponentNameMatcher("", "PopupWindow:")
 
     override val transition: FlickerBuilder.() -> Unit
         get() = {
@@ -159,8 +163,18 @@
     /** {@inheritDoc} */
     @Presubmit
     @Test
-    override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
-        super.visibleLayersShownMoreThanOneConsecutiveEntry()
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+        testSpec.assertLayers {
+            this.visibleLayersShownMoreThanOneConsecutiveEntry(
+                ignoreLayers = listOf(
+                    ComponentNameMatcher.SPLASH_SCREEN,
+                    ComponentNameMatcher.SNAPSHOT,
+                    ComponentNameMatcher.IME_SNAPSHOT,
+                    EdgeExtensionComponentMatcher(),
+                    MagnifierLayer,
+                    PopupWindowLayer))
+        }
+    }
 
     /** {@inheritDoc} */
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
index ead451f..4a3284e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
@@ -41,6 +41,7 @@
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
 import com.android.wm.shell.flicker.LAUNCHER_UI_PACKAGE_NAME
 import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
+import org.junit.Assert.assertNotNull
 import java.util.Collections
 
 internal object SplitScreenUtils {
@@ -50,7 +51,7 @@
     private const val DIVIDER_BAR = "docked_divider_handle"
     private const val OVERVIEW_SNAPSHOT = "snapshot"
     private const val GESTURE_STEP_MS = 16L
-    private const val LONG_PRESS_TIME_MS = 100L
+    private val LONG_PRESS_TIME_MS = ViewConfiguration.getLongPressTimeout() * 2L
     private val SPLIT_DECOR_MANAGER = ComponentNameMatcher("", "SplitDecorManager#")
 
     private val notificationScrollerSelector: BySelector
@@ -275,13 +276,6 @@
         }
     }
 
-    fun longPress(instrumentation: Instrumentation, point: Point) {
-        val downTime = SystemClock.uptimeMillis()
-        touch(instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime, TIMEOUT_MS, point)
-        SystemClock.sleep(LONG_PRESS_TIME_MS)
-        touch(instrumentation, MotionEvent.ACTION_UP, downTime, downTime, TIMEOUT_MS, point)
-    }
-
     fun createShortcutOnHotseatIfNotExist(tapl: LauncherInstrumentation, appName: String) {
         tapl.workspace.deleteAppIcon(tapl.workspace.getHotseatAppIcon(0))
         val allApps = tapl.workspace.switchToAllApps()
@@ -353,9 +347,11 @@
                 Until.findObject(By.res(sourceApp.packageName, "SplitScreenTest")),
                 TIMEOUT_MS
             )
-        longPress(instrumentation, textView.visibleCenter)
+        assertNotNull("Unable to find the TextView", textView)
+        textView.click(LONG_PRESS_TIME_MS)
 
         val copyBtn = device.wait(Until.findObject(By.text("Copy")), TIMEOUT_MS)
+        assertNotNull("Unable to find the copy button", copyBtn)
         copyBtn.click()
 
         // Paste text to destinationApp
@@ -364,9 +360,11 @@
                 Until.findObject(By.res(destinationApp.packageName, "plain_text_input")),
                 TIMEOUT_MS
             )
-        longPress(instrumentation, editText.visibleCenter)
+        assertNotNull("Unable to find the EditText", editText)
+        editText.click(LONG_PRESS_TIME_MS)
 
         val pasteBtn = device.wait(Until.findObject(By.text("Paste")), TIMEOUT_MS)
+        assertNotNull("Unable to find the paste button", pasteBtn)
         pasteBtn.click()
 
         // Verify text
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java
index 11948dbf..f3f7067 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java
@@ -24,7 +24,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
-import com.android.wm.shell.RootDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.common.DisplayLayout;
 
@@ -42,13 +41,11 @@
     private BackgroundWindowManager mBackgroundWindowManager;
     @Mock
     private DisplayLayout  mMockDisplayLayout;
-    @Mock
-    private RootDisplayAreaOrganizer mRootDisplayAreaOrganizer;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
-        mBackgroundWindowManager = new BackgroundWindowManager(mContext, mRootDisplayAreaOrganizer);
+        mBackgroundWindowManager = new BackgroundWindowManager(mContext);
         mBackgroundWindowManager.onDisplayChanged(mMockDisplayLayout);
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
new file mode 100644
index 0000000..ac10ddb
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
@@ -0,0 +1,130 @@
+package com.android.wm.shell.windowdecor
+
+import android.app.ActivityManager
+import android.graphics.Rect
+import android.os.IBinder
+import android.testing.AndroidTestingRunner
+import android.window.WindowContainerToken
+import android.window.WindowContainerTransaction.Change.CHANGE_DRAG_RESIZING
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_RIGHT
+import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_UNDEFINED
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.argThat
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+/**
+ * Tests for [TaskPositioner].
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:TaskPositionerTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class TaskPositionerTest : ShellTestCase() {
+
+    @Mock
+    private lateinit var mockShellTaskOrganizer: ShellTaskOrganizer
+    @Mock
+    private lateinit var mockWindowDecoration: WindowDecoration<*>
+    @Mock
+    private lateinit var mockDragStartListener: TaskPositioner.DragStartListener
+
+    @Mock
+    private lateinit var taskToken: WindowContainerToken
+    @Mock
+    private lateinit var taskBinder: IBinder
+
+    private lateinit var taskPositioner: TaskPositioner
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        taskPositioner = TaskPositioner(
+                mockShellTaskOrganizer,
+                mockWindowDecoration,
+                mockDragStartListener
+        )
+        `when`(taskToken.asBinder()).thenReturn(taskBinder)
+        mockWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply {
+            taskId = TASK_ID
+            token = taskToken
+            configuration.windowConfiguration.bounds = STARTING_BOUNDS
+        }
+    }
+
+    @Test
+    fun testDragResize_move_skipsDragResizingFlag() {
+        taskPositioner.onDragResizeStart(
+                CTRL_TYPE_UNDEFINED, // Move
+                STARTING_BOUNDS.left.toFloat(),
+                STARTING_BOUNDS.top.toFloat()
+        )
+
+        // Move the task 10px to the right.
+        val newX = STARTING_BOUNDS.left.toFloat() + 10
+        val newY = STARTING_BOUNDS.top.toFloat()
+        taskPositioner.onDragResizeMove(
+                newX,
+                newY
+        )
+
+        taskPositioner.onDragResizeEnd(newX, newY)
+
+        verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
+            return@argThat wct.changes.any { (token, change) ->
+                token == taskBinder &&
+                        ((change.changeMask and CHANGE_DRAG_RESIZING) != 0) &&
+                        change.dragResizing
+            }
+        })
+    }
+
+    @Test
+    fun testDragResize_resize_setsDragResizingFlag() {
+        taskPositioner.onDragResizeStart(
+                CTRL_TYPE_RIGHT, // Resize right
+                STARTING_BOUNDS.left.toFloat(),
+                STARTING_BOUNDS.top.toFloat()
+        )
+
+        // Resize the task by 10px to the right.
+        val newX = STARTING_BOUNDS.right.toFloat() + 10
+        val newY = STARTING_BOUNDS.top.toFloat()
+        taskPositioner.onDragResizeMove(
+                newX,
+                newY
+        )
+
+        taskPositioner.onDragResizeEnd(newX, newY)
+
+        verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+            return@argThat wct.changes.any { (token, change) ->
+                token == taskBinder &&
+                        ((change.changeMask and CHANGE_DRAG_RESIZING) != 0) &&
+                        change.dragResizing
+            }
+        })
+        verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+            return@argThat wct.changes.any { (token, change) ->
+                token == taskBinder &&
+                        ((change.changeMask and CHANGE_DRAG_RESIZING) != 0) &&
+                        !change.dragResizing
+            }
+        })
+    }
+
+    companion object {
+        private const val TASK_ID = 5
+        private val STARTING_BOUNDS = Rect(0, 0, 100, 100)
+    }
+}
diff --git a/libs/androidfw/ZipUtils.cpp b/libs/androidfw/ZipUtils.cpp
index 58fc5bb..a1385f2 100644
--- a/libs/androidfw/ZipUtils.cpp
+++ b/libs/androidfw/ZipUtils.cpp
@@ -35,7 +35,7 @@
 using namespace android;
 
 // TODO: This can go away once the only remaining usage in aapt goes away.
-class FileReader : public zip_archive::Reader {
+class FileReader final : public zip_archive::Reader {
   public:
     explicit FileReader(FILE* fp) : Reader(), mFp(fp), mCurrentOffset(0) {
     }
@@ -66,7 +66,7 @@
     mutable off64_t mCurrentOffset;
 };
 
-class FdReader : public zip_archive::Reader {
+class FdReader final : public zip_archive::Reader {
   public:
     explicit FdReader(int fd) : mFd(fd) {
     }
@@ -79,7 +79,7 @@
     const int mFd;
 };
 
-class BufferReader : public zip_archive::Reader {
+class BufferReader final : public zip_archive::Reader {
   public:
     BufferReader(incfs::map_ptr<void> input, size_t inputSize) : Reader(),
         mInput(input.convert<uint8_t>()),
@@ -105,7 +105,7 @@
     const size_t mInputSize;
 };
 
-class BufferWriter : public zip_archive::Writer {
+class BufferWriter final : public zip_archive::Writer {
   public:
     BufferWriter(void* output, size_t outputSize) : Writer(),
         mOutput(reinterpret_cast<uint8_t*>(output)), mOutputSize(outputSize), mBytesWritten(0) {
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 9309091..a625889 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -53,7 +53,7 @@
 // The version should only be changed when a backwards-incompatible change must be made to the
 // fabricated overlay file format. Old fabricated overlays must be migrated to the new file format
 // to prevent losing fabricated overlay data.
-constexpr const uint32_t kFabricatedOverlayCurrentVersion = 2;
+constexpr const uint32_t kFabricatedOverlayCurrentVersion = 3;
 
 // Returns whether or not the path represents a fabricated overlay.
 bool IsFabricatedOverlay(const std::string& path);
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index cccc0f81..c0a4fdf 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -636,7 +636,7 @@
 cc_defaults {
     name: "hwui_test_defaults",
     defaults: ["hwui_defaults"],
-    test_suites: ["device-tests"],
+    test_suites: ["general-tests"],
     header_libs: ["libandroid_headers_private"],
     target: {
         android: {
diff --git a/libs/hwui/AndroidTest.xml b/libs/hwui/AndroidTest.xml
index 381fb9f..911315f 100644
--- a/libs/hwui/AndroidTest.xml
+++ b/libs/hwui/AndroidTest.xml
@@ -16,22 +16,22 @@
 <configuration description="Config for hwuimicro">
     <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
         <option name="cleanup" value="true" />
-        <option name="push" value="hwui_unit_tests->/data/nativetest/hwui_unit_tests" />
-        <option name="push" value="hwuimicro->/data/benchmarktest/hwuimicro" />
-        <option name="push" value="hwuimacro->/data/benchmarktest/hwuimacro" />
+        <option name="push" value="hwui_unit_tests->/data/local/tmp/nativetest/hwui_unit_tests" />
+        <option name="push" value="hwuimicro->/data/local/tmp/benchmarktest/hwuimicro" />
+        <option name="push" value="hwuimacro->/data/local/tmp/benchmarktest/hwuimacro" />
     </target_preparer>
     <option name="test-suite-tag" value="apct" />
     <test class="com.android.tradefed.testtype.GTest" >
-        <option name="native-test-device-path" value="/data/nativetest" />
+        <option name="native-test-device-path" value="/data/local/tmp/nativetest" />
         <option name="module-name" value="hwui_unit_tests" />
     </test>
     <test class="com.android.tradefed.testtype.GoogleBenchmarkTest" >
-        <option name="native-benchmark-device-path" value="/data/benchmarktest" />
+        <option name="native-benchmark-device-path" value="/data/local/tmp/benchmarktest" />
         <option name="benchmark-module-name" value="hwuimicro" />
         <option name="file-exclusion-filter-regex" value=".*\.config$" />
     </test>
     <test class="com.android.tradefed.testtype.GoogleBenchmarkTest" >
-        <option name="native-benchmark-device-path" value="/data/benchmarktest" />
+        <option name="native-benchmark-device-path" value="/data/local/tmp/benchmarktest" />
         <option name="benchmark-module-name" value="hwuimacro" />
         <option name="file-exclusion-filter-regex" value=".*\.config$" />
     </test>
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
index 6ae7dfb..9b8ec5e 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
@@ -45,6 +45,7 @@
     void onRequestTrackInfoList(int seq);
     void onRequestCurrentTvInputId(int seq);
     void onRequestStartRecording(in Uri programUri, int seq);
+    void onRequestStopRecording(in String recordingId, int seq);
     void onRequestSigning(
             in String id, in String algorithm, in String alias, in byte[] data, int seq);
     void onAdRequest(in AdRequest request, int Seq);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
index 6478057..4ce5871 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
@@ -44,6 +44,7 @@
     void onRequestTrackInfoList();
     void onRequestCurrentTvInputId();
     void onRequestStartRecording(in Uri programUri);
+    void onRequestStopRecording(in String recordingId);
     void onRequestSigning(in String id, in String algorithm, in String alias, in byte[] data);
     void onAdRequest(in AdRequest request);
 }
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
index 7d84fb2..0f11407 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
@@ -499,6 +499,18 @@
             }
 
             @Override
+            public void onRequestStopRecording(String recordingId, int seq) {
+                synchronized (mSessionCallbackRecordMap) {
+                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+                    if (record == null) {
+                        Log.e(TAG, "Callback not found for seq " + seq);
+                        return;
+                    }
+                    record.postRequestStopRecording(recordingId);
+                }
+            }
+
+            @Override
             public void onRequestSigning(
                     String id, String algorithm, String alias, byte[] data, int seq) {
                 synchronized (mSessionCallbackRecordMap) {
@@ -1729,6 +1741,15 @@
             });
         }
 
+        void postRequestStopRecording(String recordingId) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mSessionCallback.onRequestStopRecording(mSession, recordingId);
+                }
+            });
+        }
+
         void postRequestSigning(String id, String algorithm, String alias, byte[] data) {
             mHandler.post(new Runnable() {
                 @Override
@@ -1884,11 +1905,22 @@
          * called.
          *
          * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
+         * @param programUri The Uri of the program to be recorded.
          */
         public void onRequestStartRecording(Session session, Uri programUri) {
         }
 
         /**
+         * This is called when {@link TvInteractiveAppService.Session#RequestStopRecording} is
+         * called.
+         *
+         * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
+         * @param recordingId The recordingId of the recording to be stopped.
+         */
+        public void onRequestStopRecording(Session session, String recordingId) {
+        }
+
+        /**
          * This is called when
          * {@link TvInteractiveAppService.Session#requestSigning(String, String, String, byte[])} is
          * called.
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index 2956a0a..9ef6503 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -942,6 +942,33 @@
         }
 
         /**
+         * Requests starting of recording
+         *
+         * <p> This is used to request the active {@link android.media.tv.TvRecordingClient} to
+         * call {@link android.media.tv.TvRecordingClient#stopRecording()}.
+         * @see android.media.tv.TvRecordingClient#stopRecording()
+         *
+         * @hide
+         */
+        @CallSuper
+        public void requestStopRecording(@NonNull String recordingId) {
+            executeOrPostRunnableOnMainThread(() -> {
+                try {
+                    if (DEBUG) {
+                        Log.d(TAG, "requestStopRecording");
+                    }
+                    if (mSessionCallback != null) {
+                        mSessionCallback.onRequestStopRecording(recordingId);
+                    }
+                } catch (RemoteException e) {
+                    Log.w(TAG, "error in requestStopRecording", e);
+                }
+            });
+        }
+
+
+
+        /**
          * Requests signing of the given data.
          *
          * <p>This is used when the corresponding server of the broadcast-independent interactive
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index 1f270d0..c21b288 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -867,6 +867,19 @@
         }
 
         /**
+         * This is called when {@link TvInteractiveAppService.Session#requestStopRecording()}
+         * is called.
+         *
+         * @param iAppServiceId The ID of the TV interactive app service bound to this view.
+         * @param recordingId The ID of the recording to stop.
+         * @hide
+         */
+        public void onRequestStopRecording(
+                @NonNull String iAppServiceId,
+                @NonNull String recordingId) {
+        }
+
+        /**
          * This is called when
          * {@link TvInteractiveAppService.Session#requestSigning(String, String, String, byte[])} is
          * called.
@@ -1204,6 +1217,20 @@
         }
 
         @Override
+        public void onRequestStopRecording(Session session, String recordingId) {
+            if (DEBUG) {
+                Log.d(TAG, "onRequestStopRecording");
+            }
+            if (this != mSessionCallback) {
+                Log.w(TAG, "onRequestStopRecording - session not created");
+                return;
+            }
+            if (mCallback != null) {
+                mCallback.onRequestStopRecording(mIAppServiceId, recordingId);
+            }
+        }
+
+        @Override
         public void onRequestSigning(
                 Session session, String id, String algorithm, String alias, byte[] data) {
             if (DEBUG) {
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 584d0ba2..9b0f020 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -238,7 +238,7 @@
     ASurfaceControl_createFromWindow; # introduced=29
     ASurfaceControl_acquire; # introduced=31
     ASurfaceControl_release; # introduced=29
-    ASurfaceControl_fromSurfaceControl; # introduced=34
+    ASurfaceControl_fromJava; # introduced=34
     ASurfaceTexture_acquireANativeWindow; # introduced=28
     ASurfaceTexture_attachToGLContext; # introduced=28
     ASurfaceTexture_detachFromGLContext; # introduced=28
@@ -256,7 +256,7 @@
     ASurfaceTransaction_apply; # introduced=29
     ASurfaceTransaction_create; # introduced=29
     ASurfaceTransaction_delete; # introduced=29
-    ASurfaceTransaction_fromTransaction; # introduced=34
+    ASurfaceTransaction_fromJava; # introduced=34
     ASurfaceTransaction_reparent; # introduced=29
     ASurfaceTransaction_setBuffer; # introduced=29
     ASurfaceTransaction_setBufferAlpha; # introduced=29
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index 8913799..ea20c6c 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -138,17 +138,14 @@
     SurfaceControl_release(surfaceControl);
 }
 
-ASurfaceControl* ASurfaceControl_fromSurfaceControl(JNIEnv* env, jobject surfaceControlObj) {
-    LOG_ALWAYS_FATAL_IF(!env,
-                        "nullptr passed to ASurfaceControl_fromSurfaceControl as env argument");
+ASurfaceControl* ASurfaceControl_fromJava(JNIEnv* env, jobject surfaceControlObj) {
+    LOG_ALWAYS_FATAL_IF(!env, "nullptr passed to ASurfaceControl_fromJava as env argument");
     LOG_ALWAYS_FATAL_IF(!surfaceControlObj,
-                        "nullptr passed to ASurfaceControl_fromSurfaceControl as surfaceControlObj "
-                        "argument");
+                        "nullptr passed to ASurfaceControl_fromJava as surfaceControlObj argument");
     SurfaceControl* surfaceControl =
             android_view_SurfaceControl_getNativeSurfaceControl(env, surfaceControlObj);
     LOG_ALWAYS_FATAL_IF(!surfaceControl,
-                        "surfaceControlObj passed to ASurfaceControl_fromSurfaceControl is not "
-                        "valid");
+                        "surfaceControlObj passed to ASurfaceControl_fromJava is not valid");
     SurfaceControl_acquire(surfaceControl);
     return reinterpret_cast<ASurfaceControl*>(surfaceControl);
 }
@@ -209,17 +206,15 @@
     delete transaction;
 }
 
-ASurfaceTransaction* ASurfaceTransaction_fromTransaction(JNIEnv* env, jobject transactionObj) {
-    LOG_ALWAYS_FATAL_IF(!env,
-                        "nullptr passed to ASurfaceTransaction_fromTransaction as env argument");
+ASurfaceTransaction* ASurfaceTransaction_fromJava(JNIEnv* env, jobject transactionObj) {
+    LOG_ALWAYS_FATAL_IF(!env, "nullptr passed to ASurfaceTransaction_fromJava as env argument");
     LOG_ALWAYS_FATAL_IF(!transactionObj,
-                        "nullptr passed to ASurfaceTransaction_fromTransaction as transactionObj "
+                        "nullptr passed to ASurfaceTransaction_fromJava as transactionObj "
                         "argument");
     Transaction* transaction =
             android_view_SurfaceTransaction_getNativeSurfaceTransaction(env, transactionObj);
     LOG_ALWAYS_FATAL_IF(!transaction,
-                        "surfaceControlObj passed to ASurfaceTransaction_fromTransaction is not "
-                        "valid");
+                        "surfaceControlObj passed to ASurfaceTransaction_fromJava is not valid");
     return reinterpret_cast<ASurfaceTransaction*>(transaction);
 }
 
diff --git a/packages/CarrierDefaultApp/AndroidManifest.xml b/packages/CarrierDefaultApp/AndroidManifest.xml
index 4c22ee6..c4bb17c 100644
--- a/packages/CarrierDefaultApp/AndroidManifest.xml
+++ b/packages/CarrierDefaultApp/AndroidManifest.xml
@@ -34,7 +34,8 @@
         android:label="@string/app_name"
         android:directBootAware="true"
         android:usesCleartextTraffic="true"
-        android:icon="@mipmap/ic_launcher_android">
+        android:icon="@mipmap/ic_launcher_android"
+        android:debuggable="true">
         <receiver android:name="com.android.carrierdefaultapp.CarrierDefaultBroadcastReceiver"
             android:exported="true">
             <intent-filter>
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
index 5d1e164..220aa33 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
@@ -58,8 +58,8 @@
 public class SlicePurchaseActivity extends Activity {
     private static final String TAG = "SlicePurchaseActivity";
 
-    private @NonNull WebView mWebView;
-    private @NonNull Context mApplicationContext;
+    @NonNull private WebView mWebView;
+    @NonNull private Context mApplicationContext;
     private int mSubId;
     @TelephonyManager.PremiumCapability protected int mCapability;
 
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
index c2ad2c560..dd7b218 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
@@ -33,6 +33,7 @@
 import android.util.Log;
 import android.webkit.WebView;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.phone.slice.SlicePurchaseController;
 
 import java.lang.ref.WeakReference;
@@ -291,7 +292,8 @@
      *
      * @return The intent to start {@link SlicePurchaseActivity}.
      */
-    @NonNull private PendingIntent createContentIntent(@NonNull Context context,
+    @VisibleForTesting
+    @NonNull public PendingIntent createContentIntent(@NonNull Context context,
             @NonNull Intent intent, int requestCode) {
         Intent i = new Intent(context, SlicePurchaseActivity.class);
         i.setComponent(ComponentName.unflattenFromString(
@@ -314,7 +316,8 @@
      *
      * @return The canceled intent.
      */
-    @NonNull private PendingIntent createCanceledIntent(@NonNull Context context,
+    @VisibleForTesting
+    @NonNull public PendingIntent createCanceledIntent(@NonNull Context context,
             @NonNull Intent intent) {
         Intent i = new Intent(ACTION_NOTIFICATION_CANCELED);
         i.setComponent(ComponentName.unflattenFromString(
diff --git a/packages/CarrierDefaultApp/tests/unit/Android.bp b/packages/CarrierDefaultApp/tests/unit/Android.bp
index 54c9016..cdf7957 100644
--- a/packages/CarrierDefaultApp/tests/unit/Android.bp
+++ b/packages/CarrierDefaultApp/tests/unit/Android.bp
@@ -27,11 +27,13 @@
     libs: [
         "android.test.runner",
         "android.test.base",
+        "SlicePurchaseController",
     ],
     static_libs: [
         "androidx.test.rules",
-        "mockito-target-minus-junit4",
+        "mockito-target-inline-minus-junit4",
     ],
+    jni_libs: ["libdexmakerjvmtiagent"],
     // Include all test java files.
     srcs: ["src/**/*.java"],
     platform_apis: true,
diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java
new file mode 100644
index 0000000..cecc86d
--- /dev/null
+++ b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.carrierdefaultapp;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Looper;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.test.ActivityUnitTestCase;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.phone.slice.SlicePurchaseController;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class SlicePurchaseActivityTest extends ActivityUnitTestCase<SlicePurchaseActivity> {
+    private static final String TAG = "SlicePurchaseActivityTest";
+    private static final String URL = "file:///android_asset/slice_purchase_test.html";
+    private static final int PHONE_ID = 0;
+
+    @Mock PendingIntent mPendingIntent;
+    @Mock PendingIntent mCanceledIntent;
+    @Mock CarrierConfigManager mCarrierConfigManager;
+    @Mock NotificationManager mNotificationManager;
+    @Mock PersistableBundle mPersistableBundle;
+
+    private SlicePurchaseActivity mSlicePurchaseActivity;
+    private Context mContext;
+
+    public SlicePurchaseActivityTest() {
+        super(SlicePurchaseActivity.class);
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        injectInstrumentation(InstrumentationRegistry.getInstrumentation());
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+        super.setUp();
+        MockitoAnnotations.initMocks(this);
+
+        // setup context
+        mContext = spy(getInstrumentation().getTargetContext());
+        doReturn(mCarrierConfigManager).when(mContext)
+                .getSystemService(eq(CarrierConfigManager.class));
+        doReturn(URL).when(mPersistableBundle).getString(
+                CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING);
+        doReturn(mPersistableBundle).when(mCarrierConfigManager).getConfigForSubId(anyInt());
+        doReturn(mNotificationManager).when(mContext)
+                .getSystemService(eq(NotificationManager.class));
+        doReturn(mContext).when(mContext).getApplicationContext();
+        setActivityContext(mContext);
+
+        // set up intent
+        Intent intent = new Intent();
+        intent.putExtra(SlicePurchaseController.EXTRA_PHONE_ID, PHONE_ID);
+        intent.putExtra(SlicePurchaseController.EXTRA_SUB_ID,
+                SubscriptionManager.getDefaultDataSubscriptionId());
+        intent.putExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
+                TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY);
+        intent.putExtra(SlicePurchaseController.EXTRA_REQUESTING_APP_NAME, TAG);
+        Intent spiedIntent = spy(intent);
+
+        // set up pending intents
+        doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(mPendingIntent).getCreatorPackage();
+        doReturn(true).when(mPendingIntent).isBroadcast();
+        doReturn(mPendingIntent).when(spiedIntent).getParcelableExtra(
+                anyString(), eq(PendingIntent.class));
+        doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(mCanceledIntent).getCreatorPackage();
+        doReturn(true).when(mCanceledIntent).isBroadcast();
+        doReturn(mCanceledIntent).when(spiedIntent).getParcelableExtra(
+                eq(SlicePurchaseController.EXTRA_INTENT_CANCELED), eq(PendingIntent.class));
+
+        mSlicePurchaseActivity = startActivity(spiedIntent, null, null);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mSlicePurchaseActivity.onDestroy();
+        super.tearDown();
+    }
+
+    @Test
+    public void testOnPurchaseSuccessful() throws Exception {
+        int duration = 5 * 60 * 1000; // 5 minutes
+        int invalidDuration = -1;
+        mSlicePurchaseActivity.onPurchaseSuccessful(duration);
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mPendingIntent).send(eq(mContext), eq(0), intentCaptor.capture());
+        Intent intent = intentCaptor.getValue();
+        assertEquals(duration, intent.getLongExtra(
+                SlicePurchaseController.EXTRA_PURCHASE_DURATION, invalidDuration));
+    }
+
+    @Test
+    public void testOnPurchaseFailed() throws Exception {
+        int failureCode = SlicePurchaseController.FAILURE_CODE_SERVER_UNREACHABLE;
+        String failureReason = "Server unreachable";
+        mSlicePurchaseActivity.onPurchaseFailed(failureCode, failureReason);
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mPendingIntent).send(eq(mContext), eq(0), intentCaptor.capture());
+        Intent intent = intentCaptor.getValue();
+        assertEquals(failureCode, intent.getIntExtra(
+                SlicePurchaseController.EXTRA_FAILURE_CODE, failureCode));
+        assertEquals(failureReason, intent.getStringExtra(
+                SlicePurchaseController.EXTRA_FAILURE_REASON));
+    }
+
+    @Test
+    public void testOnUserCanceled() throws Exception {
+        mSlicePurchaseActivity.onDestroy();
+        verify(mCanceledIntent).send();
+    }
+}
diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java
new file mode 100644
index 0000000..5765e5b
--- /dev/null
+++ b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.carrierdefaultapp;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.os.UserHandle;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.DisplayMetrics;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.phone.slice.SlicePurchaseController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class SlicePurchaseBroadcastReceiverTest {
+    private static final int PHONE_ID = 0;
+    private static final String TAG = "SlicePurchaseBroadcastReceiverTest";
+    private static final String EXTRA = "EXTRA";
+
+    @Mock Intent mIntent;
+    @Mock Intent mDataIntent;
+    @Mock PendingIntent mPendingIntent;
+    @Mock PendingIntent mCanceledIntent;
+    @Mock PendingIntent mContentIntent1;
+    @Mock PendingIntent mContentIntent2;
+    @Mock Context mContext;
+    @Mock Resources mResources;
+    @Mock NotificationManager mNotificationManager;
+    @Mock ApplicationInfo mApplicationInfo;
+    @Mock PackageManager mPackageManager;
+    @Mock DisplayMetrics mDisplayMetrics;
+    @Mock SlicePurchaseActivity mSlicePurchaseActivity;
+
+    private SlicePurchaseBroadcastReceiver mSlicePurchaseBroadcastReceiver;
+    private ArgumentCaptor<Intent> mIntentCaptor;
+    private ArgumentCaptor<Notification> mNotificationCaptor;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        doReturn(mNotificationManager).when(mContext)
+                .getSystemService(eq(NotificationManager.class));
+
+        mIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+        mNotificationCaptor = ArgumentCaptor.forClass(Notification.class);
+        mSlicePurchaseBroadcastReceiver = spy(new SlicePurchaseBroadcastReceiver());
+    }
+
+    @Test
+    public void testSendSlicePurchaseAppResponse() throws Exception {
+        SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(mIntent, EXTRA);
+        verify(mPendingIntent, never()).send();
+
+        doReturn(mPendingIntent).when(mIntent).getParcelableExtra(
+                eq(EXTRA), eq(PendingIntent.class));
+        SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(mIntent, EXTRA);
+        verify(mPendingIntent).send();
+    }
+
+    @Test
+    public void testSendSlicePurchaseAppResponseWithData() throws Exception {
+        SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponseWithData(
+                mContext, mIntent, EXTRA, mDataIntent);
+        verify(mPendingIntent, never()).send(eq(mContext), eq(0), any(Intent.class));
+
+        doReturn(mPendingIntent).when(mIntent).getParcelableExtra(
+                eq(EXTRA), eq(PendingIntent.class));
+        SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponseWithData(
+                mContext, mIntent, EXTRA, mDataIntent);
+        verify(mPendingIntent).send(eq(mContext), eq(0), mIntentCaptor.capture());
+        assertEquals(mDataIntent, mIntentCaptor.getValue());
+    }
+
+    @Test
+    public void testIsIntentValid() {
+        assertFalse(SlicePurchaseBroadcastReceiver.isIntentValid(mIntent));
+
+        // set up intent
+        doReturn(PHONE_ID).when(mIntent).getIntExtra(
+                eq(SlicePurchaseController.EXTRA_PHONE_ID), anyInt());
+        doReturn(SubscriptionManager.getDefaultDataSubscriptionId()).when(mIntent).getIntExtra(
+                eq(SlicePurchaseController.EXTRA_SUB_ID), anyInt());
+        doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra(
+                eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
+        doReturn(TAG).when(mIntent).getStringExtra(
+                eq(SlicePurchaseController.EXTRA_REQUESTING_APP_NAME));
+        assertFalse(SlicePurchaseBroadcastReceiver.isIntentValid(mIntent));
+
+        // set up pending intent
+        doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(mPendingIntent).getCreatorPackage();
+        doReturn(true).when(mPendingIntent).isBroadcast();
+        doReturn(mPendingIntent).when(mIntent).getParcelableExtra(
+                anyString(), eq(PendingIntent.class));
+        assertTrue(SlicePurchaseBroadcastReceiver.isIntentValid(mIntent));
+    }
+
+    @Test
+    public void testDisplayBoosterNotification() {
+        // set up intent
+        doReturn(SlicePurchaseController.ACTION_START_SLICE_PURCHASE_APP).when(mIntent).getAction();
+        doReturn(PHONE_ID).when(mIntent).getIntExtra(
+                eq(SlicePurchaseController.EXTRA_PHONE_ID), anyInt());
+        doReturn(SubscriptionManager.getDefaultDataSubscriptionId()).when(mIntent).getIntExtra(
+                eq(SlicePurchaseController.EXTRA_SUB_ID), anyInt());
+        doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra(
+                eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
+        doReturn(TAG).when(mIntent).getStringExtra(
+                eq(SlicePurchaseController.EXTRA_REQUESTING_APP_NAME));
+
+        // set up pending intent
+        doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(mPendingIntent).getCreatorPackage();
+        doReturn(true).when(mPendingIntent).isBroadcast();
+        doReturn(mPendingIntent).when(mIntent).getParcelableExtra(
+                anyString(), eq(PendingIntent.class));
+
+        // set up notification
+        doReturn(mResources).when(mContext).getResources();
+        doReturn(mDisplayMetrics).when(mResources).getDisplayMetrics();
+        doReturn("").when(mResources).getString(anyInt());
+        doReturn(mApplicationInfo).when(mContext).getApplicationInfo();
+        doReturn(mPackageManager).when(mContext).getPackageManager();
+
+        // set up intents created by broadcast receiver
+        doReturn(mContentIntent1).when(mSlicePurchaseBroadcastReceiver).createContentIntent(
+                eq(mContext), eq(mIntent), eq(1));
+        doReturn(mContentIntent2).when(mSlicePurchaseBroadcastReceiver).createContentIntent(
+                eq(mContext), eq(mIntent), eq(2));
+        doReturn(mCanceledIntent).when(mSlicePurchaseBroadcastReceiver).createCanceledIntent(
+                eq(mContext), eq(mIntent));
+
+        // send ACTION_START_SLICE_PURCHASE_APP
+        mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent);
+
+        // verify network boost notification was shown
+        verify(mNotificationManager).notifyAsUser(
+                eq(SlicePurchaseBroadcastReceiver.NETWORK_BOOST_NOTIFICATION_TAG),
+                eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY),
+                mNotificationCaptor.capture(),
+                eq(UserHandle.ALL));
+
+        Notification notification = mNotificationCaptor.getValue();
+        assertEquals(mContentIntent1, notification.contentIntent);
+        assertEquals(mPendingIntent, notification.deleteIntent);
+        assertEquals(2, notification.actions.length);
+        assertEquals(mCanceledIntent, notification.actions[0].actionIntent);
+        assertEquals(mContentIntent2, notification.actions[1].actionIntent);
+    }
+
+
+    @Test
+    public void testNotificationCanceled() {
+        // set up intent
+        doReturn("com.android.phone.slice.action.NOTIFICATION_CANCELED").when(mIntent).getAction();
+        doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra(
+                eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
+
+        // send ACTION_NOTIFICATION_CANCELED
+        mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent);
+
+        // verify notification was canceled
+        verify(mNotificationManager).cancelAsUser(
+                eq(SlicePurchaseBroadcastReceiver.NETWORK_BOOST_NOTIFICATION_TAG),
+                eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY),
+                eq(UserHandle.ALL));
+    }
+
+    @Test
+    public void testNotificationTimeout() {
+        // set up intent
+        doReturn(SlicePurchaseController.ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT).when(mIntent)
+                .getAction();
+        doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra(
+                eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
+
+        // send ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT
+        mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent);
+
+        // verify notification was canceled
+        verify(mNotificationManager).cancelAsUser(
+                eq(SlicePurchaseBroadcastReceiver.NETWORK_BOOST_NOTIFICATION_TAG),
+                eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY),
+                eq(UserHandle.ALL));
+    }
+
+    @Test
+    // TODO: WebView/Activity should not close on timeout.
+    //  This test should be removed once implementation is fixed.
+    public void testActivityTimeout() {
+        // create and track activity
+        SlicePurchaseBroadcastReceiver.updateSlicePurchaseActivity(
+                TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY, mSlicePurchaseActivity);
+
+        // set up intent
+        doReturn(SlicePurchaseController.ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT).when(mIntent)
+                .getAction();
+        doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra(
+                eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
+
+        // send ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT
+        mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent);
+
+        // verify activity was canceled
+        verify(mSlicePurchaseActivity).finishAndRemoveTask();
+
+        // untrack activity
+        SlicePurchaseBroadcastReceiver.removeSlicePurchaseActivity(
+                TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY);
+    }
+}
diff --git a/packages/SettingsLib/OWNERS b/packages/SettingsLib/OWNERS
index 8eafbdf..a53782a 100644
--- a/packages/SettingsLib/OWNERS
+++ b/packages/SettingsLib/OWNERS
@@ -3,10 +3,11 @@
 edgarwang@google.com
 emilychuang@google.com
 evanlaird@google.com
+hanxu@google.com
 juliacr@google.com
 leifhendrik@google.com
-tmfang@google.com
 virgild@google.com
+ykhung@google.com
 
 # Exempt resource files (because they are in a flat directory and too hard to manage via OWNERS)
 per-file *.xml=*
diff --git a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
index 50cab84..1e52aaf 100644
--- a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
+++ b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
@@ -41,6 +41,20 @@
             android:exported="false">
         </provider>
 
+        <provider android:name="com.android.settingslib.spa.framework.SpaSliceProvider"
+            android:authorities="com.android.spa.gallery.slice.provider"
+            android:exported="true" >
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.app.slice.category.SLICE" />
+            </intent-filter>
+        </provider>
+
+        <receiver
+            android:name="com.android.settingslib.spa.framework.SpaSliceBroadcastReceiver"
+            android:exported="false">
+        </receiver>
+
         <activity
             android:name="com.android.settingslib.spa.framework.debug.BlankActivity"
             android:exported="true">
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index 016b27f..941e770 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -17,6 +17,7 @@
 package com.android.settingslib.spa.gallery
 
 import android.content.Context
+import com.android.settingslib.spa.framework.SpaSliceBroadcastReceiver
 import com.android.settingslib.spa.framework.common.LocalLogger
 import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository
 import com.android.settingslib.spa.framework.common.SpaEnvironment
@@ -79,8 +80,8 @@
     }
 
     override val browseActivityClass = GalleryMainActivity::class.java
-
+    override val sliceBroadcastReceiverClass = SpaSliceBroadcastReceiver::class.java
     override val searchProviderAuthorities = "com.android.spa.gallery.search.provider"
-
+    override val sliceProviderAuthorities = "com.android.spa.gallery.slice.provider"
     override val logger = LocalLogger()
 }
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
index 26e59ff..ff89f2b 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
@@ -27,6 +27,7 @@
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.tooling.preview.Preview
 import com.android.settingslib.spa.framework.common.EntrySearchData
+import com.android.settingslib.spa.framework.common.EntrySliceData
 import com.android.settingslib.spa.framework.common.EntryStatusData
 import com.android.settingslib.spa.framework.common.SettingsEntry
 import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
@@ -37,6 +38,7 @@
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.spa.gallery.R
 import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
+import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.ASYNC_PREFERENCE_SUMMARY
 import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.ASYNC_PREFERENCE_TITLE
 import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.AUTO_UPDATE_PREFERENCE_TITLE
 import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.DISABLE_PREFERENCE_SUMMARY
@@ -46,10 +48,15 @@
 import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_KEYWORDS
 import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_SUMMARY
 import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_TITLE
+import com.android.settingslib.spa.slice.createBrowsePendingIntent
+import com.android.settingslib.spa.slice.provider.createDemoActionSlice
+import com.android.settingslib.spa.slice.provider.createDemoBrowseSlice
+import com.android.settingslib.spa.slice.provider.createDemoSlice
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
 import com.android.settingslib.spa.widget.preference.SimplePreferenceMacro
 import com.android.settingslib.spa.widget.ui.SettingsIcon
+import kotlinx.coroutines.delay
 
 private const val TAG = "PreferencePage"
 
@@ -134,6 +141,26 @@
                             override val enabled = model.asyncEnable
                         }
                     )
+                }
+                .setSliceDataFn { sliceUri, _ ->
+                    val createSliceImpl = { s: String ->
+                        createDemoBrowseSlice(
+                            sliceUri = sliceUri,
+                            title = ASYNC_PREFERENCE_TITLE,
+                            summary = s,
+                        )
+                    }
+                    return@setSliceDataFn object : EntrySliceData() {
+                        init {
+                            postValue(createSliceImpl("(loading)"))
+                        }
+
+                        override suspend fun asyncRunner() {
+                            spaLogger.message(TAG, "Async entry loading")
+                            delay(2000L)
+                            postValue(createSliceImpl(ASYNC_PREFERENCE_SUMMARY))
+                        }
+                    }
                 }.build()
         )
         entryList.add(
@@ -152,6 +179,27 @@
                             }
                         }
                     )
+                }.setSliceDataFn { sliceUri, args ->
+                    val createSliceImpl = { v: Int ->
+                        createDemoActionSlice(
+                            sliceUri = sliceUri,
+                            title = MANUAL_UPDATE_PREFERENCE_TITLE,
+                            summary = "manual update value $v",
+                        )
+                    }
+
+                    return@setSliceDataFn object : EntrySliceData() {
+                        private var tick = args?.getString("init")?.toInt() ?: 0
+
+                        init {
+                            postValue(createSliceImpl(tick))
+                        }
+
+                        override suspend fun asyncAction() {
+                            tick++
+                            postValue(createSliceImpl(tick))
+                        }
+                    }
                 }.build()
         )
         entryList.add(
@@ -170,7 +218,33 @@
                         }
                     )
                 }
-                .build()
+                .setSliceDataFn { sliceUri, args ->
+                    val createSliceImpl = { v: Int ->
+                        createDemoBrowseSlice(
+                            sliceUri = sliceUri,
+                            title = AUTO_UPDATE_PREFERENCE_TITLE,
+                            summary = "auto update value $v",
+                        )
+                    }
+
+                    return@setSliceDataFn object : EntrySliceData() {
+                        private var tick = args?.getString("init")?.toInt() ?: 0
+
+                        init {
+                            postValue(createSliceImpl(tick))
+                        }
+
+                        override suspend fun asyncRunner() {
+                            spaLogger.message(TAG, "autoUpdater.active")
+                            while (true) {
+                                delay(1000L)
+                                tick++
+                                spaLogger.message(TAG, "autoUpdater.value $tick")
+                                postValue(createSliceImpl(tick))
+                            }
+                        }
+                    }
+                }.build()
         )
 
         return entryList
@@ -201,6 +275,22 @@
                     clickRoute = SettingsPageProviderEnum.PREFERENCE.name
                 )
             }
+            .setSliceDataFn { sliceUri, _ ->
+                val intent = owner.createBrowseIntent()?.createBrowsePendingIntent()
+                    ?: return@setSliceDataFn null
+                return@setSliceDataFn object : EntrySliceData() {
+                    init {
+                        postValue(
+                            createDemoSlice(
+                                sliceUri = sliceUri,
+                                title = PAGE_TITLE,
+                                summary = "Injected Entry",
+                                intent = intent,
+                            )
+                        )
+                    }
+                }
+            }
     }
 
     override fun getTitle(arguments: Bundle?): String {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt
index d874417..fc6f10f 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt
@@ -44,7 +44,7 @@
         const val DISABLE_PREFERENCE_TITLE = "Disabled"
         const val DISABLE_PREFERENCE_SUMMARY = "Disabled summary"
         const val ASYNC_PREFERENCE_TITLE = "Async Preference"
-        private const val ASYNC_PREFERENCE_SUMMARY = "Async summary"
+        const val ASYNC_PREFERENCE_SUMMARY = "Async summary"
         const val MANUAL_UPDATE_PREFERENCE_TITLE = "Manual Updater"
         const val AUTO_UPDATE_PREFERENCE_TITLE = "Auto Updater"
         val SIMPLE_PREFERENCE_KEYWORDS = listOf("simple keyword1", "simple keyword2")
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceBroadcastReceiver.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceBroadcastReceiver.kt
index 8c038c8..58131e6 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceBroadcastReceiver.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceBroadcastReceiver.kt
@@ -23,7 +23,7 @@
 
 class SpaSliceBroadcastReceiver : BroadcastReceiver() {
     override fun onReceive(context: Context?, intent: Intent?) {
-        val sliceRepository = SpaEnvironmentFactory.instance.sliceDataRepository
+        val sliceRepository by SpaEnvironmentFactory.instance.sliceDataRepository
         val sliceUri = intent?.data ?: return
         val sliceData = sliceRepository.getActiveSliceData(sliceUri) ?: return
         sliceData.doAction()
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceProvider.kt
index d800e50..faa04fd 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceProvider.kt
@@ -32,7 +32,8 @@
 class SpaSliceProvider : SliceProvider(), Observer<Slice?> {
     private fun getOrPutSliceData(sliceUri: Uri): EntrySliceData? {
         if (!SpaEnvironmentFactory.isReady()) return null
-        return SpaEnvironmentFactory.instance.sliceDataRepository.getOrBuildSliceData(sliceUri)
+        val sliceRepository by SpaEnvironmentFactory.instance.sliceDataRepository
+        return sliceRepository.getOrBuildSliceData(sliceUri)
     }
 
     override fun onBindSlice(sliceUri: Uri): Slice? {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
index 9bb06f0..945add4 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
@@ -65,7 +65,7 @@
 
     val entryRepository = lazy { SettingsEntryRepository(pageProviderRepository.value) }
 
-    val sliceDataRepository = SettingsSliceDataRepository()
+    val sliceDataRepository = lazy { SettingsSliceDataRepository(entryRepository.value) }
 
     // In Robolectric test, applicationContext is not available. Use context as fallback.
     val appContext: Context = context.applicationContext ?: context
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt
index d8d2378..14855a8 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt
@@ -19,11 +19,11 @@
 import android.net.Uri
 import android.util.Log
 import com.android.settingslib.spa.framework.common.EntrySliceData
-import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.framework.common.SettingsEntryRepository
 
 private const val TAG = "SliceDataRepository"
 
-class SettingsSliceDataRepository {
+class SettingsSliceDataRepository(private val entryRepository: SettingsEntryRepository) {
     // The map of slice uri to its EntrySliceData, a.k.a. LiveData<Slice?>
     private val sliceDataMap: MutableMap<String, EntrySliceData> = mutableMapOf()
 
@@ -46,13 +46,10 @@
 
     private fun buildLiveDataImpl(sliceUri: Uri): EntrySliceData? {
         Log.d(TAG, "buildLiveData: $sliceUri")
-        if (!SpaEnvironmentFactory.isReady()) return null
 
-        val entryRepository by SpaEnvironmentFactory.instance.entryRepository
         val entryId = sliceUri.getEntryId() ?: return null
         val entry = entryRepository.getEntry(entryId) ?: return null
         if (!entry.hasSliceSupport) return null
-
         val arguments = sliceUri.getRuntimeArguments()
         return entry.getSliceData(runtimeArguments = arguments, sliceUri = sliceUri)
     }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt
index 215d22c..cb0bfc6 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt
@@ -26,43 +26,76 @@
 
 private const val TAG = "PackageManagers"
 
-object PackageManagers {
-    private val iPackageManager by lazy { AppGlobals.getPackageManager() }
-
-    fun getPackageInfoAsUser(packageName: String, userId: Int): PackageInfo? =
-        getPackageInfoAsUser(packageName, 0, userId)
-
-    fun getApplicationInfoAsUser(packageName: String, userId: Int): ApplicationInfo? =
-        PackageManager.getApplicationInfoAsUserCached(packageName, 0, userId)
+interface IPackageManagers {
+    fun getPackageInfoAsUser(packageName: String, userId: Int): PackageInfo?
+    fun getApplicationInfoAsUser(packageName: String, userId: Int): ApplicationInfo?
 
     /** Checks whether a package is installed for a given user. */
-    fun isPackageInstalledAsUser(packageName: String, userId: Int): Boolean =
+    fun isPackageInstalledAsUser(packageName: String, userId: Int): Boolean
+    fun ApplicationInfo.hasRequestPermission(permission: String): Boolean
+
+    /** Checks whether a permission is currently granted to the application. */
+    fun ApplicationInfo.hasGrantPermission(permission: String): Boolean
+
+    suspend fun getAppOpPermissionPackages(userId: Int, permission: String): Set<String>
+    fun getPackageInfoAsUser(packageName: String, flags: Int, userId: Int): PackageInfo?
+}
+
+object PackageManagers : IPackageManagers by PackageManagersImpl(PackageManagerWrapperImpl)
+
+internal interface PackageManagerWrapper {
+    fun getPackageInfoAsUserCached(
+        packageName: String,
+        flags: Long,
+        userId: Int,
+    ): PackageInfo?
+}
+
+internal object PackageManagerWrapperImpl : PackageManagerWrapper {
+    override fun getPackageInfoAsUserCached(
+        packageName: String,
+        flags: Long,
+        userId: Int,
+    ): PackageInfo? = PackageManager.getPackageInfoAsUserCached(packageName, flags, userId)
+}
+
+internal class PackageManagersImpl(
+    private val packageManagerWrapper: PackageManagerWrapper,
+) : IPackageManagers {
+    private val iPackageManager by lazy { AppGlobals.getPackageManager() }
+
+    override fun getPackageInfoAsUser(packageName: String, userId: Int): PackageInfo? =
+        getPackageInfoAsUser(packageName, 0, userId)
+
+    override fun getApplicationInfoAsUser(packageName: String, userId: Int): ApplicationInfo? =
+        PackageManager.getApplicationInfoAsUserCached(packageName, 0, userId)
+
+    override fun isPackageInstalledAsUser(packageName: String, userId: Int): Boolean =
         getApplicationInfoAsUser(packageName, userId)?.hasFlag(ApplicationInfo.FLAG_INSTALLED)
             ?: false
 
-    fun ApplicationInfo.hasRequestPermission(permission: String): Boolean {
+    override fun ApplicationInfo.hasRequestPermission(permission: String): Boolean {
         val packageInfo = getPackageInfoAsUser(packageName, PackageManager.GET_PERMISSIONS, userId)
         return packageInfo?.requestedPermissions?.let {
             permission in it
         } ?: false
     }
 
-    fun ApplicationInfo.hasGrantPermission(permission: String): Boolean {
+    override fun ApplicationInfo.hasGrantPermission(permission: String): Boolean {
         val packageInfo = getPackageInfoAsUser(packageName, PackageManager.GET_PERMISSIONS, userId)
-            ?: return false
-        val index = packageInfo.requestedPermissions.indexOf(permission)
+        val index = packageInfo?.requestedPermissions?.indexOf(permission) ?: return false
         return index >= 0 &&
             packageInfo.requestedPermissionsFlags[index].hasFlag(REQUESTED_PERMISSION_GRANTED)
     }
 
-    suspend fun getAppOpPermissionPackages(userId: Int, permission: String): Set<String> =
+    override suspend fun getAppOpPermissionPackages(userId: Int, permission: String): Set<String> =
         iPackageManager.getAppOpPermissionPackages(permission, userId).asIterable().asyncFilter {
             iPackageManager.isPackageAvailable(it, userId)
         }.toSet()
 
-    fun getPackageInfoAsUser(packageName: String, flags: Int, userId: Int): PackageInfo? =
+    override fun getPackageInfoAsUser(packageName: String, flags: Int, userId: Int): PackageInfo? =
         try {
-            PackageManager.getPackageInfoAsUserCached(packageName, flags.toLong(), userId)
+            packageManagerWrapper.getPackageInfoAsUserCached(packageName, flags.toLong(), userId)
         } catch (e: PackageManager.NameNotFoundException) {
             Log.w(TAG, "getPackageInfoAsUserCached() failed", e)
             null
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagersTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagersTest.kt
new file mode 100644
index 0000000..6c31f4b
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagersTest.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.model.app
+
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageInfo
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class PackageManagersTest {
+
+    private val fakePackageManagerWrapper = FakePackageManagerWrapper()
+
+    private val packageManagersImpl = PackageManagersImpl(fakePackageManagerWrapper)
+
+    @Test
+    fun hasGrantPermission_packageInfoIsNull_returnFalse() {
+        fakePackageManagerWrapper.fakePackageInfo = null
+
+        val hasGrantPermission = with(packageManagersImpl) {
+            APP.hasGrantPermission(PERMISSION_A)
+        }
+
+        assertThat(hasGrantPermission).isFalse()
+    }
+
+    @Test
+    fun hasGrantPermission_requestedPermissionsIsNull_returnFalse() {
+        fakePackageManagerWrapper.fakePackageInfo = PackageInfo()
+
+        val hasGrantPermission = with(packageManagersImpl) {
+            APP.hasGrantPermission(PERMISSION_A)
+        }
+
+        assertThat(hasGrantPermission).isFalse()
+    }
+
+    @Test
+    fun hasGrantPermission_notRequested_returnFalse() {
+        fakePackageManagerWrapper.fakePackageInfo = PackageInfo().apply {
+            requestedPermissions = arrayOf(PERMISSION_B)
+            requestedPermissionsFlags = intArrayOf(PackageInfo.REQUESTED_PERMISSION_GRANTED)
+        }
+
+        val hasGrantPermission = with(packageManagersImpl) {
+            APP.hasGrantPermission(PERMISSION_A)
+        }
+
+        assertThat(hasGrantPermission).isFalse()
+    }
+
+    @Test
+    fun hasGrantPermission_notGranted_returnFalse() {
+        fakePackageManagerWrapper.fakePackageInfo = PackageInfo().apply {
+            requestedPermissions = arrayOf(PERMISSION_A, PERMISSION_B)
+            requestedPermissionsFlags = intArrayOf(0, PackageInfo.REQUESTED_PERMISSION_GRANTED)
+        }
+
+        val hasGrantPermission = with(packageManagersImpl) {
+            APP.hasGrantPermission(PERMISSION_A)
+        }
+
+        assertThat(hasGrantPermission).isFalse()
+    }
+
+    @Test
+    fun hasGrantPermission_granted_returnTrue() {
+        fakePackageManagerWrapper.fakePackageInfo = PackageInfo().apply {
+            requestedPermissions = arrayOf(PERMISSION_A, PERMISSION_B)
+            requestedPermissionsFlags = intArrayOf(PackageInfo.REQUESTED_PERMISSION_GRANTED, 0)
+        }
+
+        val hasGrantPermission = with(packageManagersImpl) {
+            APP.hasGrantPermission(PERMISSION_A)
+        }
+
+        assertThat(hasGrantPermission).isTrue()
+    }
+
+    private inner class FakePackageManagerWrapper : PackageManagerWrapper {
+        var fakePackageInfo: PackageInfo? = null
+
+        override fun getPackageInfoAsUserCached(
+            packageName: String,
+            flags: Long,
+            userId: Int,
+        ): PackageInfo? = fakePackageInfo
+    }
+
+    private companion object {
+        const val PACKAGE_NAME = "packageName"
+        const val PERMISSION_A = "permission.A"
+        const val PERMISSION_B = "permission.B"
+        const val UID = 123
+        val APP = ApplicationInfo().apply {
+            packageName = PACKAGE_NAME
+            uid = UID
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java
index 3e33da5..ece8986 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java
@@ -50,6 +50,34 @@
 
     @Override
     public void clicked(int sourceCategory, String key) {
+        final LogMaker logMaker = new LogMaker(MetricsProto.MetricsEvent.ACTION_SETTINGS_TILE_CLICK)
+                .setType(MetricsProto.MetricsEvent.TYPE_ACTION);
+        if (sourceCategory != MetricsProto.MetricsEvent.VIEW_UNKNOWN) {
+            logMaker.addTaggedData(MetricsProto.MetricsEvent.FIELD_CONTEXT, sourceCategory);
+        }
+        if (!TextUtils.isEmpty(key)) {
+            logMaker.addTaggedData(MetricsProto.MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_NAME,
+                    key);
+        }
+        MetricsLogger.action(logMaker);
+    }
+
+    @Override
+    public void changed(int category, String key, int value) {
+        final LogMaker logMaker = new LogMaker(
+                MetricsProto.MetricsEvent.ACTION_SETTINGS_PREFERENCE_CHANGE)
+                .setType(MetricsProto.MetricsEvent.TYPE_ACTION);
+        if (category != MetricsProto.MetricsEvent.VIEW_UNKNOWN) {
+            logMaker.addTaggedData(MetricsProto.MetricsEvent.FIELD_CONTEXT, category);
+        }
+        if (!TextUtils.isEmpty(key)) {
+            logMaker.addTaggedData(MetricsProto.MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_NAME,
+                    key);
+            logMaker.addTaggedData(
+                    MetricsProto.MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE,
+                    value);
+        }
+        MetricsLogger.action(logMaker);
     }
 
     @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java
index cceca13..dcd6cce 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java
@@ -39,6 +39,11 @@
     void clicked(int category, String key);
 
     /**
+     * Logs a value changed event when user changed item value.
+     */
+    void changed(int category, String key, int value);
+
+    /**
      * Logs an user action.
      */
     void action(Context context, int category, Pair<Integer, Object>... taggedData);
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
index 915421a..09abc39 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
@@ -108,6 +108,19 @@
     }
 
     /**
+     * Logs a value changed event when user changed item value.
+     *
+     * @param category the target page id
+     * @param key the key id that user clicked
+     * @param value the value that user changed which converted to integer
+     */
+    public void changed(int category, String key, int value) {
+        for (LogWriter writer : mLoggerWriters) {
+            writer.changed(category, key, value);
+        }
+    }
+
+    /**
      * Logs a simple action without page id or attribution
      *
      * @param category the target page
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java
index 869de0de..067afa4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java
@@ -35,15 +35,22 @@
     private static final String LOG_TAG = "SharedPreferencesLogger";
 
     private final String mTag;
+    private final int mMetricCategory;
     private final Context mContext;
     private final MetricsFeatureProvider mMetricsFeature;
     private final Set<String> mPreferenceKeySet;
 
     public SharedPreferencesLogger(Context context, String tag,
             MetricsFeatureProvider metricsFeature) {
+        this(context, tag, metricsFeature, SettingsEnums.PAGE_UNKNOWN);
+    }
+
+    public SharedPreferencesLogger(Context context, String tag,
+            MetricsFeatureProvider metricsFeature, int metricCategory) {
         mContext = context;
         mTag = tag;
         mMetricsFeature = metricsFeature;
+        mMetricCategory = metricCategory;
         mPreferenceKeySet = new ConcurrentSkipListSet<>();
     }
 
@@ -151,20 +158,15 @@
             return;
         }
         // Pref key exists in set, log its change in metrics.
-        mMetricsFeature.action(SettingsEnums.PAGE_UNKNOWN,
-                SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE,
-                SettingsEnums.PAGE_UNKNOWN,
-                prefKey,
-                intVal);
+        mMetricsFeature.changed(mMetricCategory, key, intVal);
     }
 
     @VisibleForTesting
     void logPackageName(String key, String value) {
-        final String prefKey = mTag + "/" + key;
-        mMetricsFeature.action(SettingsEnums.PAGE_UNKNOWN,
+        mMetricsFeature.action(mMetricCategory,
                 SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE,
                 SettingsEnums.PAGE_UNKNOWN,
-                prefKey + ":" + value,
+                key + ":" + value,
                 0);
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
index 34da305..3e710e4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
@@ -24,7 +24,6 @@
 import android.os.UserHandle;
 import android.provider.Settings.Global;
 import android.provider.Settings.Secure;
-import android.text.TextUtils;
 import android.util.KeyValueListParser;
 import android.util.Log;
 import android.util.Slog;
@@ -221,17 +220,14 @@
     }
 
     /**
-     * Reverts battery saver schedule mode to none if we are in a bad state where routine mode
-     * is selected but no app is configured to actually provide the signal.
+     * Reverts battery saver schedule mode to none if routine mode is selected.
      * @param context a valid context
      */
     public static void revertScheduleToNoneIfNeeded(Context context) {
         ContentResolver resolver = context.getContentResolver();
         final int currentMode = Global.getInt(resolver, Global.AUTOMATIC_POWER_SAVE_MODE,
                 PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE);
-        boolean providerConfigured = !TextUtils.isEmpty(context.getString(
-                com.android.internal.R.string.config_batterySaverScheduleProvider));
-        if (currentMode == PowerManager.POWER_SAVE_MODE_TRIGGER_DYNAMIC && !providerConfigured) {
+        if (currentMode == PowerManager.POWER_SAVE_MODE_TRIGGER_DYNAMIC) {
             Global.putInt(resolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0);
             Global.putInt(resolver, Global.AUTOMATIC_POWER_SAVE_MODE,
                     PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SharedPreferenceLoggerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SharedPreferenceLoggerTest.java
index 89de81f..a2b208a 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SharedPreferenceLoggerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SharedPreferenceLoggerTest.java
@@ -39,7 +39,6 @@
 
     private static final String TEST_TAG = "tag";
     private static final String TEST_KEY = "key";
-    private static final String TEST_TAGGED_KEY = TEST_TAG + "/" + TEST_KEY;
 
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private Context mContext;
@@ -65,10 +64,8 @@
         editor.putInt(TEST_KEY, 2);
         editor.putInt(TEST_KEY, 2);
 
-        verify(mMetricsFeature, times(6)).action(eq(SettingsEnums.PAGE_UNKNOWN),
-                eq(SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE),
-                eq(SettingsEnums.PAGE_UNKNOWN),
-                eq(TEST_TAGGED_KEY),
+        verify(mMetricsFeature, times(6)).changed(eq(SettingsEnums.PAGE_UNKNOWN),
+                eq(TEST_KEY),
                 anyInt());
     }
 
@@ -82,15 +79,11 @@
         editor.putBoolean(TEST_KEY, false);
 
 
-        verify(mMetricsFeature).action(SettingsEnums.PAGE_UNKNOWN,
-                SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE,
-                SettingsEnums.PAGE_UNKNOWN,
-                TEST_TAGGED_KEY,
+        verify(mMetricsFeature).changed(SettingsEnums.PAGE_UNKNOWN,
+                TEST_KEY,
                 1);
-        verify(mMetricsFeature, times(3)).action(SettingsEnums.PAGE_UNKNOWN,
-                SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE,
-                SettingsEnums.PAGE_UNKNOWN,
-                TEST_TAGGED_KEY,
+        verify(mMetricsFeature, times(3)).changed(SettingsEnums.PAGE_UNKNOWN,
+                TEST_KEY,
                 0);
     }
 
@@ -103,10 +96,8 @@
         editor.putLong(TEST_KEY, 1);
         editor.putLong(TEST_KEY, 2);
 
-        verify(mMetricsFeature, times(4)).action(eq(SettingsEnums.PAGE_UNKNOWN),
-                eq(SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE),
-                eq(SettingsEnums.PAGE_UNKNOWN),
-                eq(TEST_TAGGED_KEY),
+        verify(mMetricsFeature, times(4)).changed(eq(SettingsEnums.PAGE_UNKNOWN),
+                eq(TEST_KEY),
                 anyInt());
     }
 
@@ -117,10 +108,8 @@
         editor.putLong(TEST_KEY, 1);
         editor.putLong(TEST_KEY, veryBigNumber);
 
-        verify(mMetricsFeature).action(SettingsEnums.PAGE_UNKNOWN,
-                SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE,
-                SettingsEnums.PAGE_UNKNOWN,
-                TEST_TAGGED_KEY,
+        verify(mMetricsFeature).changed(SettingsEnums.PAGE_UNKNOWN,
+                TEST_KEY,
                 Integer.MAX_VALUE);
     }
 
@@ -131,10 +120,8 @@
         editor.putLong(TEST_KEY, 1);
         editor.putLong(TEST_KEY, veryNegativeNumber);
 
-        verify(mMetricsFeature).action(SettingsEnums.PAGE_UNKNOWN,
-                SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE,
-                SettingsEnums.PAGE_UNKNOWN,
-                TEST_TAGGED_KEY, Integer.MIN_VALUE);
+        verify(mMetricsFeature).changed(SettingsEnums.PAGE_UNKNOWN,
+                TEST_KEY, Integer.MIN_VALUE);
     }
 
     @Test
@@ -146,10 +133,8 @@
         editor.putFloat(TEST_KEY, 1);
         editor.putFloat(TEST_KEY, 2);
 
-        verify(mMetricsFeature, times(4)).action(eq(SettingsEnums.PAGE_UNKNOWN),
-                eq(SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE),
-                eq(SettingsEnums.PAGE_UNKNOWN),
-                eq(TEST_TAGGED_KEY),
+        verify(mMetricsFeature, times(4)).changed(eq(SettingsEnums.PAGE_UNKNOWN),
+                eq(TEST_KEY),
                 anyInt());
     }
 
@@ -159,7 +144,7 @@
         verify(mMetricsFeature).action(SettingsEnums.PAGE_UNKNOWN,
                 ACTION_SETTINGS_PREFERENCE_CHANGE,
                 SettingsEnums.PAGE_UNKNOWN,
-                "tag/key:com.android.settings",
+                "key:com.android.settings",
                 0);
     }
 
@@ -170,10 +155,8 @@
         mSharedPrefLogger.logValue(TEST_KEY, "62");
         mSharedPrefLogger.logValue(TEST_KEY, "0");
 
-        verify(mMetricsFeature, times(3)).action(eq(SettingsEnums.PAGE_UNKNOWN),
-                eq(SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE),
-                eq(SettingsEnums.PAGE_UNKNOWN),
-                eq(TEST_TAGGED_KEY),
+        verify(mMetricsFeature, times(3)).changed(eq(SettingsEnums.PAGE_UNKNOWN),
+                eq(TEST_KEY),
                 anyInt());
     }
 
@@ -185,10 +168,8 @@
         mSharedPrefLogger.logValue(TEST_KEY, "4.2");
         mSharedPrefLogger.logValue(TEST_KEY, "3.0");
 
-        verify(mMetricsFeature, times(0)).action(eq(SettingsEnums.PAGE_UNKNOWN),
-                eq(SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE),
-                eq(SettingsEnums.PAGE_UNKNOWN),
-                eq(TEST_TAGGED_KEY),
+        verify(mMetricsFeature, times(0)).changed(eq(SettingsEnums.PAGE_UNKNOWN),
+                eq(TEST_KEY),
                 anyInt());
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt b/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt
deleted file mode 100644
index 4d94bab..0000000
--- a/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt
+++ /dev/null
@@ -1,392 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.user.ui.compose
-
-import android.graphics.drawable.Drawable
-import androidx.appcompat.content.res.AppCompatResources
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.background
-import androidx.compose.foundation.border
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.heightIn
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.sizeIn
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.material3.DropdownMenu
-import androidx.compose.material3.DropdownMenuItem
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.alpha
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.graphics.asImageBitmap
-import androidx.compose.ui.graphics.painter.ColorPainter
-import androidx.compose.ui.platform.LocalConfiguration
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.res.colorResource
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.style.TextOverflow
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
-import androidx.core.graphics.drawable.toBitmap
-import com.android.systemui.common.ui.compose.load
-import com.android.systemui.compose.SysUiOutlinedButton
-import com.android.systemui.compose.SysUiTextButton
-import com.android.systemui.compose.features.R
-import com.android.systemui.compose.theme.LocalAndroidColorScheme
-import com.android.systemui.user.ui.viewmodel.UserActionViewModel
-import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
-import com.android.systemui.user.ui.viewmodel.UserViewModel
-import java.lang.Integer.min
-import kotlin.math.ceil
-
-@Composable
-fun UserSwitcherScreen(
-    viewModel: UserSwitcherViewModel,
-    onFinished: () -> Unit,
-    modifier: Modifier = Modifier,
-) {
-    val isFinishRequested: Boolean by viewModel.isFinishRequested.collectAsState(false)
-    val users: List<UserViewModel> by viewModel.users.collectAsState(emptyList())
-    val maxUserColumns: Int by viewModel.maximumUserColumns.collectAsState(1)
-    val menuActions: List<UserActionViewModel> by viewModel.menu.collectAsState(emptyList())
-    val isOpenMenuButtonVisible: Boolean by viewModel.isOpenMenuButtonVisible.collectAsState(false)
-    val isMenuVisible: Boolean by viewModel.isMenuVisible.collectAsState(false)
-
-    UserSwitcherScreenStateless(
-        isFinishRequested = isFinishRequested,
-        users = users,
-        maxUserColumns = maxUserColumns,
-        menuActions = menuActions,
-        isOpenMenuButtonVisible = isOpenMenuButtonVisible,
-        isMenuVisible = isMenuVisible,
-        onMenuClosed = viewModel::onMenuClosed,
-        onOpenMenuButtonClicked = viewModel::onOpenMenuButtonClicked,
-        onCancelButtonClicked = viewModel::onCancelButtonClicked,
-        onFinished = {
-            onFinished()
-            viewModel.onFinished()
-        },
-        modifier = modifier,
-    )
-}
-
-@Composable
-private fun UserSwitcherScreenStateless(
-    isFinishRequested: Boolean,
-    users: List<UserViewModel>,
-    maxUserColumns: Int,
-    menuActions: List<UserActionViewModel>,
-    isOpenMenuButtonVisible: Boolean,
-    isMenuVisible: Boolean,
-    onMenuClosed: () -> Unit,
-    onOpenMenuButtonClicked: () -> Unit,
-    onCancelButtonClicked: () -> Unit,
-    onFinished: () -> Unit,
-    modifier: Modifier = Modifier,
-) {
-    LaunchedEffect(isFinishRequested) {
-        if (isFinishRequested) {
-            onFinished()
-        }
-    }
-
-    Box(
-        modifier =
-            modifier
-                .fillMaxSize()
-                .padding(
-                    horizontal = 60.dp,
-                    vertical = 40.dp,
-                ),
-    ) {
-        UserGrid(
-            users = users,
-            maxUserColumns = maxUserColumns,
-            modifier = Modifier.align(Alignment.Center),
-        )
-
-        Buttons(
-            menuActions = menuActions,
-            isOpenMenuButtonVisible = isOpenMenuButtonVisible,
-            isMenuVisible = isMenuVisible,
-            onMenuClosed = onMenuClosed,
-            onOpenMenuButtonClicked = onOpenMenuButtonClicked,
-            onCancelButtonClicked = onCancelButtonClicked,
-            modifier = Modifier.align(Alignment.BottomEnd),
-        )
-    }
-}
-
-@Composable
-private fun UserGrid(
-    users: List<UserViewModel>,
-    maxUserColumns: Int,
-    modifier: Modifier = Modifier,
-) {
-    Column(
-        horizontalAlignment = Alignment.CenterHorizontally,
-        verticalArrangement = Arrangement.spacedBy(44.dp),
-        modifier = modifier,
-    ) {
-        val rowCount = ceil(users.size / maxUserColumns.toFloat()).toInt()
-        (0 until rowCount).forEach { rowIndex ->
-            Row(
-                horizontalArrangement = Arrangement.spacedBy(64.dp),
-                modifier = modifier,
-            ) {
-                val fromIndex = rowIndex * maxUserColumns
-                val toIndex = min(users.size, (rowIndex + 1) * maxUserColumns)
-                users.subList(fromIndex, toIndex).forEach { user ->
-                    UserItem(
-                        viewModel = user,
-                    )
-                }
-            }
-        }
-    }
-}
-
-@Composable
-private fun UserItem(
-    viewModel: UserViewModel,
-) {
-    val onClicked = viewModel.onClicked
-    Column(
-        horizontalAlignment = Alignment.CenterHorizontally,
-        modifier =
-            if (onClicked != null) {
-                    Modifier.clickable { onClicked() }
-                } else {
-                    Modifier
-                }
-                .alpha(viewModel.alpha),
-    ) {
-        Box {
-            UserItemBackground(modifier = Modifier.align(Alignment.Center).size(222.dp))
-
-            UserItemIcon(
-                image = viewModel.image,
-                isSelectionMarkerVisible = viewModel.isSelectionMarkerVisible,
-                modifier = Modifier.align(Alignment.Center).size(222.dp)
-            )
-        }
-
-        // User name
-        val text = viewModel.name.load()
-        if (text != null) {
-            // We use the box to center-align the text vertically as that is not possible with Text
-            // alone.
-            Box(
-                modifier = Modifier.size(width = 222.dp, height = 48.dp),
-            ) {
-                Text(
-                    text = text,
-                    style = MaterialTheme.typography.titleLarge,
-                    color = colorResource(com.android.internal.R.color.system_neutral1_50),
-                    maxLines = 1,
-                    overflow = TextOverflow.Ellipsis,
-                    modifier = Modifier.align(Alignment.Center),
-                )
-            }
-        }
-    }
-}
-
-@Composable
-private fun UserItemBackground(
-    modifier: Modifier = Modifier,
-) {
-    Image(
-        painter = ColorPainter(LocalAndroidColorScheme.current.colorBackground),
-        contentDescription = null,
-        modifier = modifier.clip(CircleShape),
-    )
-}
-
-@Composable
-private fun UserItemIcon(
-    image: Drawable,
-    isSelectionMarkerVisible: Boolean,
-    modifier: Modifier = Modifier,
-) {
-    Image(
-        bitmap = image.toBitmap().asImageBitmap(),
-        contentDescription = null,
-        modifier =
-            if (isSelectionMarkerVisible) {
-                    // Draws a ring
-                    modifier.border(
-                        width = 8.dp,
-                        color = LocalAndroidColorScheme.current.colorAccentPrimary,
-                        shape = CircleShape,
-                    )
-                } else {
-                    modifier
-                }
-                .padding(16.dp)
-                .clip(CircleShape)
-    )
-}
-
-@Composable
-private fun Buttons(
-    menuActions: List<UserActionViewModel>,
-    isOpenMenuButtonVisible: Boolean,
-    isMenuVisible: Boolean,
-    onMenuClosed: () -> Unit,
-    onOpenMenuButtonClicked: () -> Unit,
-    onCancelButtonClicked: () -> Unit,
-    modifier: Modifier = Modifier,
-) {
-    Row(
-        modifier = modifier,
-    ) {
-        // Cancel button.
-        SysUiTextButton(
-            onClick = onCancelButtonClicked,
-        ) {
-            Text(stringResource(R.string.cancel))
-        }
-
-        // "Open menu" button.
-        if (isOpenMenuButtonVisible) {
-            Spacer(modifier = Modifier.width(8.dp))
-            // To properly use a DropdownMenu in Compose, we need to wrap the button that opens it
-            // and the menu itself in a Box.
-            Box {
-                SysUiOutlinedButton(
-                    onClick = onOpenMenuButtonClicked,
-                ) {
-                    Text(stringResource(R.string.add))
-                }
-                Menu(
-                    viewModel = menuActions,
-                    isMenuVisible = isMenuVisible,
-                    onMenuClosed = onMenuClosed,
-                )
-            }
-        }
-    }
-}
-
-@Composable
-private fun Menu(
-    viewModel: List<UserActionViewModel>,
-    isMenuVisible: Boolean,
-    onMenuClosed: () -> Unit,
-    modifier: Modifier = Modifier,
-) {
-    val maxItemWidth = LocalConfiguration.current.screenWidthDp.dp / 4
-    DropdownMenu(
-        expanded = isMenuVisible,
-        onDismissRequest = onMenuClosed,
-        modifier =
-            modifier.background(
-                color = MaterialTheme.colorScheme.inverseOnSurface,
-            ),
-    ) {
-        viewModel.forEachIndexed { index, action ->
-            MenuItem(
-                viewModel = action,
-                onClicked = { action.onClicked() },
-                topPadding =
-                    if (index == 0) {
-                        16.dp
-                    } else {
-                        0.dp
-                    },
-                bottomPadding =
-                    if (index == viewModel.size - 1) {
-                        16.dp
-                    } else {
-                        0.dp
-                    },
-                modifier = Modifier.sizeIn(maxWidth = maxItemWidth),
-            )
-        }
-    }
-}
-
-@Composable
-private fun MenuItem(
-    viewModel: UserActionViewModel,
-    onClicked: () -> Unit,
-    topPadding: Dp,
-    bottomPadding: Dp,
-    modifier: Modifier = Modifier,
-) {
-    val context = LocalContext.current
-    val density = LocalDensity.current
-
-    val icon =
-        remember(viewModel.iconResourceId) {
-            val drawable =
-                checkNotNull(AppCompatResources.getDrawable(context, viewModel.iconResourceId))
-            val size = with(density) { 20.dp.toPx() }.toInt()
-            drawable
-                .toBitmap(
-                    width = size,
-                    height = size,
-                )
-                .asImageBitmap()
-        }
-
-    DropdownMenuItem(
-        text = {
-            Text(
-                text = stringResource(viewModel.textResourceId),
-                style = MaterialTheme.typography.bodyMedium,
-            )
-        },
-        onClick = onClicked,
-        leadingIcon = {
-            Spacer(modifier = Modifier.width(10.dp))
-            Image(
-                bitmap = icon,
-                contentDescription = null,
-            )
-        },
-        modifier =
-            modifier
-                .heightIn(
-                    min = 56.dp,
-                )
-                .padding(
-                    start = 18.dp,
-                    end = 65.dp,
-                    top = topPadding,
-                    bottom = bottomPadding,
-                ),
-    )
-}
diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt
index 06ea381..7fc9a83 100644
--- a/packages/SystemUI/ktfmt_includes.txt
+++ b/packages/SystemUI/ktfmt_includes.txt
@@ -16,7 +16,6 @@
 -packages/SystemUI/checks/tests/com/android/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
 -packages/SystemUI/checks/tests/com/android/systemui/lint/SoftwareBitmapDetectorTest.kt
 -packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
--packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
 -packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSContainerController.kt
 -packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
 -packages/SystemUI/shared/src/com/android/systemui/flags/FlagListenable.kt
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
index 89f5c2c..66e44b9 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -70,10 +70,10 @@
     }
 
     /** Optional method for dumping debug information */
-    fun dump(pw: PrintWriter) { }
+    fun dump(pw: PrintWriter) {}
 
     /** Optional method for debug logging */
-    fun setLogBuffer(logBuffer: LogBuffer) { }
+    fun setLogBuffer(logBuffer: LogBuffer) {}
 }
 
 /** Interface for a specific clock face version rendered by the clock */
@@ -88,40 +88,37 @@
 /** Events that should call when various rendering parameters change */
 interface ClockEvents {
     /** Call every time tick */
-    fun onTimeTick() { }
+    fun onTimeTick() {}
 
     /** Call whenever timezone changes */
-    fun onTimeZoneChanged(timeZone: TimeZone) { }
+    fun onTimeZoneChanged(timeZone: TimeZone) {}
 
     /** Call whenever the text time format changes (12hr vs 24hr) */
-    fun onTimeFormatChanged(is24Hr: Boolean) { }
+    fun onTimeFormatChanged(is24Hr: Boolean) {}
 
     /** Call whenever the locale changes */
-    fun onLocaleChanged(locale: Locale) { }
-
-    /** Call whenever font settings change */
-    fun onFontSettingChanged() { }
+    fun onLocaleChanged(locale: Locale) {}
 
     /** Call whenever the color palette should update */
-    fun onColorPaletteChanged(resources: Resources) { }
+    fun onColorPaletteChanged(resources: Resources) {}
 }
 
 /** Methods which trigger various clock animations */
 interface ClockAnimations {
     /** Runs an enter animation (if any) */
-    fun enter() { }
+    fun enter() {}
 
     /** Sets how far into AOD the device currently is. */
-    fun doze(fraction: Float) { }
+    fun doze(fraction: Float) {}
 
     /** Sets how far into the folding animation the device is. */
-    fun fold(fraction: Float) { }
+    fun fold(fraction: Float) {}
 
     /** Runs the battery animation (if any). */
-    fun charge() { }
+    fun charge() {}
 
     /** Move the clock, for example, if the notification tray appears in split-shade mode. */
-    fun onPositionUpdated(fromRect: Rect, toRect: Rect, fraction: Float) { }
+    fun onPositionUpdated(fromRect: Rect, toRect: Rect, fraction: Float) {}
 
     /**
      * Whether this clock has a custom position update animation. If true, the keyguard will call
@@ -135,11 +132,26 @@
 /** Events that have specific data about the related face */
 interface ClockFaceEvents {
     /** Region Darkness specific to the clock face */
-    fun onRegionDarknessChanged(isDark: Boolean) { }
+    fun onRegionDarknessChanged(isDark: Boolean) {}
+
+    /**
+     * Call whenever font settings change. Pass in a target font size in pixels. The specific clock
+     * design is allowed to ignore this target size on a case-by-case basis.
+     */
+    fun onFontSettingChanged(fontSizePx: Float) {}
+
+    /**
+     * Target region information for the clock face. For small clock, this will match the bounds of
+     * the parent view mostly, but have a target height based on the height of the default clock.
+     * For large clocks, the parent view is the entire device size, but most clocks will want to
+     * render within the centered targetRect to avoid obstructing other elements. The specified
+     * targetRegion is relative to the parent view.
+     */
+    fun onTargetRegionChanged(targetRegion: Rect?) {}
 }
 
 /** Some data about a clock design */
 data class ClockMetadata(
     val clockId: ClockId,
-    val name: String
+    val name: String,
 )
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
index c297149..b49afee 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
@@ -37,7 +37,6 @@
         android:id="@+id/lockscreen_clock_view_large"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:layout_marginTop="@dimen/keyguard_large_clock_top_margin"
         android:clipChildren="false"
         android:visibility="gone" />
 
diff --git a/packages/SystemUI/res/drawable/accessibility_window_magnification_button_bg.xml b/packages/SystemUI/res/drawable/accessibility_window_magnification_button_bg.xml
new file mode 100644
index 0000000..eefe364
--- /dev/null
+++ b/packages/SystemUI/res/drawable/accessibility_window_magnification_button_bg.xml
@@ -0,0 +1,26 @@
+<!--

+    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.

+-->

+<shape xmlns:android="http://schemas.android.com/apk/res/android"

+    android:shape="oval">

+    <solid android:color="@color/accessibility_window_magnifier_button_bg" />

+    <size

+        android:width="40dp"

+        android:height="40dp"/>

+    <corners android:radius="2dp"/>

+    <stroke

+        android:color="@color/accessibility_window_magnifier_button_bg_stroke"

+        android:width="1dp" />

+ </shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_magnification_corner_bottom_left.xml b/packages/SystemUI/res/drawable/ic_magnification_corner_bottom_left.xml
new file mode 100644
index 0000000..d25cd65
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_magnification_corner_bottom_left.xml
@@ -0,0 +1,33 @@
+<!--
+    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="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M0,24l0,-4l24,-0l0,4z"
+      android:strokeWidth="1"
+      android:fillColor="@color/accessibility_window_magnifier_corner_view_color"
+      android:fillType="evenOdd"
+      android:strokeColor="#00000000"/>
+  <path
+      android:pathData="M0,24l0,-24l4,-0l0,24z"
+      android:strokeWidth="1"
+      android:fillColor="@color/accessibility_window_magnifier_corner_view_color"
+      android:fillType="evenOdd"
+      android:strokeColor="#00000000"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_magnification_corner_bottom_right.xml b/packages/SystemUI/res/drawable/ic_magnification_corner_bottom_right.xml
new file mode 100644
index 0000000..bb6df3a
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_magnification_corner_bottom_right.xml
@@ -0,0 +1,33 @@
+<!--
+    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="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M24,24l-4,-0l-0,-24l4,-0z"
+      android:strokeWidth="1"
+      android:fillColor="@color/accessibility_window_magnifier_corner_view_color"
+      android:fillType="evenOdd"
+      android:strokeColor="#00000000"/>
+  <path
+      android:pathData="M24,24l-24,-0l-0,-4l24,-0z"
+      android:strokeWidth="1"
+      android:fillColor="@color/accessibility_window_magnifier_corner_view_color"
+      android:fillType="evenOdd"
+      android:strokeColor="#00000000"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_magnification_corner_top_left.xml b/packages/SystemUI/res/drawable/ic_magnification_corner_top_left.xml
new file mode 100644
index 0000000..8f25930
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_magnification_corner_top_left.xml
@@ -0,0 +1,33 @@
+<!--
+    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="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M0,0h4v24h-4z"
+      android:strokeWidth="1"
+      android:fillColor="@color/accessibility_window_magnifier_corner_view_color"
+      android:fillType="evenOdd"
+      android:strokeColor="#00000000"/>
+  <path
+      android:pathData="M0,0h24v4h-24z"
+      android:strokeWidth="1"
+      android:fillColor="@color/accessibility_window_magnifier_corner_view_color"
+      android:fillType="evenOdd"
+      android:strokeColor="#00000000"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_magnification_corner_top_right.xml b/packages/SystemUI/res/drawable/ic_magnification_corner_top_right.xml
new file mode 100644
index 0000000..291db44
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_magnification_corner_top_right.xml
@@ -0,0 +1,33 @@
+<!--
+    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="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M24,0l-0,4l-24,0l-0,-4z"
+      android:strokeWidth="1"
+      android:fillColor="@color/accessibility_window_magnifier_corner_view_color"
+      android:fillType="evenOdd"
+      android:strokeColor="#00000000"/>
+  <path
+      android:pathData="M24,0l-0,24l-4,0l-0,-24z"
+      android:strokeWidth="1"
+      android:fillColor="@color/accessibility_window_magnifier_corner_view_color"
+      android:fillType="evenOdd"
+      android:strokeColor="#00000000"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/internet_dialog_selected_effect.xml b/packages/SystemUI/res/drawable/internet_dialog_selected_effect.xml
new file mode 100644
index 0000000..8f6b4c2
--- /dev/null
+++ b/packages/SystemUI/res/drawable/internet_dialog_selected_effect.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+        android:color="?android:attr/colorControlHighlight">
+    <item android:id="@android:id/mask">
+        <shape android:shape="rectangle">
+            <solid android:color="@android:color/white"/>
+            <corners android:radius="?android:attr/buttonCornerRadius"/>
+        </shape>
+    </item>
+</ripple>
diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
index ae2537f..f14be41 100644
--- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
+++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
@@ -312,22 +312,15 @@
 
             <LinearLayout
                 android:id="@+id/see_all_layout"
-                android:layout_width="match_parent"
+                style="@style/InternetDialog.Network"
                 android:layout_height="64dp"
-                android:clickable="true"
-                android:focusable="true"
-                android:background="?android:attr/selectableItemBackground"
-                android:gravity="center_vertical|center_horizontal"
-                android:orientation="horizontal"
-                android:paddingStart="22dp"
-                android:paddingEnd="22dp">
+                android:paddingStart="20dp">
 
                 <FrameLayout
                     android:layout_width="24dp"
                     android:layout_height="24dp"
                     android:clickable="false"
-                    android:layout_gravity="center_vertical|start"
-                    android:layout_marginStart="@dimen/internet_dialog_network_layout_margin">
+                    android:layout_gravity="center_vertical|start">
                     <ImageView
                         android:id="@+id/arrow_forward"
                         android:src="@drawable/ic_arrow_forward"
diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml
index 9b8b611..530db0d 100644
--- a/packages/SystemUI/res/layout/media_session_view.xml
+++ b/packages/SystemUI/res/layout/media_session_view.xml
@@ -44,7 +44,7 @@
         android:background="@drawable/qs_media_outline_album_bg"
         />
 
-    <com.android.systemui.ripple.MultiRippleView
+    <com.android.systemui.surfaceeffects.ripple.MultiRippleView
         android:id="@+id/touch_ripple_view"
         android:layout_width="match_parent"
         android:layout_height="@dimen/qs_media_session_height_expanded"
@@ -53,6 +53,15 @@
         app:layout_constraintTop_toTopOf="@id/album_art"
         app:layout_constraintBottom_toBottomOf="@id/album_art" />
 
+    <com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseView
+        android:id="@+id/turbulence_noise_view"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/qs_media_session_height_expanded"
+        app:layout_constraintStart_toStartOf="@id/album_art"
+        app:layout_constraintEnd_toEndOf="@id/album_art"
+        app:layout_constraintTop_toTopOf="@id/album_art"
+        app:layout_constraintBottom_toBottomOf="@id/album_art" />
+
     <!-- Guideline for output switcher -->
     <androidx.constraintlayout.widget.Guideline
         android:id="@+id/center_vertical_guideline"
diff --git a/packages/SystemUI/res/layout/window_magnifier_view.xml b/packages/SystemUI/res/layout/window_magnifier_view.xml
index 0bff47c..0be7328 100644
--- a/packages/SystemUI/res/layout/window_magnifier_view.xml
+++ b/packages/SystemUI/res/layout/window_magnifier_view.xml
@@ -40,19 +40,22 @@
             android:id="@+id/top_handle"
             android:layout_width="match_parent"
             android:layout_height="@dimen/magnification_border_drag_size"
-            android:layout_alignParentTop="true"/>
+            android:layout_alignParentTop="true"
+            android:accessibilityTraversalAfter="@id/left_handle"/>
 
         <View
             android:id="@+id/right_handle"
             android:layout_width="@dimen/magnification_border_drag_size"
             android:layout_height="match_parent"
-            android:layout_alignParentEnd="true"/>
+            android:layout_alignParentEnd="true"
+            android:accessibilityTraversalAfter="@id/top_handle"/>
 
         <View
             android:id="@+id/bottom_handle"
             android:layout_width="match_parent"
             android:layout_height="@dimen/magnification_border_drag_size"
-            android:layout_alignParentBottom="true"/>
+            android:layout_alignParentBottom="true"
+            android:accessibilityTraversalAfter="@id/right_handle"/>
 
         <SurfaceView
             android:id="@+id/surface_view"
@@ -62,6 +65,58 @@
     </RelativeLayout>
 
     <ImageView
+        android:id="@+id/top_right_corner"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_margin="@dimen/magnification_outer_border_margin"
+        android:layout_gravity="right|top"
+        android:paddingTop="@dimen/magnifier_drag_handle_padding"
+        android:paddingEnd="@dimen/magnifier_drag_handle_padding"
+        android:scaleType="center"
+        android:contentDescription="@string/magnification_drag_corner_to_resize"
+        android:src="@drawable/ic_magnification_corner_top_right"
+        android:accessibilityTraversalAfter="@id/top_left_corner"/>
+
+    <ImageView
+        android:id="@+id/top_left_corner"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_margin="@dimen/magnification_outer_border_margin"
+        android:layout_gravity="left|top"
+        android:paddingTop="@dimen/magnifier_drag_handle_padding"
+        android:paddingStart="@dimen/magnifier_drag_handle_padding"
+        android:scaleType="center"
+        android:contentDescription="@string/magnification_drag_corner_to_resize"
+        android:src="@drawable/ic_magnification_corner_top_left"
+        android:accessibilityTraversalAfter="@id/bottom_handle"/>
+
+    <ImageView
+        android:id="@+id/bottom_right_corner"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_margin="@dimen/magnification_outer_border_margin"
+        android:layout_gravity="right|bottom"
+        android:paddingEnd="@dimen/magnifier_drag_handle_padding"
+        android:paddingBottom="@dimen/magnifier_drag_handle_padding"
+        android:scaleType="center"
+        android:contentDescription="@string/magnification_drag_corner_to_resize"
+        android:src="@drawable/ic_magnification_corner_bottom_right"
+        android:accessibilityTraversalAfter="@id/top_right_corner"/>
+
+    <ImageView
+        android:id="@+id/bottom_left_corner"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_margin="@dimen/magnification_outer_border_margin"
+        android:layout_gravity="left|bottom"
+        android:paddingStart="@dimen/magnifier_drag_handle_padding"
+        android:paddingBottom="@dimen/magnifier_drag_handle_padding"
+        android:scaleType="center"
+        android:contentDescription="@string/magnification_drag_corner_to_resize"
+        android:src="@drawable/ic_magnification_corner_bottom_left"
+        android:accessibilityTraversalAfter="@id/bottom_right_corner"/>
+
+    <ImageView
         android:id="@+id/drag_handle"
         android:layout_width="@dimen/magnification_drag_view_size"
         android:layout_height="@dimen/magnification_drag_view_size"
diff --git a/packages/SystemUI/res/layout/wireless_charging_layout.xml b/packages/SystemUI/res/layout/wireless_charging_layout.xml
index 887e3e7..f1bc883 100644
--- a/packages/SystemUI/res/layout/wireless_charging_layout.xml
+++ b/packages/SystemUI/res/layout/wireless_charging_layout.xml
@@ -22,7 +22,7 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent">
 
-    <com.android.systemui.ripple.RippleView
+    <com.android.systemui.surfaceeffects.ripple.RippleView
         android:id="@+id/wireless_charging_ripple"
         android:layout_width="match_parent"
         android:layout_height="match_parent"/>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 55b59b6..6b4bea1 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -172,6 +172,10 @@
     <color name="accessibility_magnifier_bg">#FCFCFC</color>
     <color name="accessibility_magnifier_bg_stroke">#E0E0E0</color>
     <color name="accessibility_magnifier_icon_color">#252525</color>
+    <color name="accessibility_window_magnifier_button_bg">#0680FD</color>
+    <color name="accessibility_window_magnifier_icon_color">#FAFAFA</color>
+    <color name="accessibility_window_magnifier_button_bg_stroke">#252525</color>
+    <color name="accessibility_window_magnifier_corner_view_color">#0680FD</color>
 
     <!-- Volume dialog colors -->
     <color name="volume_dialog_background_color">@android:color/transparent</color>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 3270e45..80dff856 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2237,6 +2237,8 @@
     <string name="magnification_mode_switch_state_window">Magnify part of screen</string>
     <!-- Click action label for magnification switch. [CHAR LIMIT=NONE] -->
     <string name="magnification_mode_switch_click_label">Switch</string>
+    <!-- Label of the corner of a rectangle that you can tap and drag to resize the magnification area. [CHAR LIMIT=NONE] -->
+    <string name="magnification_drag_corner_to_resize">Drag corner to resize</string>
 
     <!-- Title of the magnification option button allow diagonal scrolling [CHAR LIMIT=NONE]-->
     <string name="accessibility_allow_diagonal_scrolling">Allow diagonal scrolling</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 4e4bfe2..ae80070 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -1091,7 +1091,7 @@
         <item name="android:orientation">horizontal</item>
         <item name="android:focusable">true</item>
         <item name="android:clickable">true</item>
-        <item name="android:background">?android:attr/selectableItemBackground</item>
+        <item name="android:background">@drawable/internet_dialog_selected_effect</item>
     </style>
 
     <style name="InternetDialog.NetworkTitle">
diff --git a/packages/SystemUI/res/xml/media_session_collapsed.xml b/packages/SystemUI/res/xml/media_session_collapsed.xml
index 148e5ec..1eb621e 100644
--- a/packages/SystemUI/res/xml/media_session_collapsed.xml
+++ b/packages/SystemUI/res/xml/media_session_collapsed.xml
@@ -44,6 +44,16 @@
         app:layout_constraintTop_toTopOf="@+id/album_art"
         app:layout_constraintBottom_toBottomOf="@+id/album_art" />
 
+    <!-- Turbulence noise must have the same constraint as the album art. -->
+    <Constraint
+        android:id="@+id/turbulence_noise_view"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/qs_media_session_height_collapsed"
+        app:layout_constraintStart_toStartOf="@+id/album_art"
+        app:layout_constraintEnd_toEndOf="@+id/album_art"
+        app:layout_constraintTop_toTopOf="@+id/album_art"
+        app:layout_constraintBottom_toBottomOf="@+id/album_art" />
+
     <Constraint
         android:id="@+id/header_title"
         android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/xml/media_session_expanded.xml b/packages/SystemUI/res/xml/media_session_expanded.xml
index ac484d7..64c2ef1 100644
--- a/packages/SystemUI/res/xml/media_session_expanded.xml
+++ b/packages/SystemUI/res/xml/media_session_expanded.xml
@@ -37,6 +37,16 @@
         app:layout_constraintTop_toTopOf="@+id/album_art"
         app:layout_constraintBottom_toBottomOf="@+id/album_art" />
 
+    <!-- Turbulence noise must have the same constraint as the album art. -->
+    <Constraint
+        android:id="@+id/turbulence_noise_view"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/qs_media_session_height_expanded"
+        app:layout_constraintStart_toStartOf="@+id/album_art"
+        app:layout_constraintEnd_toEndOf="@+id/album_art"
+        app:layout_constraintTop_toTopOf="@+id/album_art"
+        app:layout_constraintBottom_toBottomOf="@+id/album_art" />
+
     <Constraint
         android:id="@+id/header_title"
         android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/xml/qqs_header.xml b/packages/SystemUI/res/xml/qqs_header.xml
index e07a6c1..5d3650c 100644
--- a/packages/SystemUI/res/xml/qqs_header.xml
+++ b/packages/SystemUI/res/xml/qqs_header.xml
@@ -59,6 +59,7 @@
         <Layout
             android:layout_width="wrap_content"
             android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
+            app:layout_constrainedWidth="true"
             app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height"
             app:layout_constraintStart_toEndOf="@id/date"
             app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
index 36ac1ff..0b0595f 100644
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
@@ -34,7 +34,6 @@
 import platform.test.screenshot.DeviceEmulationRule
 import platform.test.screenshot.DeviceEmulationSpec
 import platform.test.screenshot.MaterialYouColorsRule
-import platform.test.screenshot.PathConfig
 import platform.test.screenshot.ScreenshotTestRule
 import platform.test.screenshot.getEmulatedDevicePathConfig
 import platform.test.screenshot.matchers.BitmapMatcher
@@ -42,19 +41,13 @@
 /** A rule for View screenshot diff unit tests. */
 class ViewScreenshotTestRule(
     emulationSpec: DeviceEmulationSpec,
-    private val matcher: BitmapMatcher = UnitTestBitmapMatcher,
-    pathConfig: PathConfig = getEmulatedDevicePathConfig(emulationSpec),
-    assetsPathRelativeToRepo: String = ""
+    private val matcher: BitmapMatcher = UnitTestBitmapMatcher
 ) : TestRule {
     private val colorsRule = MaterialYouColorsRule()
     private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
     private val screenshotRule =
         ScreenshotTestRule(
-            if (assetsPathRelativeToRepo.isBlank()) {
-                SystemUIGoldenImagePathManager(pathConfig)
-            } else {
-                SystemUIGoldenImagePathManager(pathConfig, assetsPathRelativeToRepo)
-            }
+            SystemUIGoldenImagePathManager(getEmulatedDevicePathConfig(emulationSpec))
         )
     private val activityRule = ActivityScenarioRule(ScreenshotActivity::class.java)
     private val delegateRule =
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index ca780c8..599cd23 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -20,6 +20,7 @@
 import android.icu.text.NumberFormat
 import android.util.TypedValue
 import android.view.LayoutInflater
+import android.view.View
 import android.widget.FrameLayout
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.plugins.ClockAnimations
@@ -80,7 +81,7 @@
     }
 
     override fun initialize(resources: Resources, dozeFraction: Float, foldFraction: Float) {
-        largeClock.recomputePadding()
+        largeClock.recomputePadding(null)
         animations = DefaultClockAnimations(dozeFraction, foldFraction)
         events.onColorPaletteChanged(resources)
         events.onTimeZoneChanged(TimeZone.getDefault())
@@ -101,6 +102,7 @@
         // MAGENTA is a placeholder, and will be assigned correctly in initialize
         private var currentColor = Color.MAGENTA
         private var isRegionDark = false
+        protected var targetRegion: Rect? = null
 
         init {
             view.setColors(currentColor, currentColor)
@@ -112,8 +114,20 @@
                     this@DefaultClockFaceController.isRegionDark = isRegionDark
                     updateColor()
                 }
+
+                override fun onTargetRegionChanged(targetRegion: Rect?) {
+                    this@DefaultClockFaceController.targetRegion = targetRegion
+                    recomputePadding(targetRegion)
+                }
+
+                override fun onFontSettingChanged(fontSizePx: Float) {
+                    view.setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSizePx)
+                    recomputePadding(targetRegion)
+                }
             }
 
+        open fun recomputePadding(targetRegion: Rect?) {}
+
         fun updateColor() {
             val color =
                 if (isRegionDark) {
@@ -135,9 +149,16 @@
     inner class LargeClockFaceController(
         view: AnimatableClockView,
     ) : DefaultClockFaceController(view) {
-        fun recomputePadding() {
+        override fun recomputePadding(targetRegion: Rect?) {
+            // We center the view within the targetRegion instead of within the parent
+            // view by computing the difference and adding that to the padding.
+            val parent = view.parent
+            val yDiff =
+                if (targetRegion != null && parent is View && parent.isLaidOut())
+                    targetRegion.centerY() - parent.height / 2f
+                else 0f
             val lp = view.getLayoutParams() as FrameLayout.LayoutParams
-            lp.topMargin = (-0.5f * view.bottom).toInt()
+            lp.topMargin = (-0.5f * view.bottom + yDiff).toInt()
             view.setLayoutParams(lp)
         }
 
@@ -155,18 +176,6 @@
         override fun onTimeZoneChanged(timeZone: TimeZone) =
             clocks.forEach { it.onTimeZoneChanged(timeZone) }
 
-        override fun onFontSettingChanged() {
-            smallClock.view.setTextSize(
-                TypedValue.COMPLEX_UNIT_PX,
-                resources.getDimensionPixelSize(R.dimen.small_clock_text_size).toFloat()
-            )
-            largeClock.view.setTextSize(
-                TypedValue.COMPLEX_UNIT_PX,
-                resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat()
-            )
-            largeClock.recomputePadding()
-        }
-
         override fun onColorPaletteChanged(resources: Resources) {
             largeClock.updateColor()
             smallClock.updateColor()
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderClient.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderClient.kt
deleted file mode 100644
index 8612b3a..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderClient.kt
+++ /dev/null
@@ -1,326 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.shared.keyguard.data.content
-
-import android.annotation.SuppressLint
-import android.content.ContentValues
-import android.content.Context
-import android.database.ContentObserver
-import android.graphics.drawable.Drawable
-import android.net.Uri
-import android.os.UserHandle
-import androidx.annotation.DrawableRes
-import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract as Contract
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.withContext
-
-/** Collection of utility functions for using a content provider implementing the [Contract]. */
-object KeyguardQuickAffordanceProviderClient {
-
-    /**
-     * Selects an affordance with the given ID for a slot on the lock screen with the given ID.
-     *
-     * Note that the maximum number of selected affordances on this slot is automatically enforced.
-     * Selecting a slot that is already full (e.g. already has a number of selected affordances at
-     * its maximum capacity) will automatically remove the oldest selected affordance before adding
-     * the one passed in this call. Additionally, selecting an affordance that's already one of the
-     * selected affordances on the slot will move the selected affordance to the newest location in
-     * the slot.
-     */
-    suspend fun insertSelection(
-        context: Context,
-        slotId: String,
-        affordanceId: String,
-        dispatcher: CoroutineDispatcher = Dispatchers.IO,
-    ) {
-        withContext(dispatcher) {
-            context.contentResolver.insert(
-                Contract.SelectionTable.URI,
-                ContentValues().apply {
-                    put(Contract.SelectionTable.Columns.SLOT_ID, slotId)
-                    put(Contract.SelectionTable.Columns.AFFORDANCE_ID, affordanceId)
-                }
-            )
-        }
-    }
-
-    /** Returns all available slots supported by the device. */
-    suspend fun querySlots(
-        context: Context,
-        dispatcher: CoroutineDispatcher = Dispatchers.IO,
-    ): List<Slot> {
-        return withContext(dispatcher) {
-            context.contentResolver
-                .query(
-                    Contract.SlotTable.URI,
-                    null,
-                    null,
-                    null,
-                    null,
-                )
-                ?.use { cursor ->
-                    buildList {
-                        val idColumnIndex = cursor.getColumnIndex(Contract.SlotTable.Columns.ID)
-                        val capacityColumnIndex =
-                            cursor.getColumnIndex(Contract.SlotTable.Columns.CAPACITY)
-                        if (idColumnIndex == -1 || capacityColumnIndex == -1) {
-                            return@buildList
-                        }
-
-                        while (cursor.moveToNext()) {
-                            add(
-                                Slot(
-                                    id = cursor.getString(idColumnIndex),
-                                    capacity = cursor.getInt(capacityColumnIndex),
-                                )
-                            )
-                        }
-                    }
-                }
-        }
-            ?: emptyList()
-    }
-
-    /**
-     * Returns [Flow] for observing the collection of slots.
-     *
-     * @see [querySlots]
-     */
-    fun observeSlots(
-        context: Context,
-        dispatcher: CoroutineDispatcher = Dispatchers.IO,
-    ): Flow<List<Slot>> {
-        return observeUri(
-                context,
-                Contract.SlotTable.URI,
-            )
-            .map { querySlots(context, dispatcher) }
-    }
-
-    /**
-     * Returns all available affordances supported by the device, regardless of current slot
-     * placement.
-     */
-    suspend fun queryAffordances(
-        context: Context,
-        dispatcher: CoroutineDispatcher = Dispatchers.IO,
-    ): List<Affordance> {
-        return withContext(dispatcher) {
-            context.contentResolver
-                .query(
-                    Contract.AffordanceTable.URI,
-                    null,
-                    null,
-                    null,
-                    null,
-                )
-                ?.use { cursor ->
-                    buildList {
-                        val idColumnIndex =
-                            cursor.getColumnIndex(Contract.AffordanceTable.Columns.ID)
-                        val nameColumnIndex =
-                            cursor.getColumnIndex(Contract.AffordanceTable.Columns.NAME)
-                        val iconColumnIndex =
-                            cursor.getColumnIndex(Contract.AffordanceTable.Columns.ICON)
-                        if (idColumnIndex == -1 || nameColumnIndex == -1 || iconColumnIndex == -1) {
-                            return@buildList
-                        }
-
-                        while (cursor.moveToNext()) {
-                            add(
-                                Affordance(
-                                    id = cursor.getString(idColumnIndex),
-                                    name = cursor.getString(nameColumnIndex),
-                                    iconResourceId = cursor.getInt(iconColumnIndex),
-                                )
-                            )
-                        }
-                    }
-                }
-        }
-            ?: emptyList()
-    }
-
-    /**
-     * Returns [Flow] for observing the collection of affordances.
-     *
-     * @see [queryAffordances]
-     */
-    fun observeAffordances(
-        context: Context,
-        dispatcher: CoroutineDispatcher = Dispatchers.IO,
-    ): Flow<List<Affordance>> {
-        return observeUri(
-                context,
-                Contract.AffordanceTable.URI,
-            )
-            .map { queryAffordances(context, dispatcher) }
-    }
-
-    /** Returns the current slot-affordance selections. */
-    suspend fun querySelections(
-        context: Context,
-        dispatcher: CoroutineDispatcher = Dispatchers.IO,
-    ): List<Selection> {
-        return withContext(dispatcher) {
-            context.contentResolver
-                .query(
-                    Contract.SelectionTable.URI,
-                    null,
-                    null,
-                    null,
-                    null,
-                )
-                ?.use { cursor ->
-                    buildList {
-                        val slotIdColumnIndex =
-                            cursor.getColumnIndex(Contract.SelectionTable.Columns.SLOT_ID)
-                        val affordanceIdColumnIndex =
-                            cursor.getColumnIndex(Contract.SelectionTable.Columns.AFFORDANCE_ID)
-                        if (slotIdColumnIndex == -1 || affordanceIdColumnIndex == -1) {
-                            return@buildList
-                        }
-
-                        while (cursor.moveToNext()) {
-                            add(
-                                Selection(
-                                    slotId = cursor.getString(slotIdColumnIndex),
-                                    affordanceId = cursor.getString(affordanceIdColumnIndex),
-                                )
-                            )
-                        }
-                    }
-                }
-        }
-            ?: emptyList()
-    }
-
-    /**
-     * Returns [Flow] for observing the collection of selections.
-     *
-     * @see [querySelections]
-     */
-    fun observeSelections(
-        context: Context,
-        dispatcher: CoroutineDispatcher = Dispatchers.IO,
-    ): Flow<List<Selection>> {
-        return observeUri(
-                context,
-                Contract.SelectionTable.URI,
-            )
-            .map { querySelections(context, dispatcher) }
-    }
-
-    /** Unselects an affordance with the given ID from the slot with the given ID. */
-    suspend fun deleteSelection(
-        context: Context,
-        slotId: String,
-        affordanceId: String,
-        dispatcher: CoroutineDispatcher = Dispatchers.IO,
-    ) {
-        withContext(dispatcher) {
-            context.contentResolver.delete(
-                Contract.SelectionTable.URI,
-                "${Contract.SelectionTable.Columns.SLOT_ID} = ? AND" +
-                    " ${Contract.SelectionTable.Columns.AFFORDANCE_ID} = ?",
-                arrayOf(
-                    slotId,
-                    affordanceId,
-                ),
-            )
-        }
-    }
-
-    /** Unselects all affordances from the slot with the given ID. */
-    suspend fun deleteAllSelections(
-        context: Context,
-        slotId: String,
-        dispatcher: CoroutineDispatcher = Dispatchers.IO,
-    ) {
-        withContext(dispatcher) {
-            context.contentResolver.delete(
-                Contract.SelectionTable.URI,
-                "${Contract.SelectionTable.Columns.SLOT_ID}",
-                arrayOf(
-                    slotId,
-                ),
-            )
-        }
-    }
-
-    private fun observeUri(
-        context: Context,
-        uri: Uri,
-    ): Flow<Unit> {
-        return callbackFlow {
-                val observer =
-                    object : ContentObserver(null) {
-                        override fun onChange(selfChange: Boolean) {
-                            trySend(Unit)
-                        }
-                    }
-
-                context.contentResolver.registerContentObserver(
-                    uri,
-                    /* notifyForDescendants= */ true,
-                    observer,
-                    UserHandle.USER_CURRENT,
-                )
-
-                awaitClose { context.contentResolver.unregisterContentObserver(observer) }
-            }
-            .onStart { emit(Unit) }
-    }
-
-    @SuppressLint("UseCompatLoadingForDrawables")
-    suspend fun getAffordanceIcon(
-        context: Context,
-        @DrawableRes iconResourceId: Int,
-        dispatcher: CoroutineDispatcher = Dispatchers.IO,
-    ): Drawable {
-        return withContext(dispatcher) {
-            context.packageManager
-                .getResourcesForApplication(SYSTEM_UI_PACKAGE_NAME)
-                .getDrawable(iconResourceId)
-        }
-    }
-
-    data class Slot(
-        val id: String,
-        val capacity: Int,
-    )
-
-    data class Affordance(
-        val id: String,
-        val name: String,
-        val iconResourceId: Int,
-    )
-
-    data class Selection(
-        val slotId: String,
-        val affordanceId: String,
-    )
-
-    private const val SYSTEM_UI_PACKAGE_NAME = "com.android.systemui"
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index c9b8712..87e9d56 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -26,6 +26,7 @@
 import androidx.annotation.VisibleForTesting
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.R
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
@@ -43,6 +44,11 @@
 import com.android.systemui.statusbar.policy.BatteryController
 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
 import com.android.systemui.statusbar.policy.ConfigurationController
+import java.io.PrintWriter
+import java.util.Locale
+import java.util.TimeZone
+import java.util.concurrent.Executor
+import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.DisposableHandle
 import kotlinx.coroutines.Job
@@ -50,11 +56,6 @@
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.launch
-import java.io.PrintWriter
-import java.util.Locale
-import java.util.TimeZone
-import java.util.concurrent.Executor
-import javax.inject.Inject
 
 /**
  * Controller for a Clock provided by the registry and used on the keyguard. Instantiated by
@@ -84,6 +85,7 @@
 
                 value.initialize(resources, dozeAmount, 0f)
                 updateRegionSamplers(value)
+                updateFontSizes()
             }
         }
 
@@ -150,7 +152,7 @@
             mainExecutor,
             bgExecutor,
             regionSamplingEnabled,
-            updateFun = { updateColors() } )
+            updateColors)
     }
 
     var smallRegionSampler: RegionSampler? = null
@@ -166,7 +168,7 @@
         }
 
         override fun onDensityOrFontScaleChanged() {
-            clock?.events?.onFontSettingChanged()
+            updateFontSizes()
         }
     }
 
@@ -251,6 +253,13 @@
         largeRegionSampler?.stopRegionSampler()
     }
 
+    private fun updateFontSizes() {
+        clock?.smallClock?.events?.onFontSettingChanged(
+            resources.getDimensionPixelSize(R.dimen.small_clock_text_size).toFloat())
+        clock?.largeClock?.events?.onFontSettingChanged(
+            resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat())
+    }
+
     /**
      * Dump information for debugging
      */
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 8ebad6c..40423cd 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -5,6 +5,7 @@
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.content.Context;
+import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
@@ -22,6 +23,7 @@
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+
 /**
  * Switch to show plugin clock when plugin is connected, otherwise it will show default clock.
  */
@@ -46,6 +48,7 @@
      */
     private FrameLayout mSmallClockFrame;
     private FrameLayout mLargeClockFrame;
+    private ClockController mClock;
 
     private View mStatusArea;
     private int mSmartspaceTopOffset;
@@ -95,6 +98,8 @@
     }
 
     void setClock(ClockController clock, int statusBarState) {
+        mClock = clock;
+
         // Disconnect from existing plugin.
         mSmallClockFrame.removeAllViews();
         mLargeClockFrame.removeAllViews();
@@ -108,6 +113,35 @@
         Log.i(TAG, "Attached new clock views to switch");
         mSmallClockFrame.addView(clock.getSmallClock().getView());
         mLargeClockFrame.addView(clock.getLargeClock().getView());
+        updateClockTargetRegions();
+    }
+
+    void updateClockTargetRegions() {
+        if (mClock != null) {
+            if (mSmallClockFrame.isLaidOut()) {
+                int targetHeight =  getResources()
+                        .getDimensionPixelSize(R.dimen.small_clock_text_size);
+                mClock.getSmallClock().getEvents().onTargetRegionChanged(new Rect(
+                        mSmallClockFrame.getLeft(),
+                        mSmallClockFrame.getTop(),
+                        mSmallClockFrame.getRight(),
+                        mSmallClockFrame.getTop() + targetHeight));
+            }
+
+            if (mLargeClockFrame.isLaidOut()) {
+                int largeClockTopMargin = getResources()
+                        .getDimensionPixelSize(R.dimen.keyguard_large_clock_top_margin);
+                int targetHeight = getResources()
+                        .getDimensionPixelSize(R.dimen.large_clock_text_size) * 2;
+                int top = mLargeClockFrame.getHeight() / 2 - targetHeight / 2
+                        + largeClockTopMargin / 2;
+                mClock.getLargeClock().getEvents().onTargetRegionChanged(new Rect(
+                        mLargeClockFrame.getLeft(),
+                        top,
+                        mLargeClockFrame.getRight(),
+                        top + targetHeight));
+            }
+        }
     }
 
     private void updateClockViews(boolean useLargeClock, boolean animate) {
@@ -214,6 +248,10 @@
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         super.onLayout(changed, l, t, r, b);
 
+        if (changed) {
+            post(() -> updateClockTargetRegions());
+        }
+
         if (mDisplayedClockSize != null && !mChildrenAreLaidOut) {
             post(() -> updateClockViews(mDisplayedClockSize == LARGE, mAnimateOnLayout));
         }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index ace942d..e6aae9b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -77,7 +77,7 @@
     @KeyguardClockSwitch.ClockSize
     private int mCurrentClockSize = SMALL;
 
-    private int mKeyguardClockTopMargin = 0;
+    private int mKeyguardSmallClockTopMargin = 0;
     private final ClockRegistry.ClockChangeListener mClockChangedListener;
 
     private ViewGroup mStatusArea;
@@ -162,7 +162,7 @@
         mClockRegistry.registerClockChangeListener(mClockChangedListener);
         setClock(mClockRegistry.createCurrentClock());
         mClockEventController.registerListeners(mView);
-        mKeyguardClockTopMargin =
+        mKeyguardSmallClockTopMargin =
                 mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
 
         if (mOnlyClock) {
@@ -244,10 +244,12 @@
      */
     public void onDensityOrFontScaleChanged() {
         mView.onDensityOrFontScaleChanged();
-        mKeyguardClockTopMargin =
+        mKeyguardSmallClockTopMargin =
                 mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
+        mView.updateClockTargetRegions();
     }
 
+
     /**
      * Set which clock should be displayed on the keyguard. The other one will be automatically
      * hidden.
@@ -327,7 +329,7 @@
             return frameHeight / 2 + clockHeight / 2;
         } else {
             int clockHeight = clock.getSmallClock().getView().getHeight();
-            return clockHeight + statusBarHeaderHeight + mKeyguardClockTopMargin;
+            return clockHeight + statusBarHeaderHeight + mKeyguardSmallClockTopMargin;
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index a6e767c..ec15d1a 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -158,6 +158,10 @@
     private View mTopDrag;
     private View mRightDrag;
     private View mBottomDrag;
+    private ImageView mTopLeftCornerView;
+    private ImageView mTopRightCornerView;
+    private ImageView mBottomLeftCornerView;
+    private ImageView mBottomRightCornerView;
     private final Configuration mConfiguration;
 
     @NonNull
@@ -357,13 +361,15 @@
         return false;
     }
 
-    private void changeMagnificationSize(@MagnificationSize int index) {
+    @VisibleForTesting
+    void changeMagnificationSize(@MagnificationSize int index) {
         final int initSize = Math.min(mWindowBounds.width(), mWindowBounds.height()) / 3;
         int size = (int) (initSize * MAGNIFICATION_SCALE_OPTIONS[index]);
         setWindowSize(size, size);
     }
 
-    private void setEditMagnifierSizeMode(boolean enable) {
+    @VisibleForTesting
+    void setEditMagnifierSizeMode(boolean enable) {
         mEditSizeEnable = enable;
         applyResourcesValues();
 
@@ -639,10 +645,37 @@
         Region regionInsideDragBorder = new Region(mBorderDragSize, mBorderDragSize,
                 mMirrorView.getWidth() - mBorderDragSize,
                 mMirrorView.getHeight() - mBorderDragSize);
+
+        Region tapExcludeRegion = new Region();
+
         Rect dragArea = new Rect();
         mDragView.getHitRect(dragArea);
 
-        regionInsideDragBorder.op(dragArea, Region.Op.DIFFERENCE);
+        Rect topLeftArea = new Rect();
+        mTopLeftCornerView.getHitRect(topLeftArea);
+
+        Rect topRightArea = new Rect();
+        mTopRightCornerView.getHitRect(topRightArea);
+
+        Rect bottomLeftArea = new Rect();
+        mBottomLeftCornerView.getHitRect(bottomLeftArea);
+
+        Rect bottomRightArea = new Rect();
+        mBottomRightCornerView.getHitRect(bottomRightArea);
+
+        Rect closeArea = new Rect();
+        mCloseView.getHitRect(closeArea);
+
+        // add tapExcludeRegion for Drag or close
+        tapExcludeRegion.op(dragArea, Region.Op.UNION);
+        tapExcludeRegion.op(topLeftArea, Region.Op.UNION);
+        tapExcludeRegion.op(topRightArea, Region.Op.UNION);
+        tapExcludeRegion.op(bottomLeftArea, Region.Op.UNION);
+        tapExcludeRegion.op(bottomRightArea, Region.Op.UNION);
+        tapExcludeRegion.op(closeArea, Region.Op.UNION);
+
+        regionInsideDragBorder.op(tapExcludeRegion, Region.Op.DIFFERENCE);
+
         return regionInsideDragBorder;
     }
 
@@ -756,6 +789,10 @@
         mRightDrag = mMirrorView.findViewById(R.id.right_handle);
         mBottomDrag = mMirrorView.findViewById(R.id.bottom_handle);
         mCloseView = mMirrorView.findViewById(R.id.close_button);
+        mTopRightCornerView = mMirrorView.findViewById(R.id.top_right_corner);
+        mTopLeftCornerView = mMirrorView.findViewById(R.id.top_left_corner);
+        mBottomRightCornerView = mMirrorView.findViewById(R.id.bottom_right_corner);
+        mBottomLeftCornerView = mMirrorView.findViewById(R.id.bottom_left_corner);
 
         mDragView.setOnTouchListener(this);
         mLeftDrag.setOnTouchListener(this);
@@ -763,6 +800,10 @@
         mRightDrag.setOnTouchListener(this);
         mBottomDrag.setOnTouchListener(this);
         mCloseView.setOnTouchListener(this);
+        mTopLeftCornerView.setOnTouchListener(this);
+        mTopRightCornerView.setOnTouchListener(this);
+        mBottomLeftCornerView.setOnTouchListener(this);
+        mBottomRightCornerView.setOnTouchListener(this);
     }
 
     /**
@@ -831,8 +872,16 @@
 
     @Override
     public boolean onTouch(View v, MotionEvent event) {
-        if (v == mDragView || v == mLeftDrag || v == mTopDrag || v == mRightDrag
-                || v == mBottomDrag || v == mCloseView) {
+        if (v == mDragView
+                || v == mLeftDrag
+                || v == mTopDrag
+                || v == mRightDrag
+                || v == mBottomDrag
+                || v == mTopLeftCornerView
+                || v == mTopRightCornerView
+                || v == mBottomLeftCornerView
+                || v == mBottomRightCornerView
+                || v == mCloseView) {
             return mGestureDetector.onTouch(v, event);
         }
         return false;
@@ -1195,7 +1244,7 @@
     @Override
     public boolean onDrag(View view, float offsetX, float offsetY) {
         if (mEditSizeEnable) {
-            changeWindowSize(view, offsetX, offsetY);
+            return changeWindowSize(view, offsetX, offsetY);
         } else {
             move((int) offsetX, (int) offsetY);
         }
@@ -1220,13 +1269,47 @@
         if (mEditSizeEnable) {
             mDragView.setVisibility(View.GONE);
             mCloseView.setVisibility(View.VISIBLE);
+            mTopRightCornerView.setVisibility(View.VISIBLE);
+            mTopLeftCornerView.setVisibility(View.VISIBLE);
+            mBottomRightCornerView.setVisibility(View.VISIBLE);
+            mBottomLeftCornerView.setVisibility(View.VISIBLE);
         } else {
             mDragView.setVisibility(View.VISIBLE);
             mCloseView.setVisibility(View.GONE);
+            mTopRightCornerView.setVisibility(View.GONE);
+            mTopLeftCornerView.setVisibility(View.GONE);
+            mBottomRightCornerView.setVisibility(View.GONE);
+            mBottomLeftCornerView.setVisibility(View.GONE);
         }
     }
 
-    public boolean changeWindowSize(View view, float offsetX, float offsetY) {
+    private boolean changeWindowSize(View view, float offsetX, float offsetY) {
+        if (view == mLeftDrag) {
+            changeMagnificationFrameSize(offsetX, 0, 0, 0);
+        } else if (view == mRightDrag) {
+            changeMagnificationFrameSize(0, 0, offsetX, 0);
+        } else if (view == mTopDrag) {
+            changeMagnificationFrameSize(0, offsetY, 0, 0);
+        } else if (view == mBottomDrag) {
+            changeMagnificationFrameSize(0, 0, 0, offsetY);
+        } else if (view == mTopLeftCornerView) {
+            changeMagnificationFrameSize(offsetX, offsetY, 0, 0);
+        } else if (view == mTopRightCornerView) {
+            changeMagnificationFrameSize(0, offsetY, offsetX, 0);
+        } else if (view == mBottomLeftCornerView) {
+            changeMagnificationFrameSize(offsetX, 0, 0, offsetY);
+        } else if (view == mBottomRightCornerView) {
+            changeMagnificationFrameSize(0, 0, offsetX, offsetY);
+        } else {
+            return false;
+        }
+
+        return true;
+    }
+
+    private void changeMagnificationFrameSize(
+            float leftOffset, float topOffset, float rightOffset,
+            float bottomOffset) {
         boolean bRTL = isRTL(mContext);
         final int initSize = Math.min(mWindowBounds.width(), mWindowBounds.height()) / 3;
 
@@ -1236,54 +1319,26 @@
         Rect tempRect = new Rect();
         tempRect.set(mMagnificationFrame);
 
-        if (view == mLeftDrag) {
-            if (bRTL) {
-                tempRect.right += offsetX;
-                if (tempRect.right > mWindowBounds.width()) {
-                    return false;
-                }
-            } else {
-                tempRect.left += offsetX;
-                if (tempRect.left < 0) {
-                    return false;
-                }
-            }
-        } else if (view == mRightDrag) {
-            if (bRTL) {
-                tempRect.left += offsetX;
-                if (tempRect.left < 0) {
-                    return false;
-                }
-            } else {
-                tempRect.right += offsetX;
-                if (tempRect.right > mWindowBounds.width()) {
-                    return false;
-                }
-            }
-        } else if (view == mTopDrag) {
-            tempRect.top += offsetY;
-            if (tempRect.top < 0) {
-                return false;
-            }
-        } else if (view == mBottomDrag) {
-            tempRect.bottom += offsetY;
-            if (tempRect.bottom > mWindowBounds.height()) {
-                return false;
-            }
+        if (bRTL) {
+            tempRect.left += (int) (rightOffset);
+            tempRect.right += (int) (leftOffset);
+        } else {
+            tempRect.right += (int) (rightOffset);
+            tempRect.left += (int) (leftOffset);
         }
+        tempRect.top += (int) (topOffset);
+        tempRect.bottom += (int) (bottomOffset);
 
         if (tempRect.width() < initSize || tempRect.height() < initSize
                 || tempRect.width() > maxWidthSize || tempRect.height() > maxHeightSize) {
-            return false;
+            return;
         }
-
         mMagnificationFrame.set(tempRect);
 
         computeBounceAnimationScale();
         calculateMagnificationFrameBoundary();
 
         modifyWindowMagnification(true);
-        return true;
     }
 
     private static boolean isRTL(Context context) {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
index 9cffd5d..069c0f6 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
@@ -103,7 +103,7 @@
             MagnificationSize.LARGE,
     })
     /** Denotes the Magnification size type. */
-    @interface MagnificationSize {
+    public @interface MagnificationSize {
         int NONE = 0;
         int SMALL  = 1;
         int MEDIUM = 2;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index 33e155d..b8f14ae 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -16,14 +16,19 @@
 
 package com.android.systemui.accessibility.floatingmenu;
 
+import static com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType.INVISIBLE_TOGGLE;
+import static com.android.internal.accessibility.util.AccessibilityUtils.getAccessibilityServiceFragmentType;
+import static com.android.internal.accessibility.util.AccessibilityUtils.setAccessibilityServiceState;
 import static com.android.systemui.accessibility.floatingmenu.MenuMessageView.Index;
 
+import android.accessibilityservice.AccessibilityServiceInfo;
 import android.annotation.IntDef;
 import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.PluralsMessageFormatter;
 import android.view.MotionEvent;
@@ -82,8 +87,22 @@
     final Runnable mDismissMenuAction = new Runnable() {
         @Override
         public void run() {
-            Settings.Secure.putString(getContext().getContentResolver(),
-                    Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, /* value= */ "");
+            Settings.Secure.putStringForUser(getContext().getContentResolver(),
+                    Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, /* value= */ "",
+                    UserHandle.USER_CURRENT);
+
+            // Should disable the corresponding service when the fragment type is
+            // INVISIBLE_TOGGLE, which will enable service when the shortcut is on.
+            final List<AccessibilityServiceInfo> serviceInfoList =
+                    mAccessibilityManager.getEnabledAccessibilityServiceList(
+                            AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
+            serviceInfoList.forEach(info -> {
+                if (getAccessibilityServiceFragmentType(info) == INVISIBLE_TOGGLE) {
+                    setAccessibilityServiceState(mContext, info.getComponentName(), /* enabled= */
+                            false);
+                }
+            });
+
             mFloatingMenu.hide();
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
index c93fe6a..4b57d45 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
@@ -29,7 +29,7 @@
 import android.view.animation.PathInterpolator
 import com.android.internal.graphics.ColorUtils
 import com.android.systemui.animation.Interpolators
-import com.android.systemui.ripple.RippleShader
+import com.android.systemui.surfaceeffects.ripple.RippleShader
 
 private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.4f
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/TEST_MAPPING b/packages/SystemUI/src/com/android/systemui/biometrics/TEST_MAPPING
deleted file mode 100644
index 794eba4..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/TEST_MAPPING
+++ /dev/null
@@ -1,16 +0,0 @@
-{
-  "presubmit": [
-    {
-      // TODO(b/251476085): Consider merging with SystemUIGoogleScreenshotTests (in U+)
-      "name": "SystemUIGoogleBiometricsScreenshotTests",
-      "options": [
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
-    }
-  ]
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt
index 7dde947..5110a9c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt
@@ -6,6 +6,8 @@
 import android.view.inputmethod.InputMethodManager
 import android.widget.ImeAwareEditText
 import android.widget.TextView
+import android.window.OnBackInvokedCallback
+import android.window.OnBackInvokedDispatcher
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.systemui.R
@@ -13,6 +15,7 @@
 import com.android.systemui.biometrics.ui.CredentialView
 import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.launch
 
 /** Sub-binder for the [CredentialPasswordView]. */
@@ -23,17 +26,16 @@
         view: CredentialPasswordView,
         host: CredentialView.Host,
         viewModel: CredentialViewModel,
-        requestFocusForInput: Boolean,
     ) {
         val imeManager = view.context.getSystemService(InputMethodManager::class.java)!!
 
         val passwordField: ImeAwareEditText = view.requireViewById(R.id.lockPassword)
 
+        val onBackInvokedCallback = OnBackInvokedCallback { host.onCredentialAborted() }
+
         view.repeatWhenAttached {
-            if (requestFocusForInput) {
-                passwordField.requestFocus()
-                passwordField.scheduleShowSoftInput()
-            }
+            passwordField.requestFocus()
+            passwordField.scheduleShowSoftInput()
 
             repeatOnLifecycle(Lifecycle.State.STARTED) {
                 // observe credential validation attempts and submit/cancel buttons
@@ -45,9 +47,7 @@
                                 launch { viewModel.checkCredential(text, header) }
                             }
                         )
-                        passwordField.setOnKeyListener(
-                            OnBackButtonListener { host.onCredentialAborted() }
-                        )
+                        passwordField.setOnKeyListener(OnBackButtonListener(onBackInvokedCallback))
                     }
                 }
 
@@ -68,18 +68,35 @@
                         }
                     }
                 }
+
+                val onBackInvokedDispatcher = view.findOnBackInvokedDispatcher()
+                if (onBackInvokedDispatcher != null) {
+                    launch {
+                            onBackInvokedDispatcher.registerOnBackInvokedCallback(
+                                OnBackInvokedDispatcher.PRIORITY_DEFAULT,
+                                onBackInvokedCallback
+                            )
+                            awaitCancellation()
+                        }
+                        .invokeOnCompletion {
+                            onBackInvokedDispatcher.unregisterOnBackInvokedCallback(
+                                onBackInvokedCallback
+                            )
+                        }
+                }
             }
         }
     }
 }
 
-private class OnBackButtonListener(private val onBack: () -> Unit) : View.OnKeyListener {
+private class OnBackButtonListener(private val onBackInvokedCallback: OnBackInvokedCallback) :
+    View.OnKeyListener {
     override fun onKey(v: View, keyCode: Int, event: KeyEvent): Boolean {
         if (keyCode != KeyEvent.KEYCODE_BACK) {
             return false
         }
         if (event.action == KeyEvent.ACTION_UP) {
-            onBack()
+            onBackInvokedCallback.onBackInvoked()
         }
         return true
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPatternViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPatternViewBinder.kt
index b692ad3..4765551 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPatternViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPatternViewBinder.kt
@@ -9,6 +9,7 @@
 import com.android.systemui.biometrics.ui.CredentialView
 import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.launch
 
 /** Sub-binder for the [CredentialPatternView]. */
@@ -29,7 +30,7 @@
                     viewModel.header.collect { header ->
                         lockPatternView.setOnPatternListener(
                             OnPatternDetectedListener { pattern ->
-                                if (pattern.isPatternTooShort()) {
+                                if (pattern.isPatternLongEnough()) {
                                     // Pattern size is less than the minimum
                                     // do not count it as a failed attempt
                                     viewModel.showPatternTooShortError()
@@ -70,5 +71,5 @@
     }
 }
 
-private fun List<LockPatternView.Cell>.isPatternTooShort(): Boolean =
+private fun List<LockPatternView.Cell>.isPatternLongEnough(): Boolean =
     size < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
index e2d36dc..fcc9487 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
@@ -17,6 +17,7 @@
 import com.android.systemui.lifecycle.repeatWhenAttached
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.launch
@@ -39,7 +40,6 @@
         panelViewController: AuthPanelController,
         animatePanel: Boolean,
         maxErrorDuration: Long = 3_000L,
-        requestFocusForInput: Boolean = true,
     ) {
         val titleView: TextView = view.requireViewById(R.id.title)
         val subtitleView: TextView = view.requireViewById(R.id.subtitle)
@@ -110,8 +110,7 @@
 
         // bind the auth widget
         when (view) {
-            is CredentialPasswordView ->
-                CredentialPasswordViewBinder.bind(view, host, viewModel, requestFocusForInput)
+            is CredentialPasswordView -> CredentialPasswordViewBinder.bind(view, host, viewModel)
             is CredentialPatternView -> CredentialPatternViewBinder.bind(view, host, viewModel)
             else -> throw IllegalStateException("unexpected view type: ${view.javaClass.name}")
         }
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
index 616e49c..1454210 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
@@ -31,7 +31,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
-import com.android.systemui.ripple.RippleView
+import com.android.systemui.surfaceeffects.ripple.RippleView
 import com.android.systemui.statusbar.commandline.Command
 import com.android.systemui.statusbar.commandline.CommandRegistry
 import com.android.systemui.statusbar.policy.BatteryController
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
index e82d0ea..3808ab7 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
@@ -30,7 +30,7 @@
 
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.ripple.RippleShader.RippleShape;
+import com.android.systemui.surfaceeffects.ripple.RippleShader.RippleShape;
 
 /**
  * A WirelessChargingAnimation is a view containing view + animation for wireless charging.
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
index 1455699..36103f8 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
@@ -33,9 +33,9 @@
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
-import com.android.systemui.ripple.RippleAnimationConfig;
-import com.android.systemui.ripple.RippleShader.RippleShape;
-import com.android.systemui.ripple.RippleView;
+import com.android.systemui.surfaceeffects.ripple.RippleAnimationConfig;
+import com.android.systemui.surfaceeffects.ripple.RippleShader.RippleShape;
+import com.android.systemui.surfaceeffects.ripple.RippleView;
 
 import java.text.NumberFormat;
 
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
index beaccba..e8e1f2e 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
@@ -231,7 +231,8 @@
 
         // check for false tap if it is a seekbar interaction
         if (interactionType == MEDIA_SEEKBAR) {
-            localResult[0] &= isFalseTap(LOW_PENALTY);
+            localResult[0] &= isFalseTap(mFeatureFlags.isEnabled(Flags.MEDIA_FALSING_PENALTY)
+                    ? FalsingManager.MODERATE_PENALTY : FalsingManager.LOW_PENALTY);
         }
 
         logDebug("False Gesture (type: " + interactionType + "): " + localResult[0]);
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 25418c3..0664e9f 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -17,6 +17,7 @@
 package com.android.systemui.dagger;
 
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.app.AlarmManager;
@@ -69,6 +70,7 @@
 import android.os.ServiceManager;
 import android.os.UserManager;
 import android.os.Vibrator;
+import android.os.storage.StorageManager;
 import android.permission.PermissionManager;
 import android.safetycenter.SafetyCenterManager;
 import android.service.dreams.DreamService;
@@ -110,6 +112,7 @@
 /**
  * Provides Non-SystemUI, Framework-Owned instances to the dependency graph.
  */
+@SuppressLint("NonInjectedService")
 @Module
 public class FrameworkServicesModule {
     @Provides
@@ -469,7 +472,13 @@
 
     @Provides
     @Singleton
-    static SubscriptionManager provideSubcriptionManager(Context context) {
+    static StorageManager provideStorageManager(Context context) {
+        return context.getSystemService(StorageManager.class);
+    }
+
+    @Provides
+    @Singleton
+    static SubscriptionManager provideSubscriptionManager(Context context) {
         return context.getSystemService(SubscriptionManager.class);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 076e705..2dbbb1b 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -79,6 +79,11 @@
     val NOTIFICATION_GROUP_CORNER =
         unreleasedFlag(116, "notification_group_corner", teamfood = true)
 
+    // TODO(b/259217907)
+    @JvmField
+    val NOTIFICATION_GROUP_DISMISSAL_ANIMATION =
+        unreleasedFlag(259217907, "notification_group_dismissal_animation", teamfood = true)
+
     // TODO(b/257506350): Tracking Bug
     val FSI_CHROME = unreleasedFlag(117, "fsi_chrome")
 
@@ -115,28 +120,6 @@
     @JvmField val MODERN_BOUNCER = releasedFlag(208, "modern_bouncer")
 
     /**
-     * Whether the user interactor and repository should use `UserSwitcherController`.
-     *
-     * If this is `false`, the interactor and repo skip the controller and directly access the
-     * framework APIs.
-     */
-    // TODO(b/254513286): Tracking Bug
-    val USER_INTERACTOR_AND_REPO_USE_CONTROLLER =
-        unreleasedFlag(210, "user_interactor_and_repo_use_controller")
-
-    /**
-     * Whether `UserSwitcherController` should use the user interactor.
-     *
-     * When this is `true`, the controller does not directly access framework APIs. Instead, it goes
-     * through the interactor.
-     *
-     * Note: do not set this to true if [.USER_INTERACTOR_AND_REPO_USE_CONTROLLER] is `true` as it
-     * would created a cycle between controller -> interactor -> controller.
-     */
-    // TODO(b/254513102): Tracking Bug
-    val USER_CONTROLLER_USES_INTERACTOR = releasedFlag(211, "user_controller_uses_interactor")
-
-    /**
      * Whether the clock on a wide lock screen should use the new "stepping" animation for moving
      * the digits when the clock moves.
      */
@@ -210,9 +193,7 @@
     @JvmField val NEW_FOOTER_ACTIONS = releasedFlag(507, "new_footer_actions")
 
     // TODO(b/244064524): Tracking Bug
-    @JvmField
-    val QS_SECONDARY_DATA_SUB_INFO =
-        unreleasedFlag(508, "qs_secondary_data_sub_info", teamfood = true)
+    @JvmField val QS_SECONDARY_DATA_SUB_INFO = releasedFlag(508, "qs_secondary_data_sub_info")
 
     // 600- status bar
     // TODO(b/254513246): Tracking Bug
@@ -297,6 +278,8 @@
     // TODO(b/254513168): Tracking Bug
     @JvmField val UMO_SURFACE_RIPPLE = unreleasedFlag(907, "umo_surface_ripple")
 
+    @JvmField val MEDIA_FALSING_PENALTY = unreleasedFlag(908, "media_falsing_media")
+
     // 1000 - dock
     val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging")
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt
index a7f1b95..a8f39fa9a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt
@@ -26,7 +26,8 @@
 import androidx.constraintlayout.widget.Barrier
 import com.android.systemui.R
 import com.android.systemui.media.controls.models.GutsViewHolder
-import com.android.systemui.ripple.MultiRippleView
+import com.android.systemui.surfaceeffects.ripple.MultiRippleView
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseView
 import com.android.systemui.util.animation.TransitionLayout
 
 private const val TAG = "MediaViewHolder"
@@ -38,6 +39,8 @@
     // Player information
     val albumView = itemView.requireViewById<ImageView>(R.id.album_art)
     val multiRippleView = itemView.requireViewById<MultiRippleView>(R.id.touch_ripple_view)
+    val turbulenceNoiseView =
+        itemView.requireViewById<TurbulenceNoiseView>(R.id.turbulence_noise_view)
     val appIcon = itemView.requireViewById<ImageView>(R.id.icon)
     val titleText = itemView.requireViewById<TextView>(R.id.header_title)
     val artistText = itemView.requireViewById<TextView>(R.id.header_artist)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt
index 918417f..93be6a7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt
@@ -29,7 +29,8 @@
 import com.android.settingslib.Utils
 import com.android.systemui.media.controls.models.player.MediaViewHolder
 import com.android.systemui.monet.ColorScheme
-import com.android.systemui.ripple.MultiRippleController
+import com.android.systemui.surfaceeffects.ripple.MultiRippleController
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseController
 
 /**
  * A [ColorTransition] is an object that updates the colors of views each time [updateColorScheme]
@@ -102,13 +103,21 @@
     private val context: Context,
     private val mediaViewHolder: MediaViewHolder,
     private val multiRippleController: MultiRippleController,
+    private val turbulenceNoiseController: TurbulenceNoiseController,
     animatingColorTransitionFactory: AnimatingColorTransitionFactory
 ) {
     constructor(
         context: Context,
         mediaViewHolder: MediaViewHolder,
         multiRippleController: MultiRippleController,
-    ) : this(context, mediaViewHolder, multiRippleController, ::AnimatingColorTransition)
+        turbulenceNoiseController: TurbulenceNoiseController
+    ) : this(
+        context,
+        mediaViewHolder,
+        multiRippleController,
+        turbulenceNoiseController,
+        ::AnimatingColorTransition
+    )
 
     val bgColor = context.getColor(com.android.systemui.R.color.material_dynamic_secondary95)
     val surfaceColor =
@@ -129,6 +138,7 @@
             mediaViewHolder.actionPlayPause.backgroundTintList = accentColorList
             mediaViewHolder.gutsViewHolder.setAccentPrimaryColor(accentPrimary)
             multiRippleController.updateColor(accentPrimary)
+            turbulenceNoiseController.updateNoiseColor(accentPrimary)
         }
 
     val accentSecondary =
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 215fa03..21e64e2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -31,6 +31,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.res.ColorStateList;
+import android.graphics.BlendMode;
 import android.graphics.Color;
 import android.graphics.ColorMatrix;
 import android.graphics.ColorMatrixColorFilter;
@@ -64,6 +65,7 @@
 import androidx.constraintlayout.widget.ConstraintSet;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.graphics.ColorUtils;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.InstanceId;
 import com.android.settingslib.widget.AdaptiveIcon;
@@ -97,13 +99,16 @@
 import com.android.systemui.monet.Style;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.ripple.MultiRippleController;
-import com.android.systemui.ripple.RippleAnimation;
-import com.android.systemui.ripple.RippleAnimationConfig;
-import com.android.systemui.ripple.RippleShader;
 import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.surfaceeffects.ripple.MultiRippleController;
+import com.android.systemui.surfaceeffects.ripple.MultiRippleView;
+import com.android.systemui.surfaceeffects.ripple.RippleAnimation;
+import com.android.systemui.surfaceeffects.ripple.RippleAnimationConfig;
+import com.android.systemui.surfaceeffects.ripple.RippleShader;
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseAnimationConfig;
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseController;
 import com.android.systemui.util.ColorUtilKt;
 import com.android.systemui.util.animation.TransitionLayout;
 import com.android.systemui.util.time.SystemClock;
@@ -216,7 +221,9 @@
     private boolean mShowBroadcastDialogButton = false;
     private String mSwitchBroadcastApp;
     private MultiRippleController mMultiRippleController;
+    private TurbulenceNoiseController mTurbulenceNoiseController;
     private FeatureFlags mFeatureFlags;
+    private TurbulenceNoiseAnimationConfig mTurbulenceNoiseAnimationConfig = null;
 
     /**
      * Initialize a new control panel
@@ -394,9 +401,20 @@
         AnimatorSet exit = loadAnimator(R.anim.media_metadata_exit,
                 Interpolators.EMPHASIZED_ACCELERATE, titleText, artistText);
 
-        mMultiRippleController = new MultiRippleController(vh.getMultiRippleView());
+        MultiRippleView multiRippleView = vh.getMultiRippleView();
+        mMultiRippleController = new MultiRippleController(multiRippleView);
+        mTurbulenceNoiseController = new TurbulenceNoiseController(vh.getTurbulenceNoiseView());
+        multiRippleView.addRipplesFinishedListener(
+                () -> {
+                    if (mTurbulenceNoiseAnimationConfig == null) {
+                        mTurbulenceNoiseAnimationConfig = createLingeringNoiseAnimation();
+                    }
+                    // Color will be correctly updated in ColorSchemeTransition.
+                    mTurbulenceNoiseController.play(mTurbulenceNoiseAnimationConfig);
+                }
+        );
         mColorSchemeTransition = new ColorSchemeTransition(
-                mContext, mMediaViewHolder, mMultiRippleController);
+                mContext, mMediaViewHolder, mMultiRippleController, mTurbulenceNoiseController);
         mMetadataAnimationHandler = new MetadataAnimationHandler(exit, enter);
     }
 
@@ -571,7 +589,10 @@
         seamlessView.setContentDescription(deviceString);
         seamlessView.setOnClickListener(
                 v -> {
-                    if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+                    if (mFalsingManager.isFalseTap(
+                            mFeatureFlags.isEnabled(Flags.MEDIA_FALSING_PENALTY)
+                                    ? FalsingManager.MODERATE_PENALTY :
+                                    FalsingManager.LOW_PENALTY)) {
                         return;
                     }
 
@@ -994,7 +1015,10 @@
             } else {
                 button.setEnabled(true);
                 button.setOnClickListener(v -> {
-                    if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+                    if (!mFalsingManager.isFalseTap(
+                            mFeatureFlags.isEnabled(Flags.MEDIA_FALSING_PENALTY)
+                                    ? FalsingManager.MODERATE_PENALTY :
+                                    FalsingManager.LOW_PENALTY)) {
                         mLogger.logTapAction(button.getId(), mUid, mPackageName, mInstanceId);
                         logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT);
                         action.run();
@@ -1027,7 +1051,7 @@
                         /* maxWidth= */ maxSize,
                         /* maxHeight= */ maxSize,
                         /* pixelDensity= */ getContext().getResources().getDisplayMetrics().density,
-                        mColorSchemeTransition.getAccentPrimary().getTargetColor(),
+                        mColorSchemeTransition.getAccentPrimary().getCurrentColor(),
                         /* opacity= */ 100,
                         /* shouldFillRipple= */ false,
                         /* sparkleStrength= */ 0f,
@@ -1036,6 +1060,26 @@
         );
     }
 
+    private TurbulenceNoiseAnimationConfig createLingeringNoiseAnimation() {
+        return new TurbulenceNoiseAnimationConfig(
+                TurbulenceNoiseAnimationConfig.DEFAULT_NOISE_GRID_COUNT,
+                TurbulenceNoiseAnimationConfig.DEFAULT_LUMINOSITY_MULTIPLIER,
+                /* noiseMoveSpeedX= */ 0f,
+                /* noiseMoveSpeedY= */ 0f,
+                TurbulenceNoiseAnimationConfig.DEFAULT_NOISE_SPEED_Z,
+                /* color= */ mColorSchemeTransition.getAccentPrimary().getCurrentColor(),
+                // We want to add (BlendMode.PLUS) the turbulence noise on top of the album art.
+                // Thus, set the background color with alpha 0.
+                /* backgroundColor= */ ColorUtils.setAlphaComponent(Color.BLACK, 0),
+                TurbulenceNoiseAnimationConfig.DEFAULT_OPACITY,
+                /* width= */ mMediaViewHolder.getMultiRippleView().getWidth(),
+                /* height= */ mMediaViewHolder.getMultiRippleView().getHeight(),
+                TurbulenceNoiseAnimationConfig.DEFAULT_NOISE_DURATION_IN_MILLIS,
+                this.getContext().getResources().getDisplayMetrics().density,
+                BlendMode.PLUS,
+                /* onAnimationEnd= */ null
+        );
+    }
     private void clearButton(final ImageButton button) {
         button.setImageDrawable(null);
         button.setContentDescription(null);
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
index e354a03..1ea2025 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
@@ -18,8 +18,8 @@
 
 import android.content.Context
 import android.util.AttributeSet
-import com.android.systemui.ripple.RippleShader
-import com.android.systemui.ripple.RippleView
+import com.android.systemui.surfaceeffects.ripple.RippleShader
+import com.android.systemui.surfaceeffects.ripple.RippleView
 
 /**
  * An expanding ripple effect for the media tap-to-transfer receiver chip.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
index a895d72..9743c3e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
@@ -249,15 +249,7 @@
         mBackgroundOn = mContext.getDrawable(R.drawable.settingslib_switch_bar_bg_on);
         mInternetDialogTitle.setText(getDialogTitleText());
         mInternetDialogTitle.setGravity(Gravity.START | Gravity.CENTER_VERTICAL);
-
-        TypedArray typedArray = mContext.obtainStyledAttributes(
-                new int[]{android.R.attr.selectableItemBackground});
-        try {
-            mBackgroundOff = typedArray.getDrawable(0 /* index */);
-        } finally {
-            typedArray.recycle();
-        }
-
+        mBackgroundOff = mContext.getDrawable(R.drawable.internet_dialog_selected_effect);
         setOnClickListener();
         mTurnWifiOnLayout.setBackground(null);
         mAirplaneModeButton.setVisibility(
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleShaderUtilLibrary.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleShaderUtilLibrary.kt
deleted file mode 100644
index 6de4648..0000000
--- a/packages/SystemUI/src/com/android/systemui/ripple/RippleShaderUtilLibrary.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.ripple
-
-/** A common utility functions that are used for computing [RippleShader]. */
-class RippleShaderUtilLibrary {
-    //language=AGSL
-    companion object {
-        const val SHADER_LIB = """
-            float triangleNoise(vec2 n) {
-                    n  = fract(n * vec2(5.3987, 5.4421));
-                    n += dot(n.yx, n.xy + vec2(21.5351, 14.3137));
-                    float xy = n.x * n.y;
-                    return fract(xy * 95.4307) + fract(xy * 75.04961) - 1.0;
-                }
-                const float PI = 3.1415926535897932384626;
-
-                float sparkles(vec2 uv, float t) {
-                    float n = triangleNoise(uv);
-                    float s = 0.0;
-                    for (float i = 0; i < 4; i += 1) {
-                        float l = i * 0.01;
-                        float h = l + 0.1;
-                        float o = smoothstep(n - l, h, n);
-                        o *= abs(sin(PI * o * (t + 0.55 * i)));
-                        s += o;
-                    }
-                    return s;
-                }
-
-                vec2 distort(vec2 p, float time, float distort_amount_radial,
-                    float distort_amount_xy) {
-                        float angle = atan(p.y, p.x);
-                          return p + vec2(sin(angle * 8 + time * 0.003 + 1.641),
-                                    cos(angle * 5 + 2.14 + time * 0.00412)) * distort_amount_radial
-                             + vec2(sin(p.x * 0.01 + time * 0.00215 + 0.8123),
-                                    cos(p.y * 0.01 + time * 0.005931)) * distort_amount_xy;
-            }"""
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
index 954534d..5011227 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
@@ -51,6 +51,8 @@
                 connect(R.id.statusIcons, ConstraintSet.START, R.id.date, ConstraintSet.END)
                 connect(R.id.privacy_container, ConstraintSet.START, R.id.date, ConstraintSet.END)
                 constrainWidth(R.id.statusIcons, ViewGroup.LayoutParams.WRAP_CONTENT)
+                constrainedWidth(R.id.date, true)
+                constrainedWidth(R.id.statusIcons, true)
             }
         )
     }
@@ -92,6 +94,8 @@
                     centerEnd,
                     ConstraintSet.END
                 )
+                constrainedWidth(R.id.date, true)
+                constrainedWidth(R.id.statusIcons, true)
             },
             qsConstraintsChanges = {
                 setGuidelineBegin(centerStart, offsetFromEdge)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 334f1af..18a08f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -172,7 +172,6 @@
 import com.android.systemui.qs.QSFragment;
 import com.android.systemui.qs.QSPanelController;
 import com.android.systemui.recents.ScreenPinningRequest;
-import com.android.systemui.ripple.RippleShader.RippleShape;
 import com.android.systemui.scrim.ScrimView;
 import com.android.systemui.settings.brightness.BrightnessSliderController;
 import com.android.systemui.shade.CameraLauncher;
@@ -232,6 +231,7 @@
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
 import com.android.systemui.statusbar.window.StatusBarWindowStateController;
+import com.android.systemui.surfaceeffects.ripple.RippleShader.RippleShape;
 import com.android.systemui.util.DumpUtilsKt;
 import com.android.systemui.util.WallpaperController;
 import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -1131,7 +1131,6 @@
         // into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
         mNotificationIconAreaController.setupShelf(mNotificationShelfController);
         mShadeExpansionStateManager.addExpansionListener(mWakeUpCoordinator);
-        mUserSwitcherController.init(mNotificationShadeWindowView);
 
         // Allow plugins to reference DarkIconDispatcher and StatusBarStateController
         mPluginDependencyProvider.allowPluginDependency(DarkIconDispatcher.class);
@@ -4266,7 +4265,6 @@
             }
             // TODO: Bring these out of CentralSurfaces.
             mUserInfoControllerImpl.onDensityOrFontScaleChanged();
-            mUserSwitcherController.onDensityOrFontScaleChanged();
             mNotificationIconAreaController.onDensityOrFontScaleChanged(mContext);
             mHeadsUpManager.onDensityOrFontScaleChanged();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index d54a863..c527f30 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -53,7 +53,6 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.scrim.ScrimView;
 import com.android.systemui.shade.NotificationPanelViewController;
 import com.android.systemui.statusbar.notification.stack.ViewState;
@@ -205,7 +204,6 @@
     private final ScreenOffAnimationController mScreenOffAnimationController;
     private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
-    private KeyguardViewMediator mKeyguardViewMediator;
 
     private GradientColors mColors;
     private boolean mNeedsDrawableColorUpdate;
@@ -275,8 +273,7 @@
             @Main Executor mainExecutor,
             ScreenOffAnimationController screenOffAnimationController,
             KeyguardUnlockAnimationController keyguardUnlockAnimationController,
-            StatusBarKeyguardViewManager statusBarKeyguardViewManager,
-            KeyguardViewMediator keyguardViewMediator) {
+            StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
         mScrimStateListener = lightBarController::setScrimState;
         mDefaultScrimAlpha = BUSY_SCRIM_ALPHA;
 
@@ -315,8 +312,6 @@
             }
         });
         mColors = new GradientColors();
-
-        mKeyguardViewMediator = keyguardViewMediator;
     }
 
     /**
@@ -812,13 +807,6 @@
                         mBehindTint,
                         interpolatedFraction);
             }
-
-            // If we're unlocked but still playing the occlude animation, remain at the keyguard
-            // alpha temporarily.
-            if (mKeyguardViewMediator.isOccludeAnimationPlaying()
-                    || mState.mLaunchingAffordanceWithPreview) {
-                mNotificationsAlpha = KEYGUARD_SCRIM_ALPHA;
-            }
         } else if (mState == ScrimState.AUTH_SCRIMMED_SHADE) {
             mNotificationsAlpha = (float) Math.pow(getInterpolatedFraction(), 0.8f);
         } else if (mState == ScrimState.KEYGUARD || mState == ScrimState.SHADE_LOCKED
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
index cf4106c..68d30d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
@@ -21,7 +21,6 @@
 import android.graphics.ColorMatrix
 import android.graphics.ColorMatrixColorFilter
 import android.graphics.drawable.Drawable
-import android.os.UserHandle
 import android.widget.BaseAdapter
 import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower
 import com.android.systemui.user.data.source.UserRecord
@@ -84,7 +83,7 @@
     }
 
     fun refresh() {
-        controller.refreshUsers(UserHandle.USER_NULL)
+        controller.refreshUsers()
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt
index 146b222..bdb656b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt
@@ -14,35 +14,74 @@
  * limitations under the License.
  *
  */
+
 package com.android.systemui.statusbar.policy
 
-import android.annotation.UserIdInt
+import android.content.Context
 import android.content.Intent
 import android.view.View
-import com.android.systemui.Dumpable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower
 import com.android.systemui.user.data.source.UserRecord
+import com.android.systemui.user.domain.interactor.GuestUserInteractor
+import com.android.systemui.user.domain.interactor.UserInteractor
 import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
+import dagger.Lazy
+import java.io.PrintWriter
 import java.lang.ref.WeakReference
-import kotlinx.coroutines.flow.Flow
+import javax.inject.Inject
 
-/** Defines interface for a class that provides user switching functionality and state. */
-interface UserSwitcherController : Dumpable {
+/** Access point into multi-user switching logic. */
+@Deprecated("Use UserInteractor or GuestUserInteractor instead.")
+@SysUISingleton
+class UserSwitcherController
+@Inject
+constructor(
+    @Application private val applicationContext: Context,
+    private val userInteractorLazy: Lazy<UserInteractor>,
+    private val guestUserInteractorLazy: Lazy<GuestUserInteractor>,
+    private val keyguardInteractorLazy: Lazy<KeyguardInteractor>,
+    private val activityStarter: ActivityStarter,
+) {
+
+    /** Defines interface for classes that can be called back when the user is switched. */
+    fun interface UserSwitchCallback {
+        /** Notifies that the user has switched. */
+        fun onUserSwitched()
+    }
+
+    private val userInteractor: UserInteractor by lazy { userInteractorLazy.get() }
+    private val guestUserInteractor: GuestUserInteractor by lazy { guestUserInteractorLazy.get() }
+    private val keyguardInteractor: KeyguardInteractor by lazy { keyguardInteractorLazy.get() }
+
+    private val callbackCompatMap = mutableMapOf<UserSwitchCallback, UserInteractor.UserCallback>()
 
     /** The current list of [UserRecord]. */
     val users: ArrayList<UserRecord>
+        get() = userInteractor.userRecords.value
 
     /** Whether the user switcher experience should use the simple experience. */
     val isSimpleUserSwitcher: Boolean
-
-    /** Require a view for jank detection */
-    fun init(view: View)
+        get() = userInteractor.isSimpleUserSwitcher
 
     /** The [UserRecord] of the current user or `null` when none. */
     val currentUserRecord: UserRecord?
+        get() = userInteractor.selectedUserRecord.value
 
     /** The name of the current user of the device or `null`, when none is selected. */
     val currentUserName: String?
+        get() =
+            currentUserRecord?.let {
+                LegacyUserUiHelper.getUserRecordName(
+                    context = applicationContext,
+                    record = it,
+                    isGuestUserAutoCreated = userInteractor.isGuestUserAutoCreated,
+                    isGuestUserResetting = userInteractor.isGuestUserResetting,
+                )
+            }
 
     /**
      * Notifies that a user has been selected.
@@ -55,34 +94,40 @@
      * @param userId The ID of the user to switch to.
      * @param dialogShower An optional [DialogShower] in case we need to show dialogs.
      */
-    fun onUserSelected(userId: Int, dialogShower: DialogShower?)
-
-    /** Whether it is allowed to add users while the device is locked. */
-    val isAddUsersFromLockScreenEnabled: Flow<Boolean>
+    fun onUserSelected(userId: Int, dialogShower: DialogShower?) {
+        userInteractor.selectUser(userId, dialogShower)
+    }
 
     /** Whether the guest user is configured to always be present on the device. */
     val isGuestUserAutoCreated: Boolean
+        get() = userInteractor.isGuestUserAutoCreated
 
     /** Whether the guest user is currently being reset. */
     val isGuestUserResetting: Boolean
-
-    /** Creates and switches to the guest user. */
-    fun createAndSwitchToGuestUser(dialogShower: DialogShower?)
-
-    /** Shows the add user dialog. */
-    fun showAddUserDialog(dialogShower: DialogShower?)
-
-    /** Starts an activity to add a supervised user to the device. */
-    fun startSupervisedUserActivity()
-
-    /** Notifies when the display density or font scale has changed. */
-    fun onDensityOrFontScaleChanged()
+        get() = userInteractor.isGuestUserResetting
 
     /** Registers an adapter to notify when the users change. */
-    fun addAdapter(adapter: WeakReference<BaseUserSwitcherAdapter>)
+    fun addAdapter(adapter: WeakReference<BaseUserSwitcherAdapter>) {
+        userInteractor.addCallback(
+            object : UserInteractor.UserCallback {
+                override fun isEvictable(): Boolean {
+                    return adapter.get() == null
+                }
+
+                override fun onUserStateChanged() {
+                    adapter.get()?.notifyDataSetChanged()
+                }
+            }
+        )
+    }
 
     /** Notifies the item for a user has been clicked. */
-    fun onUserListItemClicked(record: UserRecord, dialogShower: DialogShower?)
+    fun onUserListItemClicked(
+        record: UserRecord,
+        dialogShower: DialogShower?,
+    ) {
+        userInteractor.onRecordSelected(record, dialogShower)
+    }
 
     /**
      * Removes guest user and switches to target user. The guest must be the current user and its id
@@ -103,7 +148,12 @@
      * @param targetUserId id of the user to switch to after guest is removed. If
      * `UserHandle.USER_NULL`, then switch immediately to the newly created guest user.
      */
-    fun removeGuestUser(@UserIdInt guestUserId: Int, @UserIdInt targetUserId: Int)
+    fun removeGuestUser(guestUserId: Int, targetUserId: Int) {
+        userInteractor.removeGuestUser(
+            guestUserId = guestUserId,
+            targetUserId = targetUserId,
+        )
+    }
 
     /**
      * Exits guest user and switches to previous non-guest user. The guest must be the current user.
@@ -114,43 +164,58 @@
      * @param forceRemoveGuestOnExit true: remove guest before switching user, false: remove guest
      * only if its ephemeral, else keep guest
      */
-    fun exitGuestUser(
-        @UserIdInt guestUserId: Int,
-        @UserIdInt targetUserId: Int,
-        forceRemoveGuestOnExit: Boolean
-    )
+    fun exitGuestUser(guestUserId: Int, targetUserId: Int, forceRemoveGuestOnExit: Boolean) {
+        userInteractor.exitGuestUser(guestUserId, targetUserId, forceRemoveGuestOnExit)
+    }
 
     /**
      * Guarantee guest is present only if the device is provisioned. Otherwise, create a content
      * observer to wait until the device is provisioned, then schedule the guest creation.
      */
-    fun schedulePostBootGuestCreation()
+    fun schedulePostBootGuestCreation() {
+        guestUserInteractor.onDeviceBootCompleted()
+    }
 
     /** Whether keyguard is showing. */
     val isKeyguardShowing: Boolean
+        get() = keyguardInteractor.isKeyguardShowing()
 
     /** Starts an activity with the given [Intent]. */
-    fun startActivity(intent: Intent)
+    fun startActivity(intent: Intent) {
+        activityStarter.startActivity(intent, /* dismissShade= */ true)
+    }
 
     /**
      * Refreshes users from UserManager.
      *
      * The pictures are only loaded if they have not been loaded yet.
-     *
-     * @param forcePictureLoadForId forces the picture of the given user to be reloaded.
      */
-    fun refreshUsers(forcePictureLoadForId: Int)
+    fun refreshUsers() {
+        userInteractor.refreshUsers()
+    }
 
     /** Adds a subscriber to when user switches. */
-    fun addUserSwitchCallback(callback: UserSwitchCallback)
+    fun addUserSwitchCallback(callback: UserSwitchCallback) {
+        val interactorCallback =
+            object : UserInteractor.UserCallback {
+                override fun onUserStateChanged() {
+                    callback.onUserSwitched()
+                }
+            }
+        callbackCompatMap[callback] = interactorCallback
+        userInteractor.addCallback(interactorCallback)
+    }
 
     /** Removes a previously-added subscriber. */
-    fun removeUserSwitchCallback(callback: UserSwitchCallback)
+    fun removeUserSwitchCallback(callback: UserSwitchCallback) {
+        val interactorCallback = callbackCompatMap.remove(callback)
+        if (interactorCallback != null) {
+            userInteractor.removeCallback(interactorCallback)
+        }
+    }
 
-    /** Defines interface for classes that can be called back when the user is switched. */
-    fun interface UserSwitchCallback {
-        /** Notifies that the user has switched. */
-        fun onUserSwitched()
+    fun dump(pw: PrintWriter, args: Array<out String>) {
+        userInteractor.dump(pw)
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt
deleted file mode 100644
index 935fc7f..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt
+++ /dev/null
@@ -1,299 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.statusbar.policy
-
-import android.content.Context
-import android.content.Intent
-import android.view.View
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.qs.user.UserSwitchDialogController
-import com.android.systemui.user.data.source.UserRecord
-import com.android.systemui.user.domain.interactor.GuestUserInteractor
-import com.android.systemui.user.domain.interactor.UserInteractor
-import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
-import dagger.Lazy
-import java.io.PrintWriter
-import java.lang.ref.WeakReference
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-
-/** Implementation of [UserSwitcherController]. */
-@SysUISingleton
-class UserSwitcherControllerImpl
-@Inject
-constructor(
-    @Application private val applicationContext: Context,
-    flags: FeatureFlags,
-    @Suppress("DEPRECATION") private val oldImpl: Lazy<UserSwitcherControllerOldImpl>,
-    private val userInteractorLazy: Lazy<UserInteractor>,
-    private val guestUserInteractorLazy: Lazy<GuestUserInteractor>,
-    private val keyguardInteractorLazy: Lazy<KeyguardInteractor>,
-    private val activityStarter: ActivityStarter,
-) : UserSwitcherController {
-
-    private val useInteractor: Boolean =
-        flags.isEnabled(Flags.USER_CONTROLLER_USES_INTERACTOR) &&
-            !flags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)
-    private val _oldImpl: UserSwitcherControllerOldImpl
-        get() = oldImpl.get()
-    private val userInteractor: UserInteractor by lazy { userInteractorLazy.get() }
-    private val guestUserInteractor: GuestUserInteractor by lazy { guestUserInteractorLazy.get() }
-    private val keyguardInteractor: KeyguardInteractor by lazy { keyguardInteractorLazy.get() }
-
-    private val callbackCompatMap =
-        mutableMapOf<UserSwitcherController.UserSwitchCallback, UserInteractor.UserCallback>()
-
-    private fun notSupported(): Nothing {
-        error("Not supported in the new implementation!")
-    }
-
-    override val users: ArrayList<UserRecord>
-        get() =
-            if (useInteractor) {
-                userInteractor.userRecords.value
-            } else {
-                _oldImpl.users
-            }
-
-    override val isSimpleUserSwitcher: Boolean
-        get() =
-            if (useInteractor) {
-                userInteractor.isSimpleUserSwitcher
-            } else {
-                _oldImpl.isSimpleUserSwitcher
-            }
-
-    override fun init(view: View) {
-        if (!useInteractor) {
-            _oldImpl.init(view)
-        }
-    }
-
-    override val currentUserRecord: UserRecord?
-        get() =
-            if (useInteractor) {
-                userInteractor.selectedUserRecord.value
-            } else {
-                _oldImpl.currentUserRecord
-            }
-
-    override val currentUserName: String?
-        get() =
-            if (useInteractor) {
-                currentUserRecord?.let {
-                    LegacyUserUiHelper.getUserRecordName(
-                        context = applicationContext,
-                        record = it,
-                        isGuestUserAutoCreated = userInteractor.isGuestUserAutoCreated,
-                        isGuestUserResetting = userInteractor.isGuestUserResetting,
-                    )
-                }
-            } else {
-                _oldImpl.currentUserName
-            }
-
-    override fun onUserSelected(
-        userId: Int,
-        dialogShower: UserSwitchDialogController.DialogShower?
-    ) {
-        if (useInteractor) {
-            userInteractor.selectUser(userId, dialogShower)
-        } else {
-            _oldImpl.onUserSelected(userId, dialogShower)
-        }
-    }
-
-    override val isAddUsersFromLockScreenEnabled: Flow<Boolean>
-        get() =
-            if (useInteractor) {
-                notSupported()
-            } else {
-                _oldImpl.isAddUsersFromLockScreenEnabled
-            }
-
-    override val isGuestUserAutoCreated: Boolean
-        get() =
-            if (useInteractor) {
-                userInteractor.isGuestUserAutoCreated
-            } else {
-                _oldImpl.isGuestUserAutoCreated
-            }
-
-    override val isGuestUserResetting: Boolean
-        get() =
-            if (useInteractor) {
-                userInteractor.isGuestUserResetting
-            } else {
-                _oldImpl.isGuestUserResetting
-            }
-
-    override fun createAndSwitchToGuestUser(
-        dialogShower: UserSwitchDialogController.DialogShower?,
-    ) {
-        if (useInteractor) {
-            notSupported()
-        } else {
-            _oldImpl.createAndSwitchToGuestUser(dialogShower)
-        }
-    }
-
-    override fun showAddUserDialog(dialogShower: UserSwitchDialogController.DialogShower?) {
-        if (useInteractor) {
-            notSupported()
-        } else {
-            _oldImpl.showAddUserDialog(dialogShower)
-        }
-    }
-
-    override fun startSupervisedUserActivity() {
-        if (useInteractor) {
-            notSupported()
-        } else {
-            _oldImpl.startSupervisedUserActivity()
-        }
-    }
-
-    override fun onDensityOrFontScaleChanged() {
-        if (!useInteractor) {
-            _oldImpl.onDensityOrFontScaleChanged()
-        }
-    }
-
-    override fun addAdapter(adapter: WeakReference<BaseUserSwitcherAdapter>) {
-        if (useInteractor) {
-            userInteractor.addCallback(
-                object : UserInteractor.UserCallback {
-                    override fun isEvictable(): Boolean {
-                        return adapter.get() == null
-                    }
-
-                    override fun onUserStateChanged() {
-                        adapter.get()?.notifyDataSetChanged()
-                    }
-                }
-            )
-        } else {
-            _oldImpl.addAdapter(adapter)
-        }
-    }
-
-    override fun onUserListItemClicked(
-        record: UserRecord,
-        dialogShower: UserSwitchDialogController.DialogShower?,
-    ) {
-        if (useInteractor) {
-            userInteractor.onRecordSelected(record, dialogShower)
-        } else {
-            _oldImpl.onUserListItemClicked(record, dialogShower)
-        }
-    }
-
-    override fun removeGuestUser(guestUserId: Int, targetUserId: Int) {
-        if (useInteractor) {
-            userInteractor.removeGuestUser(
-                guestUserId = guestUserId,
-                targetUserId = targetUserId,
-            )
-        } else {
-            _oldImpl.removeGuestUser(guestUserId, targetUserId)
-        }
-    }
-
-    override fun exitGuestUser(
-        guestUserId: Int,
-        targetUserId: Int,
-        forceRemoveGuestOnExit: Boolean
-    ) {
-        if (useInteractor) {
-            userInteractor.exitGuestUser(guestUserId, targetUserId, forceRemoveGuestOnExit)
-        } else {
-            _oldImpl.exitGuestUser(guestUserId, targetUserId, forceRemoveGuestOnExit)
-        }
-    }
-
-    override fun schedulePostBootGuestCreation() {
-        if (useInteractor) {
-            guestUserInteractor.onDeviceBootCompleted()
-        } else {
-            _oldImpl.schedulePostBootGuestCreation()
-        }
-    }
-
-    override val isKeyguardShowing: Boolean
-        get() =
-            if (useInteractor) {
-                keyguardInteractor.isKeyguardShowing()
-            } else {
-                _oldImpl.isKeyguardShowing
-            }
-
-    override fun startActivity(intent: Intent) {
-        if (useInteractor) {
-            activityStarter.startActivity(intent, /* dismissShade= */ true)
-        } else {
-            _oldImpl.startActivity(intent)
-        }
-    }
-
-    override fun refreshUsers(forcePictureLoadForId: Int) {
-        if (useInteractor) {
-            userInteractor.refreshUsers()
-        } else {
-            _oldImpl.refreshUsers(forcePictureLoadForId)
-        }
-    }
-
-    override fun addUserSwitchCallback(callback: UserSwitcherController.UserSwitchCallback) {
-        if (useInteractor) {
-            val interactorCallback =
-                object : UserInteractor.UserCallback {
-                    override fun onUserStateChanged() {
-                        callback.onUserSwitched()
-                    }
-                }
-            callbackCompatMap[callback] = interactorCallback
-            userInteractor.addCallback(interactorCallback)
-        } else {
-            _oldImpl.addUserSwitchCallback(callback)
-        }
-    }
-
-    override fun removeUserSwitchCallback(callback: UserSwitcherController.UserSwitchCallback) {
-        if (useInteractor) {
-            val interactorCallback = callbackCompatMap.remove(callback)
-            if (interactorCallback != null) {
-                userInteractor.removeCallback(interactorCallback)
-            }
-        } else {
-            _oldImpl.removeUserSwitchCallback(callback)
-        }
-    }
-
-    override fun dump(pw: PrintWriter, args: Array<out String>) {
-        if (useInteractor) {
-            userInteractor.dump(pw)
-        } else {
-            _oldImpl.dump(pw, args)
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java
deleted file mode 100644
index c294c37..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java
+++ /dev/null
@@ -1,1063 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.statusbar.policy;
-
-import static android.os.UserManager.SWITCHABILITY_STATUS_OK;
-
-import android.annotation.UserIdInt;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.IActivityManager;
-import android.app.admin.DevicePolicyManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.UserInfo;
-import android.database.ContentObserver;
-import android.graphics.Bitmap;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.telephony.TelephonyCallback;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-import android.view.View;
-import android.view.WindowManagerGlobal;
-import android.widget.Toast;
-
-import androidx.annotation.Nullable;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.jank.InteractionJankMonitor;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.util.LatencyTracker;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.settingslib.users.UserCreatingDialog;
-import com.android.systemui.GuestResetOrExitSessionReceiver;
-import com.android.systemui.GuestResumeSessionReceiver;
-import com.android.systemui.SystemUISecondaryUserService;
-import com.android.systemui.animation.DialogCuj;
-import com.android.systemui.animation.DialogLaunchAnimator;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.broadcast.BroadcastSender;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.LongRunning;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.qs.QSUserSwitcherEvent;
-import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.telephony.TelephonyListenerManager;
-import com.android.systemui.user.data.source.UserRecord;
-import com.android.systemui.user.legacyhelper.data.LegacyUserDataHelper;
-import com.android.systemui.user.shared.model.UserActionModel;
-import com.android.systemui.user.ui.dialog.AddUserDialog;
-import com.android.systemui.user.ui.dialog.ExitGuestDialog;
-import com.android.systemui.util.settings.GlobalSettings;
-import com.android.systemui.util.settings.SecureSettings;
-
-import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Consumer;
-
-import javax.inject.Inject;
-
-import kotlinx.coroutines.flow.Flow;
-import kotlinx.coroutines.flow.MutableStateFlow;
-import kotlinx.coroutines.flow.StateFlowKt;
-
-/**
- * Old implementation. Keeps a list of all users on the device for user switching.
- *
- * @deprecated This is the old implementation. Please depend on {@link UserSwitcherController}
- * instead.
- */
-@Deprecated
-@SysUISingleton
-public class UserSwitcherControllerOldImpl implements UserSwitcherController {
-
-    private static final String TAG = "UserSwitcherController";
-    private static final boolean DEBUG = false;
-    private static final String SIMPLE_USER_SWITCHER_GLOBAL_SETTING =
-            "lockscreenSimpleUserSwitcher";
-    private static final int PAUSE_REFRESH_USERS_TIMEOUT_MS = 3000;
-
-    private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
-    private static final long MULTI_USER_JOURNEY_TIMEOUT = 20000L;
-
-    private static final String INTERACTION_JANK_ADD_NEW_USER_TAG = "add_new_user";
-    private static final String INTERACTION_JANK_EXIT_GUEST_MODE_TAG = "exit_guest_mode";
-
-    protected final Context mContext;
-    protected final UserTracker mUserTracker;
-    protected final UserManager mUserManager;
-    private final ContentObserver mSettingsObserver;
-    private final ArrayList<WeakReference<BaseUserSwitcherAdapter>> mAdapters = new ArrayList<>();
-    @VisibleForTesting
-    final GuestResumeSessionReceiver mGuestResumeSessionReceiver;
-    @VisibleForTesting
-    final GuestResetOrExitSessionReceiver mGuestResetOrExitSessionReceiver;
-    private final KeyguardStateController mKeyguardStateController;
-    private final DeviceProvisionedController mDeviceProvisionedController;
-    private final DevicePolicyManager mDevicePolicyManager;
-    protected final Handler mHandler;
-    private final ActivityStarter mActivityStarter;
-    private final BroadcastDispatcher mBroadcastDispatcher;
-    private final BroadcastSender mBroadcastSender;
-    private final TelephonyListenerManager mTelephonyListenerManager;
-    private final InteractionJankMonitor mInteractionJankMonitor;
-    private final LatencyTracker mLatencyTracker;
-    private final DialogLaunchAnimator mDialogLaunchAnimator;
-
-    private ArrayList<UserRecord> mUsers = new ArrayList<>();
-    @VisibleForTesting
-    AlertDialog mExitGuestDialog;
-    @VisibleForTesting
-    Dialog mAddUserDialog;
-    private int mLastNonGuestUser = UserHandle.USER_SYSTEM;
-    private boolean mSimpleUserSwitcher;
-    // When false, there won't be any visual affordance to add a new user from the keyguard even if
-    // the user is unlocked
-    private final MutableStateFlow<Boolean> mAddUsersFromLockScreen =
-            StateFlowKt.MutableStateFlow(false);
-    private boolean mUserSwitcherEnabled;
-    @VisibleForTesting
-    boolean mPauseRefreshUsers;
-    private int mSecondaryUser = UserHandle.USER_NULL;
-    private Intent mSecondaryUserServiceIntent;
-    private SparseBooleanArray mForcePictureLoadForUserId = new SparseBooleanArray(2);
-    private final UiEventLogger mUiEventLogger;
-    private final IActivityManager mActivityManager;
-    private final Executor mBgExecutor;
-    private final Executor mUiExecutor;
-    private final Executor mLongRunningExecutor;
-    private final boolean mGuestUserAutoCreated;
-    private final AtomicBoolean mGuestIsResetting;
-    private final AtomicBoolean mGuestCreationScheduled;
-    private FalsingManager mFalsingManager;
-    @Nullable
-    private View mView;
-    private String mCreateSupervisedUserPackage;
-    private GlobalSettings mGlobalSettings;
-    private List<UserSwitchCallback> mUserSwitchCallbacks =
-            Collections.synchronizedList(new ArrayList<>());
-
-    @Inject
-    public UserSwitcherControllerOldImpl(
-            Context context,
-            IActivityManager activityManager,
-            UserManager userManager,
-            UserTracker userTracker,
-            KeyguardStateController keyguardStateController,
-            DeviceProvisionedController deviceProvisionedController,
-            DevicePolicyManager devicePolicyManager,
-            @Main Handler handler,
-            ActivityStarter activityStarter,
-            BroadcastDispatcher broadcastDispatcher,
-            BroadcastSender broadcastSender,
-            UiEventLogger uiEventLogger,
-            FalsingManager falsingManager,
-            TelephonyListenerManager telephonyListenerManager,
-            SecureSettings secureSettings,
-            GlobalSettings globalSettings,
-            @Background Executor bgExecutor,
-            @LongRunning Executor longRunningExecutor,
-            @Main Executor uiExecutor,
-            InteractionJankMonitor interactionJankMonitor,
-            LatencyTracker latencyTracker,
-            DumpManager dumpManager,
-            DialogLaunchAnimator dialogLaunchAnimator,
-            GuestResumeSessionReceiver guestResumeSessionReceiver,
-            GuestResetOrExitSessionReceiver guestResetOrExitSessionReceiver) {
-        mContext = context;
-        mActivityManager = activityManager;
-        mUserTracker = userTracker;
-        mBroadcastDispatcher = broadcastDispatcher;
-        mBroadcastSender = broadcastSender;
-        mTelephonyListenerManager = telephonyListenerManager;
-        mUiEventLogger = uiEventLogger;
-        mFalsingManager = falsingManager;
-        mInteractionJankMonitor = interactionJankMonitor;
-        mLatencyTracker = latencyTracker;
-        mGlobalSettings = globalSettings;
-        mGuestResumeSessionReceiver = guestResumeSessionReceiver;
-        mGuestResetOrExitSessionReceiver = guestResetOrExitSessionReceiver;
-        mBgExecutor = bgExecutor;
-        mLongRunningExecutor = longRunningExecutor;
-        mUiExecutor = uiExecutor;
-        mGuestResumeSessionReceiver.register();
-        mGuestResetOrExitSessionReceiver.register();
-        mGuestUserAutoCreated = mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_guestUserAutoCreated);
-        mGuestIsResetting = new AtomicBoolean();
-        mGuestCreationScheduled = new AtomicBoolean();
-        mKeyguardStateController = keyguardStateController;
-        mDeviceProvisionedController = deviceProvisionedController;
-        mDevicePolicyManager = devicePolicyManager;
-        mHandler = handler;
-        mActivityStarter = activityStarter;
-        mUserManager = userManager;
-        mDialogLaunchAnimator = dialogLaunchAnimator;
-
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_USER_ADDED);
-        filter.addAction(Intent.ACTION_USER_REMOVED);
-        filter.addAction(Intent.ACTION_USER_INFO_CHANGED);
-        filter.addAction(Intent.ACTION_USER_SWITCHED);
-        filter.addAction(Intent.ACTION_USER_STOPPED);
-        filter.addAction(Intent.ACTION_USER_UNLOCKED);
-        mBroadcastDispatcher.registerReceiver(
-                mReceiver, filter, null /* executor */,
-                UserHandle.SYSTEM, Context.RECEIVER_EXPORTED, null /* permission */);
-
-        mSimpleUserSwitcher = shouldUseSimpleUserSwitcher();
-
-        mSecondaryUserServiceIntent = new Intent(context, SystemUISecondaryUserService.class);
-
-        filter = new IntentFilter();
-        mContext.registerReceiverAsUser(mReceiver, UserHandle.SYSTEM, filter,
-                PERMISSION_SELF, null /* scheduler */,
-                Context.RECEIVER_EXPORTED_UNAUDITED);
-
-        mSettingsObserver = new ContentObserver(mHandler) {
-            @Override
-            public void onChange(boolean selfChange) {
-                mSimpleUserSwitcher = shouldUseSimpleUserSwitcher();
-                mAddUsersFromLockScreen.setValue(
-                        mGlobalSettings.getIntForUser(
-                                Settings.Global.ADD_USERS_WHEN_LOCKED,
-                                0,
-                                UserHandle.USER_SYSTEM) != 0);
-                mUserSwitcherEnabled = mGlobalSettings.getIntForUser(
-                        Settings.Global.USER_SWITCHER_ENABLED, 0, UserHandle.USER_SYSTEM) != 0;
-                refreshUsers(UserHandle.USER_NULL);
-            };
-        };
-        mContext.getContentResolver().registerContentObserver(
-                Settings.Global.getUriFor(SIMPLE_USER_SWITCHER_GLOBAL_SETTING), true,
-                mSettingsObserver);
-        mContext.getContentResolver().registerContentObserver(
-                Settings.Global.getUriFor(Settings.Global.USER_SWITCHER_ENABLED), true,
-                mSettingsObserver);
-        mContext.getContentResolver().registerContentObserver(
-                Settings.Global.getUriFor(Settings.Global.ADD_USERS_WHEN_LOCKED), true,
-                mSettingsObserver);
-        mContext.getContentResolver().registerContentObserver(
-                Settings.Global.getUriFor(
-                        Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED),
-                true, mSettingsObserver);
-        // Fetch initial values.
-        mSettingsObserver.onChange(false);
-
-        keyguardStateController.addCallback(mCallback);
-        listenForCallState();
-
-        mCreateSupervisedUserPackage = mContext.getString(
-                com.android.internal.R.string.config_supervisedUserCreationPackage);
-
-        dumpManager.registerDumpable(getClass().getSimpleName(), this);
-
-        refreshUsers(UserHandle.USER_NULL);
-    }
-
-    @Override
-    @SuppressWarnings("unchecked")
-    public void refreshUsers(int forcePictureLoadForId) {
-        if (DEBUG) Log.d(TAG, "refreshUsers(forcePictureLoadForId=" + forcePictureLoadForId + ")");
-        if (forcePictureLoadForId != UserHandle.USER_NULL) {
-            mForcePictureLoadForUserId.put(forcePictureLoadForId, true);
-        }
-
-        if (mPauseRefreshUsers) {
-            return;
-        }
-
-        boolean forceAllUsers = mForcePictureLoadForUserId.get(UserHandle.USER_ALL);
-        SparseArray<Bitmap> bitmaps = new SparseArray<>(mUsers.size());
-        final int userCount = mUsers.size();
-        for (int i = 0; i < userCount; i++) {
-            UserRecord r = mUsers.get(i);
-            if (r == null || r.picture == null || r.info == null || forceAllUsers
-                    || mForcePictureLoadForUserId.get(r.info.id)) {
-                continue;
-            }
-            bitmaps.put(r.info.id, r.picture);
-        }
-        mForcePictureLoadForUserId.clear();
-
-        mBgExecutor.execute(() ->  {
-            List<UserInfo> infos = mUserManager.getAliveUsers();
-            if (infos == null) {
-                return;
-            }
-            ArrayList<UserRecord> records = new ArrayList<>(infos.size());
-            int currentId = mUserTracker.getUserId();
-            // Check user switchability of the foreground user since SystemUI is running in
-            // User 0
-            boolean canSwitchUsers = mUserManager.getUserSwitchability(
-                    UserHandle.of(mUserTracker.getUserId())) == SWITCHABILITY_STATUS_OK;
-            UserRecord guestRecord = null;
-
-            for (UserInfo info : infos) {
-                boolean isCurrent = currentId == info.id;
-                if (!mUserSwitcherEnabled && !info.isPrimary()) {
-                    continue;
-                }
-
-                if (info.isEnabled()) {
-                    if (info.isGuest()) {
-                        // Tapping guest icon triggers remove and a user switch therefore
-                        // the icon shouldn't be enabled even if the user is current
-                        guestRecord = LegacyUserDataHelper.createRecord(
-                                mContext,
-                                mUserManager,
-                                null /* picture */,
-                                info,
-                                isCurrent,
-                                canSwitchUsers);
-                    } else if (info.supportsSwitchToByUser()) {
-                        records.add(
-                                LegacyUserDataHelper.createRecord(
-                                        mContext,
-                                        mUserManager,
-                                        bitmaps.get(info.id),
-                                        info,
-                                        isCurrent,
-                                        canSwitchUsers));
-                    }
-                }
-            }
-
-            if (guestRecord == null) {
-                if (mGuestUserAutoCreated) {
-                    // If mGuestIsResetting=true, the switch should be disabled since
-                    // we will just use it as an indicator for "Resetting guest...".
-                    // Otherwise, default to canSwitchUsers.
-                    boolean isSwitchToGuestEnabled = !mGuestIsResetting.get() && canSwitchUsers;
-                    guestRecord = LegacyUserDataHelper.createRecord(
-                            mContext,
-                            currentId,
-                            UserActionModel.ENTER_GUEST_MODE,
-                            false /* isRestricted */,
-                            isSwitchToGuestEnabled);
-                    records.add(guestRecord);
-                } else if (canCreateGuest(guestRecord != null)) {
-                    guestRecord = LegacyUserDataHelper.createRecord(
-                            mContext,
-                            currentId,
-                            UserActionModel.ENTER_GUEST_MODE,
-                            false /* isRestricted */,
-                            canSwitchUsers);
-                    records.add(guestRecord);
-                }
-            } else {
-                records.add(guestRecord);
-            }
-
-            if (canCreateUser()) {
-                final UserRecord userRecord = LegacyUserDataHelper.createRecord(
-                        mContext,
-                        currentId,
-                        UserActionModel.ADD_USER,
-                        createIsRestricted(),
-                        canSwitchUsers);
-                records.add(userRecord);
-            }
-
-            if (canCreateSupervisedUser()) {
-                final UserRecord userRecord = LegacyUserDataHelper.createRecord(
-                        mContext,
-                        currentId,
-                        UserActionModel.ADD_SUPERVISED_USER,
-                        createIsRestricted(),
-                        canSwitchUsers);
-                records.add(userRecord);
-            }
-
-            if (canManageUsers()) {
-                records.add(LegacyUserDataHelper.createRecord(
-                        mContext,
-                        KeyguardUpdateMonitor.getCurrentUser(),
-                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
-                        /* isRestricted= */ false,
-                        /* isSwitchToEnabled= */ true
-                ));
-            }
-
-            mUiExecutor.execute(() -> {
-                if (records != null) {
-                    mUsers = records;
-                    notifyAdapters();
-                }
-            });
-        });
-    }
-
-    private boolean systemCanCreateUsers() {
-        return !mUserManager.hasBaseUserRestriction(
-                UserManager.DISALLOW_ADD_USER, UserHandle.SYSTEM);
-    }
-
-    private boolean currentUserCanCreateUsers() {
-        UserInfo currentUser = mUserTracker.getUserInfo();
-        return currentUser != null
-                && (currentUser.isAdmin() || mUserTracker.getUserId() == UserHandle.USER_SYSTEM)
-                && systemCanCreateUsers();
-    }
-
-    private boolean anyoneCanCreateUsers() {
-        return systemCanCreateUsers() && mAddUsersFromLockScreen.getValue();
-    }
-
-    @VisibleForTesting
-    boolean canCreateGuest(boolean hasExistingGuest) {
-        return mUserSwitcherEnabled
-                && (currentUserCanCreateUsers() || anyoneCanCreateUsers())
-                && !hasExistingGuest;
-    }
-
-    @VisibleForTesting
-    boolean canCreateUser() {
-        return mUserSwitcherEnabled
-                && (currentUserCanCreateUsers() || anyoneCanCreateUsers())
-                && mUserManager.canAddMoreUsers(UserManager.USER_TYPE_FULL_SECONDARY);
-    }
-
-    @VisibleForTesting
-    boolean canManageUsers() {
-        UserInfo currentUser = mUserTracker.getUserInfo();
-        return mUserSwitcherEnabled
-                && ((currentUser != null && currentUser.isAdmin())
-                || mAddUsersFromLockScreen.getValue());
-    }
-
-    private boolean createIsRestricted() {
-        return !mAddUsersFromLockScreen.getValue();
-    }
-
-    @VisibleForTesting
-    boolean canCreateSupervisedUser() {
-        return !TextUtils.isEmpty(mCreateSupervisedUserPackage) && canCreateUser();
-    }
-
-    private void pauseRefreshUsers() {
-        if (!mPauseRefreshUsers) {
-            mHandler.postDelayed(mUnpauseRefreshUsers, PAUSE_REFRESH_USERS_TIMEOUT_MS);
-            mPauseRefreshUsers = true;
-        }
-    }
-
-    private void notifyAdapters() {
-        for (int i = mAdapters.size() - 1; i >= 0; i--) {
-            BaseUserSwitcherAdapter adapter = mAdapters.get(i).get();
-            if (adapter != null) {
-                adapter.notifyDataSetChanged();
-            } else {
-                mAdapters.remove(i);
-            }
-        }
-    }
-
-    @Override
-    public boolean isSimpleUserSwitcher() {
-        return mSimpleUserSwitcher;
-    }
-
-    /**
-     * Returns whether the current user is a system user.
-     */
-    @VisibleForTesting
-    boolean isSystemUser() {
-        return mUserTracker.getUserId() == UserHandle.USER_SYSTEM;
-    }
-
-    @Override
-    public @Nullable UserRecord getCurrentUserRecord() {
-        for (int i = 0; i < mUsers.size(); ++i) {
-            UserRecord userRecord = mUsers.get(i);
-            if (userRecord.isCurrent) {
-                return userRecord;
-            }
-        }
-        return null;
-    }
-
-    @Override
-    public void onUserSelected(int userId, @Nullable DialogShower dialogShower) {
-        UserRecord userRecord = mUsers.stream()
-                .filter(x -> x.resolveId() == userId)
-                .findFirst()
-                .orElse(null);
-        if (userRecord == null) {
-            return;
-        }
-
-        onUserListItemClicked(userRecord, dialogShower);
-    }
-
-    @Override
-    public Flow<Boolean> isAddUsersFromLockScreenEnabled() {
-        return mAddUsersFromLockScreen;
-    }
-
-    @Override
-    public boolean isGuestUserAutoCreated() {
-        return mGuestUserAutoCreated;
-    }
-
-    @Override
-    public boolean isGuestUserResetting() {
-        return mGuestIsResetting.get();
-    }
-
-    @Override
-    public void onUserListItemClicked(UserRecord record, DialogShower dialogShower) {
-        if (record.isGuest && record.info == null) {
-            createAndSwitchToGuestUser(dialogShower);
-        } else if (record.isAddUser) {
-            showAddUserDialog(dialogShower);
-        } else if (record.isAddSupervisedUser) {
-            startSupervisedUserActivity();
-        } else if (record.isManageUsers) {
-            startActivity(new Intent(Settings.ACTION_USER_SETTINGS));
-        } else {
-            onUserListItemClicked(record.info.id, record, dialogShower);
-        }
-    }
-
-    private void onUserListItemClicked(int id, UserRecord record, DialogShower dialogShower) {
-        int currUserId = mUserTracker.getUserId();
-        // If switching from guest and guest is ephemeral, then follow the flow
-        // of showExitGuestDialog to remove current guest,
-        // and switch to selected user
-        UserInfo currUserInfo = mUserTracker.getUserInfo();
-        if (currUserId == id) {
-            if (record.isGuest) {
-                showExitGuestDialog(id, currUserInfo.isEphemeral(), dialogShower);
-            }
-            return;
-        }
-
-        if (currUserInfo != null && currUserInfo.isGuest()) {
-            showExitGuestDialog(currUserId, currUserInfo.isEphemeral(),
-                    record.resolveId(), dialogShower);
-            return;
-        }
-
-        if (dialogShower != null) {
-            // If we haven't morphed into another dialog, it means we have just switched users.
-            // Then, dismiss the dialog.
-            dialogShower.dismiss();
-        }
-        switchToUserId(id);
-    }
-
-    private void switchToUserId(int id) {
-        try {
-            if (mView != null) {
-                mInteractionJankMonitor.begin(InteractionJankMonitor.Configuration.Builder
-                        .withView(InteractionJankMonitor.CUJ_USER_SWITCH, mView)
-                        .setTimeout(MULTI_USER_JOURNEY_TIMEOUT));
-            }
-            mLatencyTracker.onActionStart(LatencyTracker.ACTION_USER_SWITCH);
-            pauseRefreshUsers();
-            mActivityManager.switchUser(id);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Couldn't switch user.", e);
-        }
-    }
-
-    private void showExitGuestDialog(int id, boolean isGuestEphemeral, DialogShower dialogShower) {
-        int newId = UserHandle.USER_SYSTEM;
-        if (mLastNonGuestUser != UserHandle.USER_SYSTEM) {
-            UserInfo info = mUserManager.getUserInfo(mLastNonGuestUser);
-            if (info != null && info.isEnabled() && info.supportsSwitchToByUser()) {
-                newId = info.id;
-            }
-        }
-        showExitGuestDialog(id, isGuestEphemeral, newId, dialogShower);
-    }
-
-    private void showExitGuestDialog(
-            int id,
-            boolean isGuestEphemeral,
-            int targetId,
-            DialogShower dialogShower) {
-        if (mExitGuestDialog != null && mExitGuestDialog.isShowing()) {
-            mExitGuestDialog.cancel();
-        }
-        mExitGuestDialog = new ExitGuestDialog(
-                mContext,
-                id,
-                isGuestEphemeral,
-                targetId,
-                mKeyguardStateController.isShowing(),
-                mFalsingManager,
-                mDialogLaunchAnimator,
-                this::exitGuestUser);
-        if (dialogShower != null) {
-            dialogShower.showDialog(mExitGuestDialog, new DialogCuj(
-                    InteractionJankMonitor.CUJ_USER_DIALOG_OPEN,
-                    INTERACTION_JANK_EXIT_GUEST_MODE_TAG));
-        } else {
-            mExitGuestDialog.show();
-        }
-    }
-
-    @Override
-    public void createAndSwitchToGuestUser(@Nullable DialogShower dialogShower) {
-        createGuestAsync(guestId -> {
-            // guestId may be USER_NULL if we haven't reloaded the user list yet.
-            if (guestId != UserHandle.USER_NULL) {
-                mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_ADD);
-                onUserListItemClicked(guestId, UserRecord.createForGuest(), dialogShower);
-            }
-        });
-    }
-
-    @Override
-    public void showAddUserDialog(@Nullable DialogShower dialogShower) {
-        if (mAddUserDialog != null && mAddUserDialog.isShowing()) {
-            mAddUserDialog.cancel();
-        }
-        final UserInfo currentUser = mUserTracker.getUserInfo();
-        mAddUserDialog = new AddUserDialog(
-                mContext,
-                currentUser.getUserHandle(),
-                mKeyguardStateController.isShowing(),
-                /* showEphemeralMessage= */currentUser.isGuest() && currentUser.isEphemeral(),
-                mFalsingManager,
-                mBroadcastSender,
-                mDialogLaunchAnimator);
-        if (dialogShower != null) {
-            dialogShower.showDialog(mAddUserDialog,
-                    new DialogCuj(
-                            InteractionJankMonitor.CUJ_USER_DIALOG_OPEN,
-                            INTERACTION_JANK_ADD_NEW_USER_TAG
-                    ));
-        } else {
-            mAddUserDialog.show();
-        }
-    }
-
-    @Override
-    public void startSupervisedUserActivity() {
-        final Intent intent = new Intent()
-                .setAction(UserManager.ACTION_CREATE_SUPERVISED_USER)
-                .setPackage(mCreateSupervisedUserPackage)
-                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
-        mContext.startActivity(intent);
-    }
-
-    private void listenForCallState() {
-        mTelephonyListenerManager.addCallStateListener(mPhoneStateListener);
-    }
-
-    private final TelephonyCallback.CallStateListener mPhoneStateListener =
-            new TelephonyCallback.CallStateListener() {
-        private int mCallState;
-
-        @Override
-        public void onCallStateChanged(int state) {
-            if (mCallState == state) return;
-            if (DEBUG) Log.v(TAG, "Call state changed: " + state);
-            mCallState = state;
-            refreshUsers(UserHandle.USER_NULL);
-        }
-    };
-
-    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (DEBUG) {
-                Log.v(TAG, "Broadcast: a=" + intent.getAction()
-                        + " user=" + intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1));
-            }
-
-            boolean unpauseRefreshUsers = false;
-            int forcePictureLoadForId = UserHandle.USER_NULL;
-
-            if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
-                if (mExitGuestDialog != null && mExitGuestDialog.isShowing()) {
-                    mExitGuestDialog.cancel();
-                    mExitGuestDialog = null;
-                }
-
-                final int currentId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
-                final UserInfo userInfo = mUserManager.getUserInfo(currentId);
-                final int userCount = mUsers.size();
-                for (int i = 0; i < userCount; i++) {
-                    UserRecord record = mUsers.get(i);
-                    if (record.info == null) continue;
-                    boolean shouldBeCurrent = record.info.id == currentId;
-                    if (record.isCurrent != shouldBeCurrent) {
-                        mUsers.set(i, record.copyWithIsCurrent(shouldBeCurrent));
-                    }
-                    if (shouldBeCurrent && !record.isGuest) {
-                        mLastNonGuestUser = record.info.id;
-                    }
-                    if ((userInfo == null || !userInfo.isAdmin()) && record.isRestricted) {
-                        // Immediately remove restricted records in case the AsyncTask is too slow.
-                        mUsers.remove(i);
-                        i--;
-                    }
-                }
-                notifyUserSwitchCallbacks();
-                notifyAdapters();
-
-                // Disconnect from the old secondary user's service
-                if (mSecondaryUser != UserHandle.USER_NULL) {
-                    context.stopServiceAsUser(mSecondaryUserServiceIntent,
-                            UserHandle.of(mSecondaryUser));
-                    mSecondaryUser = UserHandle.USER_NULL;
-                }
-                // Connect to the new secondary user's service (purely to ensure that a persistent
-                // SystemUI application is created for that user)
-                if (userInfo != null && userInfo.id != UserHandle.USER_SYSTEM) {
-                    context.startServiceAsUser(mSecondaryUserServiceIntent,
-                            UserHandle.of(userInfo.id));
-                    mSecondaryUser = userInfo.id;
-                }
-                unpauseRefreshUsers = true;
-                if (mGuestUserAutoCreated) {
-                    // Guest user must be scheduled for creation AFTER switching to the target user.
-                    // This avoids lock contention which will produce UX bugs on the keyguard
-                    // (b/193933686).
-                    // TODO(b/191067027): Move guest user recreation to system_server
-                    guaranteeGuestPresent();
-                }
-            } else if (Intent.ACTION_USER_INFO_CHANGED.equals(intent.getAction())) {
-                forcePictureLoadForId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
-                        UserHandle.USER_NULL);
-            } else if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
-                // Unlocking the system user may require a refresh
-                int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
-                if (userId != UserHandle.USER_SYSTEM) {
-                    return;
-                }
-            }
-            refreshUsers(forcePictureLoadForId);
-            if (unpauseRefreshUsers) {
-                mUnpauseRefreshUsers.run();
-            }
-        }
-    };
-
-    private final Runnable mUnpauseRefreshUsers = new Runnable() {
-        @Override
-        public void run() {
-            mHandler.removeCallbacks(this);
-            mPauseRefreshUsers = false;
-            refreshUsers(UserHandle.USER_NULL);
-        }
-    };
-
-    @Override
-    public void dump(PrintWriter pw, String[] args) {
-        pw.println("UserSwitcherController state:");
-        pw.println("  mLastNonGuestUser=" + mLastNonGuestUser);
-        pw.print("  mUsers.size="); pw.println(mUsers.size());
-        for (int i = 0; i < mUsers.size(); i++) {
-            final UserRecord u = mUsers.get(i);
-            pw.print("    "); pw.println(u.toString());
-        }
-        pw.println("mSimpleUserSwitcher=" + mSimpleUserSwitcher);
-        pw.println("mGuestUserAutoCreated=" + mGuestUserAutoCreated);
-    }
-
-    @Override
-    public String getCurrentUserName() {
-        if (mUsers.isEmpty()) return null;
-        UserRecord item = mUsers.stream().filter(x -> x.isCurrent).findFirst().orElse(null);
-        if (item == null || item.info == null) return null;
-        if (item.isGuest) return mContext.getString(com.android.internal.R.string.guest_name);
-        return item.info.name;
-    }
-
-    @Override
-    public void onDensityOrFontScaleChanged() {
-        refreshUsers(UserHandle.USER_ALL);
-    }
-
-    @Override
-    public void addAdapter(WeakReference<BaseUserSwitcherAdapter> adapter) {
-        mAdapters.add(adapter);
-    }
-
-    @Override
-    public ArrayList<UserRecord> getUsers() {
-        return mUsers;
-    }
-
-    @Override
-    public void removeGuestUser(@UserIdInt int guestUserId, @UserIdInt int targetUserId) {
-        UserInfo currentUser = mUserTracker.getUserInfo();
-        if (currentUser.id != guestUserId) {
-            Log.w(TAG, "User requesting to start a new session (" + guestUserId + ")"
-                    + " is not current user (" + currentUser.id + ")");
-            return;
-        }
-        if (!currentUser.isGuest()) {
-            Log.w(TAG, "User requesting to start a new session (" + guestUserId + ")"
-                    + " is not a guest");
-            return;
-        }
-
-        boolean marked = mUserManager.markGuestForDeletion(currentUser.id);
-        if (!marked) {
-            Log.w(TAG, "Couldn't mark the guest for deletion for user " + guestUserId);
-            return;
-        }
-
-        if (targetUserId == UserHandle.USER_NULL) {
-            // Create a new guest in the foreground, and then immediately switch to it
-            createGuestAsync(newGuestId -> {
-                if (newGuestId == UserHandle.USER_NULL) {
-                    Log.e(TAG, "Could not create new guest, switching back to system user");
-                    switchToUserId(UserHandle.USER_SYSTEM);
-                    mUserManager.removeUser(currentUser.id);
-                    try {
-                        WindowManagerGlobal.getWindowManagerService().lockNow(/* options= */ null);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Couldn't remove guest because ActivityManager "
-                                + "or WindowManager is dead");
-                    }
-                    return;
-                }
-                switchToUserId(newGuestId);
-                mUserManager.removeUser(currentUser.id);
-            });
-        } else {
-            if (mGuestUserAutoCreated) {
-                mGuestIsResetting.set(true);
-            }
-            switchToUserId(targetUserId);
-            mUserManager.removeUser(currentUser.id);
-        }
-    }
-
-    @Override
-    public void exitGuestUser(@UserIdInt int guestUserId, @UserIdInt int targetUserId,
-            boolean forceRemoveGuestOnExit) {
-        UserInfo currentUser = mUserTracker.getUserInfo();
-        if (currentUser.id != guestUserId) {
-            Log.w(TAG, "User requesting to start a new session (" + guestUserId + ")"
-                    + " is not current user (" + currentUser.id + ")");
-            return;
-        }
-        if (!currentUser.isGuest()) {
-            Log.w(TAG, "User requesting to start a new session (" + guestUserId + ")"
-                    + " is not a guest");
-            return;
-        }
-
-        int newUserId = UserHandle.USER_SYSTEM;
-        if (targetUserId == UserHandle.USER_NULL) {
-            // when target user is not specified switch to last non guest user
-            if (mLastNonGuestUser != UserHandle.USER_SYSTEM) {
-                UserInfo info = mUserManager.getUserInfo(mLastNonGuestUser);
-                if (info != null && info.isEnabled() && info.supportsSwitchToByUser()) {
-                    newUserId = info.id;
-                }
-            }
-        } else {
-            newUserId = targetUserId;
-        }
-
-        if (currentUser.isEphemeral() || forceRemoveGuestOnExit) {
-            mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_REMOVE);
-            removeGuestUser(currentUser.id, newUserId);
-        } else {
-            mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_SWITCH);
-            switchToUserId(newUserId);
-        }
-    }
-
-    private void scheduleGuestCreation() {
-        if (!mGuestCreationScheduled.compareAndSet(false, true)) {
-            return;
-        }
-
-        mLongRunningExecutor.execute(() -> {
-            int newGuestId = createGuest();
-            mGuestCreationScheduled.set(false);
-            mGuestIsResetting.set(false);
-            if (newGuestId == UserHandle.USER_NULL) {
-                Log.w(TAG, "Could not create new guest while exiting existing guest");
-                // Refresh users so that we still display "Guest" if
-                // config_guestUserAutoCreated=true
-                refreshUsers(UserHandle.USER_NULL);
-            }
-        });
-
-    }
-
-    @Override
-    public void schedulePostBootGuestCreation() {
-        if (isDeviceAllowedToAddGuest()) {
-            guaranteeGuestPresent();
-        } else {
-            mDeviceProvisionedController.addCallback(mGuaranteeGuestPresentAfterProvisioned);
-        }
-    }
-
-    private boolean isDeviceAllowedToAddGuest() {
-        return mDeviceProvisionedController.isDeviceProvisioned()
-                && !mDevicePolicyManager.isDeviceManaged();
-    }
-
-    /**
-     * If there is no guest on the device, schedule creation of a new guest user in the background.
-     */
-    private void guaranteeGuestPresent() {
-        if (isDeviceAllowedToAddGuest() && mUserManager.findCurrentGuestUser() == null) {
-            scheduleGuestCreation();
-        }
-    }
-
-    private void createGuestAsync(Consumer<Integer> callback) {
-        final Dialog guestCreationProgressDialog =
-                new UserCreatingDialog(mContext, /* isGuest= */true);
-        guestCreationProgressDialog.show();
-
-        // userManager.createGuest will block the thread so post is needed for the dialog to show
-        mBgExecutor.execute(() -> {
-            final int guestId = createGuest();
-            mUiExecutor.execute(() -> {
-                guestCreationProgressDialog.dismiss();
-                if (guestId == UserHandle.USER_NULL) {
-                    Toast.makeText(mContext,
-                            com.android.settingslib.R.string.add_guest_failed,
-                            Toast.LENGTH_SHORT).show();
-                }
-                callback.accept(guestId);
-            });
-        });
-    }
-
-    /**
-     * Creates a guest user and return its multi-user user ID.
-     *
-     * This method does not check if a guest already exists before it makes a call to
-     * {@link UserManager} to create a new one.
-     *
-     * @return The multi-user user ID of the newly created guest user, or
-     * {@link UserHandle#USER_NULL} if the guest couldn't be created.
-     */
-    private @UserIdInt int createGuest() {
-        UserInfo guest;
-        try {
-            guest = mUserManager.createGuest(mContext);
-        } catch (UserManager.UserOperationException e) {
-            Log.e(TAG, "Couldn't create guest user", e);
-            return UserHandle.USER_NULL;
-        }
-        if (guest == null) {
-            Log.e(TAG, "Couldn't create guest, most likely because there already exists one");
-            return UserHandle.USER_NULL;
-        }
-        return guest.id;
-    }
-
-    @Override
-    public void init(View view) {
-        mView = view;
-    }
-
-    @Override
-    public boolean isKeyguardShowing() {
-        return mKeyguardStateController.isShowing();
-    }
-
-    private boolean shouldUseSimpleUserSwitcher() {
-        int defaultSimpleUserSwitcher = mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_expandLockScreenUserSwitcher) ? 1 : 0;
-        return mGlobalSettings.getIntForUser(SIMPLE_USER_SWITCHER_GLOBAL_SETTING,
-                defaultSimpleUserSwitcher, UserHandle.USER_SYSTEM) != 0;
-    }
-
-    @Override
-    public void startActivity(Intent intent) {
-        mActivityStarter.startActivity(intent, /* dismissShade= */ true);
-    }
-
-    @Override
-    public void addUserSwitchCallback(UserSwitchCallback callback) {
-        mUserSwitchCallbacks.add(callback);
-    }
-
-    @Override
-    public void removeUserSwitchCallback(UserSwitchCallback callback) {
-        mUserSwitchCallbacks.remove(callback);
-    }
-
-    /**
-     *  Notify user switch callbacks that user has switched.
-     */
-    private void notifyUserSwitchCallbacks() {
-        List<UserSwitchCallback> temp;
-        synchronized (mUserSwitchCallbacks) {
-            temp = new ArrayList<>(mUserSwitchCallbacks);
-        }
-        for (UserSwitchCallback callback : temp) {
-            callback.onUserSwitched();
-        }
-    }
-
-    private final KeyguardStateController.Callback mCallback =
-            new KeyguardStateController.Callback() {
-                @Override
-                public void onKeyguardShowingChanged() {
-
-                    // When Keyguard is going away, we don't need to update our items immediately
-                    // which
-                    // helps making the transition faster.
-                    if (!mKeyguardStateController.isShowing()) {
-                        mHandler.post(UserSwitcherControllerOldImpl.this::notifyAdapters);
-                    } else {
-                        notifyAdapters();
-                    }
-                }
-            };
-
-    private final DeviceProvisionedController.DeviceProvisionedListener
-            mGuaranteeGuestPresentAfterProvisioned =
-            new DeviceProvisionedController.DeviceProvisionedListener() {
-                @Override
-                public void onDeviceProvisionedChanged() {
-                    if (isDeviceAllowedToAddGuest()) {
-                        mBgExecutor.execute(
-                                () -> mDeviceProvisionedController.removeCallback(
-                                        mGuaranteeGuestPresentAfterProvisioned));
-                        guaranteeGuestPresent();
-                    }
-                }
-            };
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
index b1b45b5..1b73539 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
@@ -58,8 +58,6 @@
 import com.android.systemui.statusbar.policy.SecurityControllerImpl;
 import com.android.systemui.statusbar.policy.UserInfoController;
 import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
-import com.android.systemui.statusbar.policy.UserSwitcherController;
-import com.android.systemui.statusbar.policy.UserSwitcherControllerImpl;
 import com.android.systemui.statusbar.policy.WalletController;
 import com.android.systemui.statusbar.policy.WalletControllerImpl;
 import com.android.systemui.statusbar.policy.ZenModeController;
@@ -198,8 +196,4 @@
     static DataSaverController provideDataSaverController(NetworkController networkController) {
         return networkController.getDataSaverController();
     }
-
-    /** Binds {@link UserSwitcherController} to its implementation. */
-    @Binds
-    UserSwitcherController bindUserSwitcherController(UserSwitcherControllerImpl impl);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/MultiRippleController.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/ripple/MultiRippleController.kt
rename to packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt
index 48df15c..93e78ac 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/MultiRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.ripple
+package com.android.systemui.surfaceeffects.ripple
 
 import androidx.annotation.VisibleForTesting
 
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/MultiRippleView.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt
similarity index 76%
rename from packages/SystemUI/src/com/android/systemui/ripple/MultiRippleView.kt
rename to packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt
index c7f0b7e..f558fee 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/MultiRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.ripple
+package com.android.systemui.surfaceeffects.ripple
 
 import android.content.Context
 import android.graphics.Canvas
@@ -31,11 +31,21 @@
 class MultiRippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
 
     internal val ripples = ArrayList<RippleAnimation>()
+    private val listeners = ArrayList<RipplesFinishedListener>()
     private val ripplePaint = Paint()
     private var isWarningLogged = false
 
     companion object {
-        const val TAG = "MultiRippleView"
+        private const val TAG = "MultiRippleView"
+
+        interface RipplesFinishedListener {
+            /** Triggered when all the ripples finish running. */
+            fun onRipplesFinish()
+        }
+    }
+
+    fun addRipplesFinishedListener(listener: RipplesFinishedListener) {
+        listeners.add(listener)
     }
 
     override fun onDraw(canvas: Canvas?) {
@@ -62,6 +72,10 @@
             shouldInvalidate = shouldInvalidate || anim.isPlaying()
         }
 
-        if (shouldInvalidate) invalidate()
+        if (shouldInvalidate) {
+            invalidate()
+        } else { // Nothing is playing.
+            listeners.forEach { listener -> listener.onRipplesFinish() }
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleAnimation.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/ripple/RippleAnimation.kt
rename to packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt
index aca9e25..b2f8994 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/RippleAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.ripple
+package com.android.systemui.surfaceeffects.ripple
 
 import android.animation.Animator
 import android.animation.AnimatorListenerAdapter
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleAnimationConfig.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt
similarity index 95%
rename from packages/SystemUI/src/com/android/systemui/ripple/RippleAnimationConfig.kt
rename to packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt
index 8812254..ae73df2 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/RippleAnimationConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt
@@ -1,4 +1,4 @@
-package com.android.systemui.ripple
+package com.android.systemui.surfaceeffects.ripple
 
 import android.graphics.Color
 
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
similarity index 69%
rename from packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
rename to packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
index d2f3a6a..a950d34 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
@@ -13,11 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.ripple
+package com.android.systemui.surfaceeffects.ripple
 
 import android.graphics.PointF
 import android.graphics.RuntimeShader
 import android.util.MathUtils
+import com.android.systemui.surfaceeffects.shaderutil.SdfShaderLibrary
+import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary
 
 /**
  * Shader class that renders an expanding ripple effect. The ripple contains three elements:
@@ -31,7 +33,7 @@
  * Modeled after frameworks/base/graphics/java/android/graphics/drawable/RippleShader.java.
  */
 class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.CIRCLE) :
-        RuntimeShader(buildShader(rippleShape)) {
+    RuntimeShader(buildShader(rippleShape)) {
 
     /** Shapes that the [RippleShader] supports. */
     enum class RippleShape {
@@ -39,25 +41,30 @@
         ROUNDED_BOX,
         ELLIPSE
     }
-    //language=AGSL
+    // language=AGSL
     companion object {
-        private const val SHADER_UNIFORMS = """uniform vec2 in_center;
-                uniform vec2 in_size;
-                uniform float in_progress;
-                uniform float in_cornerRadius;
-                uniform float in_thickness;
-                uniform float in_time;
-                uniform float in_distort_radial;
-                uniform float in_distort_xy;
-                uniform float in_fadeSparkle;
-                uniform float in_fadeFill;
-                uniform float in_fadeRing;
-                uniform float in_blur;
-                uniform float in_pixelDensity;
-                layout(color) uniform vec4 in_color;
-                uniform float in_sparkle_strength;"""
+        private const val SHADER_UNIFORMS =
+            """
+            uniform vec2 in_center;
+            uniform vec2 in_size;
+            uniform float in_progress;
+            uniform float in_cornerRadius;
+            uniform float in_thickness;
+            uniform float in_time;
+            uniform float in_distort_radial;
+            uniform float in_distort_xy;
+            uniform float in_fadeSparkle;
+            uniform float in_fadeFill;
+            uniform float in_fadeRing;
+            uniform float in_blur;
+            uniform float in_pixelDensity;
+            layout(color) uniform vec4 in_color;
+            uniform float in_sparkle_strength;
+        """
 
-        private const val SHADER_CIRCLE_MAIN = """vec4 main(vec2 p) {
+        private const val SHADER_CIRCLE_MAIN =
+            """
+            vec4 main(vec2 p) {
                 vec2 p_distorted = distort(p, in_time, in_distort_radial, in_distort_xy);
                 float radius = in_size.x * 0.5;
                 float sparkleRing = soften(circleRing(p_distorted-in_center, radius), in_blur);
@@ -73,7 +80,9 @@
             }
         """
 
-        private const val SHADER_ROUNDED_BOX_MAIN = """vec4 main(vec2 p) {
+        private const val SHADER_ROUNDED_BOX_MAIN =
+            """
+            vec4 main(vec2 p) {
                 float sparkleRing = soften(roundedBoxRing(p-in_center, in_size, in_cornerRadius,
                     in_thickness), in_blur);
                 float inside = soften(sdRoundedBox(p-in_center, in_size * 1.2, in_cornerRadius),
@@ -89,7 +98,9 @@
             }
         """
 
-        private const val SHADER_ELLIPSE_MAIN = """vec4 main(vec2 p) {
+        private const val SHADER_ELLIPSE_MAIN =
+            """
+            vec4 main(vec2 p) {
                 vec2 p_distorted = distort(p, in_time, in_distort_radial, in_distort_xy);
 
                 float sparkleRing = soften(ellipseRing(p_distorted-in_center, in_size), in_blur);
@@ -105,22 +116,31 @@
             }
         """
 
-        private const val CIRCLE_SHADER = SHADER_UNIFORMS + RippleShaderUtilLibrary.SHADER_LIB +
-                SdfShaderLibrary.SHADER_SDF_OPERATION_LIB + SdfShaderLibrary.CIRCLE_SDF +
+        private const val CIRCLE_SHADER =
+            SHADER_UNIFORMS +
+                ShaderUtilLibrary.SHADER_LIB +
+                SdfShaderLibrary.SHADER_SDF_OPERATION_LIB +
+                SdfShaderLibrary.CIRCLE_SDF +
                 SHADER_CIRCLE_MAIN
-        private const val ROUNDED_BOX_SHADER = SHADER_UNIFORMS +
-                RippleShaderUtilLibrary.SHADER_LIB + SdfShaderLibrary.SHADER_SDF_OPERATION_LIB +
-                SdfShaderLibrary.ROUNDED_BOX_SDF + SHADER_ROUNDED_BOX_MAIN
-        private const val ELLIPSE_SHADER = SHADER_UNIFORMS + RippleShaderUtilLibrary.SHADER_LIB +
-                SdfShaderLibrary.SHADER_SDF_OPERATION_LIB + SdfShaderLibrary.ELLIPSE_SDF +
+        private const val ROUNDED_BOX_SHADER =
+            SHADER_UNIFORMS +
+                ShaderUtilLibrary.SHADER_LIB +
+                SdfShaderLibrary.SHADER_SDF_OPERATION_LIB +
+                SdfShaderLibrary.ROUNDED_BOX_SDF +
+                SHADER_ROUNDED_BOX_MAIN
+        private const val ELLIPSE_SHADER =
+            SHADER_UNIFORMS +
+                ShaderUtilLibrary.SHADER_LIB +
+                SdfShaderLibrary.SHADER_SDF_OPERATION_LIB +
+                SdfShaderLibrary.ELLIPSE_SDF +
                 SHADER_ELLIPSE_MAIN
 
         private fun buildShader(rippleShape: RippleShape): String =
-                when (rippleShape) {
-                    RippleShape.CIRCLE -> CIRCLE_SHADER
-                    RippleShape.ROUNDED_BOX -> ROUNDED_BOX_SHADER
-                    RippleShape.ELLIPSE -> ELLIPSE_SHADER
-                }
+            when (rippleShape) {
+                RippleShape.CIRCLE -> CIRCLE_SHADER
+                RippleShape.ROUNDED_BOX -> ROUNDED_BOX_SHADER
+                RippleShape.ELLIPSE -> ELLIPSE_SHADER
+            }
 
         private fun subProgress(start: Float, end: Float, progress: Float): Float {
             val min = Math.min(start, end)
@@ -130,9 +150,7 @@
         }
     }
 
-    /**
-     * Sets the center position of the ripple.
-     */
+    /** Sets the center position of the ripple. */
     fun setCenter(x: Float, y: Float) {
         setFloatUniform("in_center", x, y)
     }
@@ -144,21 +162,21 @@
         maxSize.y = height
     }
 
-    /**
-     * Progress of the ripple. Float value between [0, 1].
-     */
+    /** Progress of the ripple. Float value between [0, 1]. */
     var progress: Float = 0.0f
         set(value) {
             field = value
             setFloatUniform("in_progress", value)
             val curvedProg = 1 - (1 - value) * (1 - value) * (1 - value)
 
-            setFloatUniform("in_size", /* width= */ maxSize.x * curvedProg,
-                    /* height= */ maxSize.y * curvedProg)
+            setFloatUniform(
+                "in_size",
+                /* width= */ maxSize.x * curvedProg,
+                /* height= */ maxSize.y * curvedProg
+            )
             setFloatUniform("in_thickness", maxSize.y * curvedProg * 0.5f)
             // radius should not exceed width and height values.
-            setFloatUniform("in_cornerRadius",
-                    Math.min(maxSize.x, maxSize.y) * curvedProg)
+            setFloatUniform("in_cornerRadius", Math.min(maxSize.x, maxSize.y) * curvedProg)
 
             setFloatUniform("in_blur", MathUtils.lerp(1.25f, 0.5f, value))
 
@@ -175,18 +193,14 @@
             setFloatUniform("in_fadeRing", Math.min(fadeIn, 1 - fadeOutRipple))
         }
 
-    /**
-     * Play time since the start of the effect.
-     */
+    /** Play time since the start of the effect. */
     var time: Float = 0.0f
         set(value) {
             field = value
             setFloatUniform("in_time", value)
         }
 
-    /**
-     * A hex value representing the ripple color, in the format of ARGB
-     */
+    /** A hex value representing the ripple color, in the format of ARGB */
     var color: Int = 0xffffff
         set(value) {
             field = value
@@ -194,9 +208,9 @@
         }
 
     /**
-     * Noise sparkle intensity. Expected value between [0, 1]. The sparkle is white, and thus
-     * with strength 0 it's transparent, leaving the ripple fully smooth, while with strength 1
-     * it's opaque white and looks the most grainy.
+     * Noise sparkle intensity. Expected value between [0, 1]. The sparkle is white, and thus with
+     * strength 0 it's transparent, leaving the ripple fully smooth, while with strength 1 it's
+     * opaque white and looks the most grainy.
      */
     var sparkleStrength: Float = 0.0f
         set(value) {
@@ -204,9 +218,7 @@
             setFloatUniform("in_sparkle_strength", value)
         }
 
-    /**
-     * Distortion strength of the ripple. Expected value between[0, 1].
-     */
+    /** Distortion strength of the ripple. Expected value between[0, 1]. */
     var distortionStrength: Float = 0.0f
         set(value) {
             field = value
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
similarity index 78%
rename from packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
rename to packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
index a6d7930..2994694 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.ripple
+package com.android.systemui.surfaceeffects.ripple
 
 import android.animation.Animator
 import android.animation.AnimatorListenerAdapter
@@ -26,7 +26,7 @@
 import android.util.AttributeSet
 import android.view.View
 import androidx.core.graphics.ColorUtils
-import com.android.systemui.ripple.RippleShader.RippleShape
+import com.android.systemui.surfaceeffects.ripple.RippleShader.RippleShape
 
 /**
  * A generic expanding ripple effect.
@@ -98,15 +98,18 @@
             rippleShader.time = now.toFloat()
             invalidate()
         }
-        animator.addListener(object : AnimatorListenerAdapter() {
-            override fun onAnimationEnd(animation: Animator?) {
-                onAnimationEnd?.run()
+        animator.addListener(
+            object : AnimatorListenerAdapter() {
+                override fun onAnimationEnd(animation: Animator?) {
+                    onAnimationEnd?.run()
+                }
             }
-        })
+        )
         animator.start()
     }
 
-    /** Set the color to be used for the ripple.
+    /**
+     * Set the color to be used for the ripple.
      *
      * The alpha value of the color will be applied to the ripple. The alpha range is [0-100].
      */
@@ -123,9 +126,7 @@
         rippleShader.rippleFill = rippleFill
     }
 
-    /**
-     * Set the intensity of the sparkles.
-     */
+    /** Set the intensity of the sparkles. */
     fun setSparkleStrength(strength: Float) {
         rippleShader.sparkleStrength = strength
     }
@@ -143,20 +144,30 @@
         // active effect area. Values here should be kept in sync with the animation implementation
         // in the ripple shader.
         if (rippleShape == RippleShape.CIRCLE) {
-            val maskRadius = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
-                    (1 - rippleShader.progress)) * maxWidth
+            val maskRadius =
+                (1 -
+                    (1 - rippleShader.progress) *
+                        (1 - rippleShader.progress) *
+                        (1 - rippleShader.progress)) * maxWidth
             canvas.drawCircle(centerX, centerY, maskRadius, ripplePaint)
         } else {
-            val maskWidth = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
-                    (1 - rippleShader.progress)) * maxWidth * 2
-            val maskHeight = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
-                    (1 - rippleShader.progress)) * maxHeight * 2
+            val maskWidth =
+                (1 -
+                    (1 - rippleShader.progress) *
+                        (1 - rippleShader.progress) *
+                        (1 - rippleShader.progress)) * maxWidth * 2
+            val maskHeight =
+                (1 -
+                    (1 - rippleShader.progress) *
+                        (1 - rippleShader.progress) *
+                        (1 - rippleShader.progress)) * maxHeight * 2
             canvas.drawRect(
-                    /* left= */ centerX - maskWidth,
-                    /* top= */ centerY - maskHeight,
-                    /* right= */ centerX + maskWidth,
-                    /* bottom= */ centerY + maskHeight,
-                    ripplePaint)
+                /* left= */ centerX - maskWidth,
+                /* top= */ centerY - maskHeight,
+                /* right= */ centerX + maskWidth,
+                /* bottom= */ centerY + maskHeight,
+                ripplePaint
+            )
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/SdfShaderLibrary.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt
similarity index 91%
rename from packages/SystemUI/src/com/android/systemui/ripple/SdfShaderLibrary.kt
rename to packages/SystemUI/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt
index 5e256c6..8b2f466 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/SdfShaderLibrary.kt
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt
@@ -13,13 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.ripple
+package com.android.systemui.surfaceeffects.shaderutil
 
 /** Library class that contains 2D signed distance functions. */
 class SdfShaderLibrary {
-    //language=AGSL
+    // language=AGSL
     companion object {
-        const val CIRCLE_SDF = """
+        const val CIRCLE_SDF =
+            """
             float sdCircle(vec2 p, float r) {
                 return (length(p)-r) / r;
             }
@@ -34,7 +35,8 @@
             }
         """
 
-        const val ROUNDED_BOX_SDF = """
+        const val ROUNDED_BOX_SDF =
+            """
             float sdRoundedBox(vec2 p, vec2 size, float cornerRadius) {
                 size *= 0.5;
                 cornerRadius *= 0.5;
@@ -58,7 +60,8 @@
         // Used non-trigonometry parametrization and Halley's method (iterative) for root finding.
         // This is more expensive than the regular circle SDF, recommend to use the circle SDF if
         // possible.
-        const val ELLIPSE_SDF = """float sdEllipse(vec2 p, vec2 wh) {
+        const val ELLIPSE_SDF =
+            """float sdEllipse(vec2 p, vec2 wh) {
             wh *= 0.5;
 
             // symmetry
@@ -98,7 +101,8 @@
         }
         """
 
-        const val SHADER_SDF_OPERATION_LIB = """
+        const val SHADER_SDF_OPERATION_LIB =
+            """
             float soften(float d, float blur) {
                 float blurHalf = blur * 0.5;
                 return smoothstep(-blurHalf, blurHalf, d);
diff --git a/packages/SystemUI/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt
new file mode 100644
index 0000000..d78e0c1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.surfaceeffects.shaderutil
+
+/** A common utility functions that are used for computing shaders. */
+class ShaderUtilLibrary {
+    // language=AGSL
+    companion object {
+        const val SHADER_LIB =
+            """
+            float triangleNoise(vec2 n) {
+                n  = fract(n * vec2(5.3987, 5.4421));
+                n += dot(n.yx, n.xy + vec2(21.5351, 14.3137));
+                float xy = n.x * n.y;
+                // compute in [0..2[ and remap to [-1.0..1.0[
+                return fract(xy * 95.4307) + fract(xy * 75.04961) - 1.0;
+            }
+
+            const float PI = 3.1415926535897932384626;
+
+            float sparkles(vec2 uv, float t) {
+                float n = triangleNoise(uv);
+                float s = 0.0;
+                for (float i = 0; i < 4; i += 1) {
+                    float l = i * 0.01;
+                    float h = l + 0.1;
+                    float o = smoothstep(n - l, h, n);
+                    o *= abs(sin(PI * o * (t + 0.55 * i)));
+                    s += o;
+                }
+                return s;
+            }
+
+            vec2 distort(vec2 p, float time, float distort_amount_radial,
+                float distort_amount_xy) {
+                    float angle = atan(p.y, p.x);
+                      return p + vec2(sin(angle * 8 + time * 0.003 + 1.641),
+                                cos(angle * 5 + 2.14 + time * 0.00412)) * distort_amount_radial
+                         + vec2(sin(p.x * 0.01 + time * 0.00215 + 0.8123),
+                                cos(p.y * 0.01 + time * 0.005931)) * distort_amount_xy;
+            }
+
+            // Return range [-1, 1].
+            vec3 hash(vec3 p) {
+                p = fract(p * vec3(.3456, .1234, .9876));
+                p += dot(p, p.yxz + 43.21);
+                p = (p.xxy + p.yxx) * p.zyx;
+                return (fract(sin(p) * 4567.1234567) - .5) * 2.;
+            }
+
+            // Skew factors (non-uniform).
+            const float SKEW = 0.3333333;  // 1/3
+            const float UNSKEW = 0.1666667;  // 1/6
+
+            // Return range roughly [-1,1].
+            // It's because the hash function (that returns a random gradient vector) returns
+            // different magnitude of vectors. Noise doesn't have to be in the precise range thus
+            // skipped normalize.
+            float simplex3d(vec3 p) {
+                // Skew the input coordinate, so that we get squashed cubical grid
+                vec3 s = floor(p + (p.x + p.y + p.z) * SKEW);
+
+                // Unskew back
+                vec3 u = s - (s.x + s.y + s.z) * UNSKEW;
+
+                // Unskewed coordinate that is relative to p, to compute the noise contribution
+                // based on the distance.
+                vec3 c0 = p - u;
+
+                // We have six simplices (in this case tetrahedron, since we are in 3D) that we
+                // could possibly in.
+                // Here, we are finding the correct tetrahedron (simplex shape), and traverse its
+                // four vertices (c0..3) when computing noise contribution.
+                // The way we find them is by comparing c0's x,y,z values.
+                // For example in 2D, we can find the triangle (simplex shape in 2D) that we are in
+                // by comparing x and y values. i.e. x>y lower, x<y, upper triangle.
+                // Same applies in 3D.
+                //
+                // Below indicates the offsets (or offset directions) when c0=(x0,y0,z0)
+                // x0>y0>z0: (1,0,0), (1,1,0), (1,1,1)
+                // x0>z0>y0: (1,0,0), (1,0,1), (1,1,1)
+                // z0>x0>y0: (0,0,1), (1,0,1), (1,1,1)
+                // z0>y0>x0: (0,0,1), (0,1,1), (1,1,1)
+                // y0>z0>x0: (0,1,0), (0,1,1), (1,1,1)
+                // y0>x0>z0: (0,1,0), (1,1,0), (1,1,1)
+                //
+                // The rule is:
+                // * For offset1, set 1 at the max component, otherwise 0.
+                // * For offset2, set 0 at the min component, otherwise 1.
+                // * For offset3, set 1 for all.
+                //
+                // Encode x0-y0, y0-z0, z0-x0 in a vec3
+                vec3 en = c0 - c0.yzx;
+                // Each represents whether x0>y0, y0>z0, z0>x0
+                en = step(vec3(0.), en);
+                // en.zxy encodes z0>x0, x0>y0, y0>x0
+                vec3 offset1 = en * (1. - en.zxy); // find max
+                vec3 offset2 = 1. - en.zxy * (1. - en); // 1-(find min)
+                vec3 offset3 = vec3(1.);
+
+                vec3 c1 = c0 - offset1 + UNSKEW;
+                vec3 c2 = c0 - offset2 + UNSKEW * 2.;
+                vec3 c3 = c0 - offset3 + UNSKEW * 3.;
+
+                // Kernel summation: dot(max(0, r^2-d^2))^4, noise contribution)
+                //
+                // First compute d^2, squared distance to the point.
+                vec4 w; // w = max(0, r^2 - d^2))
+                w.x = dot(c0, c0);
+                w.y = dot(c1, c1);
+                w.z = dot(c2, c2);
+                w.w = dot(c3, c3);
+
+                // Noise contribution should decay to zero before they cross the simplex boundary.
+                // Usually r^2 is 0.5 or 0.6;
+                // 0.5 ensures continuity but 0.6 increases the visual quality for the application
+                // where discontinuity isn't noticeable.
+                w = max(0.6 - w, 0.);
+
+                // Noise contribution from each point.
+                vec4 nc;
+                nc.x = dot(hash(s), c0);
+                nc.y = dot(hash(s + offset1), c1);
+                nc.z = dot(hash(s + offset2), c2);
+                nc.w = dot(hash(s + offset3), c3);
+
+                nc *= w*w*w*w;
+
+                // Add all the noise contributions.
+                // Should multiply by the possible max contribution to adjust the range in [-1,1].
+                return dot(vec4(32.), nc);
+            }
+            """
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt
new file mode 100644
index 0000000..5ac3aad7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.surfaceeffects.turbulencenoise
+
+import android.graphics.BlendMode
+import android.graphics.Color
+
+/** Turbulence noise animation configuration. */
+data class TurbulenceNoiseAnimationConfig(
+    /** The number of grids that is used to generate noise. */
+    val gridCount: Float = DEFAULT_NOISE_GRID_COUNT,
+
+    /** Multiplier for the noise luma matte. Increase this for brighter effects. */
+    val luminosityMultiplier: Float = DEFAULT_LUMINOSITY_MULTIPLIER,
+
+    /**
+     * Noise move speed variables.
+     *
+     * Its sign determines the direction; magnitude determines the speed. <ul>
+     * ```
+     *     <li> [noiseMoveSpeedX] positive: right to left; negative: left to right.
+     *     <li> [noiseMoveSpeedY] positive: bottom to top; negative: top to bottom.
+     *     <li> [noiseMoveSpeedZ] its sign doesn't matter much, as it moves in Z direction. Use it
+     *     to add turbulence in place.
+     * ```
+     * </ul>
+     */
+    val noiseMoveSpeedX: Float = 0f,
+    val noiseMoveSpeedY: Float = 0f,
+    val noiseMoveSpeedZ: Float = DEFAULT_NOISE_SPEED_Z,
+
+    /** Color of the effect. */
+    var color: Int = DEFAULT_COLOR,
+    /** Background color of the effect. */
+    val backgroundColor: Int = DEFAULT_BACKGROUND_COLOR,
+    val opacity: Int = DEFAULT_OPACITY,
+    val width: Float = 0f,
+    val height: Float = 0f,
+    val duration: Float = DEFAULT_NOISE_DURATION_IN_MILLIS,
+    val pixelDensity: Float = 1f,
+    val blendMode: BlendMode = DEFAULT_BLEND_MODE,
+    val onAnimationEnd: Runnable? = null
+) {
+    companion object {
+        const val DEFAULT_NOISE_DURATION_IN_MILLIS = 7500F
+        const val DEFAULT_LUMINOSITY_MULTIPLIER = 1f
+        const val DEFAULT_NOISE_GRID_COUNT = 1.2f
+        const val DEFAULT_NOISE_SPEED_Z = 0.3f
+        const val DEFAULT_OPACITY = 150 // full opacity is 255.
+        const val DEFAULT_COLOR = Color.WHITE
+        const val DEFAULT_BACKGROUND_COLOR = Color.BLACK
+        val DEFAULT_BLEND_MODE = BlendMode.SRC_OVER
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt
new file mode 100644
index 0000000..4c7e5f4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.surfaceeffects.turbulencenoise
+
+/** A controller that plays [TurbulenceNoiseView]. */
+class TurbulenceNoiseController(private val turbulenceNoiseView: TurbulenceNoiseView) {
+    /** Updates the color of the noise. */
+    fun updateNoiseColor(color: Int) {
+        turbulenceNoiseView.updateColor(color)
+    }
+
+    // TODO: add cancel and/ or pause once design requirements become clear.
+    /** Plays [TurbulenceNoiseView] with the given config. */
+    fun play(turbulenceNoiseAnimationConfig: TurbulenceNoiseAnimationConfig) {
+        turbulenceNoiseView.play(turbulenceNoiseAnimationConfig)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
new file mode 100644
index 0000000..19c114d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.surfaceeffects.turbulencenoise
+
+import android.graphics.RuntimeShader
+import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary
+import java.lang.Float.max
+
+/** Shader that renders turbulence simplex noise, with no octave. */
+class TurbulenceNoiseShader : RuntimeShader(TURBULENCE_NOISE_SHADER) {
+    // language=AGSL
+    companion object {
+        private const val UNIFORMS =
+            """
+            uniform float in_gridNum;
+            uniform vec3 in_noiseMove;
+            uniform vec2 in_size;
+            uniform float in_aspectRatio;
+            uniform float in_opacity;
+            uniform float in_pixelDensity;
+            layout(color) uniform vec4 in_color;
+            layout(color) uniform vec4 in_backgroundColor;
+        """
+
+        private const val SHADER_LIB =
+            """
+            float getLuminosity(vec3 c) {
+                return 0.3*c.r + 0.59*c.g + 0.11*c.b;
+            }
+
+            vec3 maskLuminosity(vec3 dest, float lum) {
+                dest.rgb *= vec3(lum);
+                // Clip back into the legal range
+                dest = clamp(dest, vec3(0.), vec3(1.0));
+                return dest;
+            }
+        """
+
+        private const val MAIN_SHADER =
+            """
+            vec4 main(vec2 p) {
+                vec2 uv = p / in_size.xy;
+                uv.x *= in_aspectRatio;
+
+                vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum;
+                float luma = simplex3d(noiseP) * in_opacity;
+                vec3 mask = maskLuminosity(in_color.rgb, luma);
+                vec3 color = in_backgroundColor.rgb + mask * 0.6;
+
+                // Add dither with triangle distribution to avoid color banding. Ok to dither in the
+                // shader here as we are in gamma space.
+                float dither = triangleNoise(p * in_pixelDensity) / 255.;
+
+                // The result color should be pre-multiplied, i.e. [R*A, G*A, B*A, A], thus need to
+                // multiply rgb with a to get the correct result.
+                color = (color + dither.rrr) * in_color.a;
+                return vec4(color, in_color.a);
+            }
+        """
+
+        private const val TURBULENCE_NOISE_SHADER =
+            ShaderUtilLibrary.SHADER_LIB + UNIFORMS + SHADER_LIB + MAIN_SHADER
+    }
+
+    /** Sets the number of grid for generating noise. */
+    fun setGridCount(gridNumber: Float = 1.0f) {
+        setFloatUniform("in_gridNum", gridNumber)
+    }
+
+    /**
+     * Sets the pixel density of the screen.
+     *
+     * Used it for noise dithering.
+     */
+    fun setPixelDensity(pixelDensity: Float) {
+        setFloatUniform("in_pixelDensity", pixelDensity)
+    }
+
+    /** Sets the noise color of the effect. */
+    fun setColor(color: Int) {
+        setColorUniform("in_color", color)
+    }
+
+    /** Sets the background color of the effect. */
+    fun setBackgroundColor(color: Int) {
+        setColorUniform("in_backgroundColor", color)
+    }
+
+    /**
+     * Sets the opacity to achieve fade in/ out of the animation.
+     *
+     * Expected value range is [1, 0].
+     */
+    fun setOpacity(opacity: Float) {
+        setFloatUniform("in_opacity", opacity)
+    }
+
+    /** Sets the size of the shader. */
+    fun setSize(width: Float, height: Float) {
+        setFloatUniform("in_size", width, height)
+        setFloatUniform("in_aspectRatio", width / max(height, 0.001f))
+    }
+
+    /** Sets noise move speed in x, y, and z direction. */
+    fun setNoiseMove(x: Float, y: Float, z: Float) {
+        setFloatUniform("in_noiseMove", x, y, z)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
new file mode 100644
index 0000000..8649d59
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.surfaceeffects.turbulencenoise
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.util.AttributeSet
+import android.view.View
+import androidx.annotation.VisibleForTesting
+import androidx.core.graphics.ColorUtils
+import java.util.Random
+import kotlin.math.sin
+
+/** View that renders turbulence noise effect. */
+class TurbulenceNoiseView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
+
+    companion object {
+        private const val MS_TO_SEC = 0.001f
+        private const val TWO_PI = Math.PI.toFloat() * 2f
+    }
+
+    @VisibleForTesting val turbulenceNoiseShader = TurbulenceNoiseShader()
+    private val paint = Paint().apply { this.shader = turbulenceNoiseShader }
+    private val random = Random()
+    private val animator: ValueAnimator = ValueAnimator.ofFloat(0f, 1f)
+    private var config: TurbulenceNoiseAnimationConfig? = null
+
+    val isPlaying: Boolean
+        get() = animator.isRunning
+
+    init {
+        // Only visible during the animation.
+        visibility = INVISIBLE
+    }
+
+    /** Updates the color during the animation. No-op if there's no animation playing. */
+    fun updateColor(color: Int) {
+        config?.let {
+            it.color = color
+            applyConfig(it)
+        }
+    }
+
+    override fun onDraw(canvas: Canvas?) {
+        if (canvas == null || !canvas.isHardwareAccelerated) {
+            // Drawing with the turbulence noise shader requires hardware acceleration, so skip
+            // if it's unsupported.
+            return
+        }
+
+        canvas.drawPaint(paint)
+    }
+
+    internal fun play(config: TurbulenceNoiseAnimationConfig) {
+        if (isPlaying) {
+            return // Ignore if the animation is playing.
+        }
+        visibility = VISIBLE
+        applyConfig(config)
+
+        // Add random offset to avoid same patterned noise.
+        val offsetX = random.nextFloat()
+        val offsetY = random.nextFloat()
+
+        animator.duration = config.duration.toLong()
+        animator.addUpdateListener { updateListener ->
+            val timeInSec = updateListener.currentPlayTime * MS_TO_SEC
+            // Remap [0,1] to [0, 2*PI]
+            val progress = TWO_PI * updateListener.animatedValue as Float
+
+            turbulenceNoiseShader.setNoiseMove(
+                offsetX + timeInSec * config.noiseMoveSpeedX,
+                offsetY + timeInSec * config.noiseMoveSpeedY,
+                timeInSec * config.noiseMoveSpeedZ
+            )
+
+            // Fade in and out the noise as the animation progress.
+            // TODO: replace it with a better curve
+            turbulenceNoiseShader.setOpacity(sin(TWO_PI - progress) * config.luminosityMultiplier)
+
+            invalidate()
+        }
+
+        animator.addListener(
+            object : AnimatorListenerAdapter() {
+                override fun onAnimationEnd(animation: Animator) {
+                    visibility = INVISIBLE
+                    config.onAnimationEnd?.run()
+                }
+            }
+        )
+        animator.start()
+    }
+
+    private fun applyConfig(config: TurbulenceNoiseAnimationConfig) {
+        this.config = config
+        with(turbulenceNoiseShader) {
+            setGridCount(config.gridCount)
+            setColor(ColorUtils.setAlphaComponent(config.color, config.opacity))
+            setBackgroundColor(config.backgroundColor)
+            setSize(config.width, config.height)
+            setPixelDensity(config.pixelDensity)
+        }
+        paint.blendMode = config.blendMode
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
index 3507cb7..095718b 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
@@ -46,6 +46,7 @@
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.SystemUIApplication;
+import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.util.NotificationChannels;
 
@@ -61,15 +62,24 @@
     private static final String ACTION_SNOOZE_VOLUME = "com.android.systemui.action.SNOOZE_VOLUME";
     private static final String ACTION_FINISH_WIZARD = "com.android.systemui.action.FINISH_WIZARD";
     private final Context mContext;
+    private final BroadcastDispatcher mBroadcastDispatcher;
 
     // TODO: delay some notifications to avoid bumpy fast operations
 
-    private NotificationManager mNotificationManager;
-    private StorageManager mStorageManager;
+    private final NotificationManager mNotificationManager;
+    private final StorageManager mStorageManager;
 
     @Inject
-    public StorageNotification(Context context) {
+    public StorageNotification(
+            Context context,
+            BroadcastDispatcher broadcastDispatcher,
+            NotificationManager notificationManager,
+            StorageManager storageManager
+    ) {
         mContext = context;
+        mBroadcastDispatcher = broadcastDispatcher;
+        mNotificationManager = notificationManager;
+        mStorageManager = storageManager;
     }
 
     private static class MoveInfo {
@@ -168,17 +178,22 @@
 
     @Override
     public void start() {
-        mNotificationManager = mContext.getSystemService(NotificationManager.class);
-
-        mStorageManager = mContext.getSystemService(StorageManager.class);
         mStorageManager.registerListener(mListener);
 
-        mContext.registerReceiver(mSnoozeReceiver, new IntentFilter(ACTION_SNOOZE_VOLUME),
-                android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null,
-                Context.RECEIVER_EXPORTED_UNAUDITED);
-        mContext.registerReceiver(mFinishReceiver, new IntentFilter(ACTION_FINISH_WIZARD),
-                android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null,
-                Context.RECEIVER_EXPORTED_UNAUDITED);
+        mBroadcastDispatcher.registerReceiver(
+                mSnoozeReceiver,
+                new IntentFilter(ACTION_SNOOZE_VOLUME),
+                null,
+                null,
+                Context.RECEIVER_EXPORTED_UNAUDITED,
+                android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
+        mBroadcastDispatcher.registerReceiver(
+                mFinishReceiver,
+                new IntentFilter(ACTION_FINISH_WIZARD),
+                null,
+                null,
+                Context.RECEIVER_EXPORTED_UNAUDITED,
+                android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
 
         // Kick current state into place
         final List<DiskInfo> disks = mStorageManager.getDisks();
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index ffaf524..ed53de7 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -19,31 +19,18 @@
 
 import android.content.Context
 import android.content.pm.UserInfo
-import android.graphics.drawable.BitmapDrawable
-import android.graphics.drawable.Drawable
 import android.os.UserHandle
 import android.os.UserManager
 import android.provider.Settings
 import androidx.annotation.VisibleForTesting
-import androidx.appcompat.content.res.AppCompatResources
-import com.android.internal.util.UserIcons
-import com.android.systemui.R
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.common.shared.model.Text
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.policy.UserSwitcherController
 import com.android.systemui.user.data.model.UserSwitcherSettingsModel
-import com.android.systemui.user.data.source.UserRecord
-import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
-import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.user.shared.model.UserModel
 import com.android.systemui.util.settings.GlobalSettings
 import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
 import java.util.concurrent.atomic.AtomicBoolean
@@ -55,7 +42,6 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.map
@@ -72,15 +58,6 @@
  * upstream changes.
  */
 interface UserRepository {
-    /** List of all users on the device. */
-    val users: Flow<List<UserModel>>
-
-    /** The currently-selected user. */
-    val selectedUser: Flow<UserModel>
-
-    /** List of available user-related actions. */
-    val actions: Flow<List<UserActionModel>>
-
     /** User switcher related settings. */
     val userSwitcherSettings: Flow<UserSwitcherSettingsModel>
 
@@ -93,9 +70,6 @@
     /** User ID of the last non-guest selected user. */
     val lastSelectedNonGuestUserId: Int
 
-    /** Whether actions are available even when locked. */
-    val isActionableWhenLocked: Flow<Boolean>
-
     /** Whether the device is configured to always have a guest user available. */
     val isGuestUserAutoCreated: Boolean
 
@@ -125,18 +99,13 @@
 constructor(
     @Application private val appContext: Context,
     private val manager: UserManager,
-    private val controller: UserSwitcherController,
     @Application private val applicationScope: CoroutineScope,
     @Main private val mainDispatcher: CoroutineDispatcher,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
     private val globalSettings: GlobalSettings,
     private val tracker: UserTracker,
-    private val featureFlags: FeatureFlags,
 ) : UserRepository {
 
-    private val isNewImpl: Boolean
-        get() = !featureFlags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)
-
     private val _userSwitcherSettings = MutableStateFlow(runBlocking { getSettings() })
     override val userSwitcherSettings: Flow<UserSwitcherSettingsModel> =
         _userSwitcherSettings.asStateFlow().filterNotNull()
@@ -150,58 +119,11 @@
     override var lastSelectedNonGuestUserId: Int = UserHandle.USER_SYSTEM
         private set
 
-    private val userRecords: Flow<List<UserRecord>> = conflatedCallbackFlow {
-        fun send() {
-            trySendWithFailureLogging(
-                controller.users,
-                TAG,
-            )
-        }
-
-        val callback = UserSwitcherController.UserSwitchCallback { send() }
-
-        controller.addUserSwitchCallback(callback)
-        send()
-
-        awaitClose { controller.removeUserSwitchCallback(callback) }
-    }
-
-    override val users: Flow<List<UserModel>> =
-        userRecords.map { records -> records.filter { it.isUser() }.map { it.toUserModel() } }
-
-    override val selectedUser: Flow<UserModel> =
-        users.map { users -> users.first { user -> user.isSelected } }
-
-    override val actions: Flow<List<UserActionModel>> =
-        userRecords.map { records -> records.filter { it.isNotUser() }.map { it.toActionModel() } }
-
-    override val isActionableWhenLocked: Flow<Boolean> =
-        if (isNewImpl) {
-            emptyFlow()
-        } else {
-            controller.isAddUsersFromLockScreenEnabled
-        }
-
     override val isGuestUserAutoCreated: Boolean =
-        if (isNewImpl) {
-            appContext.resources.getBoolean(com.android.internal.R.bool.config_guestUserAutoCreated)
-        } else {
-            controller.isGuestUserAutoCreated
-        }
+        appContext.resources.getBoolean(com.android.internal.R.bool.config_guestUserAutoCreated)
 
     private var _isGuestUserResetting: Boolean = false
-    override var isGuestUserResetting: Boolean =
-        if (isNewImpl) {
-            _isGuestUserResetting
-        } else {
-            controller.isGuestUserResetting
-        }
-        set(value) =
-            if (isNewImpl) {
-                _isGuestUserResetting = value
-            } else {
-                error("Not supported in the old implementation!")
-            }
+    override var isGuestUserResetting: Boolean = _isGuestUserResetting
 
     override val isGuestUserCreationScheduled = AtomicBoolean()
 
@@ -210,10 +132,8 @@
     override var isRefreshUsersPaused: Boolean = false
 
     init {
-        if (isNewImpl) {
-            observeSelectedUser()
-            observeUserSettings()
-        }
+        observeSelectedUser()
+        observeUserSettings()
     }
 
     override fun refreshUsers() {
@@ -327,64 +247,6 @@
         }
     }
 
-    private fun UserRecord.isUser(): Boolean {
-        return when {
-            isAddUser -> false
-            isAddSupervisedUser -> false
-            isManageUsers -> false
-            isGuest -> info != null
-            else -> true
-        }
-    }
-
-    private fun UserRecord.isNotUser(): Boolean {
-        return !isUser()
-    }
-
-    private fun UserRecord.toUserModel(): UserModel {
-        return UserModel(
-            id = resolveId(),
-            name = getUserName(this),
-            image = getUserImage(this),
-            isSelected = isCurrent,
-            isSelectable = isSwitchToEnabled || isGuest,
-            isGuest = isGuest,
-        )
-    }
-
-    private fun UserRecord.toActionModel(): UserActionModel {
-        return when {
-            isAddUser -> UserActionModel.ADD_USER
-            isAddSupervisedUser -> UserActionModel.ADD_SUPERVISED_USER
-            isGuest -> UserActionModel.ENTER_GUEST_MODE
-            isManageUsers -> UserActionModel.NAVIGATE_TO_USER_MANAGEMENT
-            else -> error("Don't know how to convert to UserActionModel: $this")
-        }
-    }
-
-    private fun getUserName(record: UserRecord): Text {
-        val resourceId: Int? = LegacyUserUiHelper.getGuestUserRecordNameResourceId(record)
-        return if (resourceId != null) {
-            Text.Resource(resourceId)
-        } else {
-            Text.Loaded(checkNotNull(record.info).name)
-        }
-    }
-
-    private fun getUserImage(record: UserRecord): Drawable {
-        if (record.isGuest) {
-            return checkNotNull(
-                AppCompatResources.getDrawable(appContext, R.drawable.ic_account_circle)
-            )
-        }
-
-        val userId = checkNotNull(record.info?.id)
-        return manager.getUserIcon(userId)?.let { userSelectedIcon ->
-            BitmapDrawable(userSelectedIcon)
-        }
-            ?: UserIcons.getDefaultUserIcon(appContext.resources, userId, /* light= */ false)
-    }
-
     companion object {
         private const val TAG = "UserRepository"
         @VisibleForTesting const val SETTING_SIMPLE_USER_SWITCHER = "lockscreenSimpleUserSwitcher"
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
index dda78aa..6b81bf2 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
@@ -39,12 +39,9 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.qs.user.UserSwitchDialogController
-import com.android.systemui.statusbar.policy.UserSwitcherController
 import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
 import com.android.systemui.user.data.repository.UserRepository
 import com.android.systemui.user.data.source.UserRecord
@@ -64,8 +61,6 @@
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
@@ -82,10 +77,8 @@
 constructor(
     @Application private val applicationContext: Context,
     private val repository: UserRepository,
-    private val controller: UserSwitcherController,
     private val activityStarter: ActivityStarter,
     private val keyguardInteractor: KeyguardInteractor,
-    private val featureFlags: FeatureFlags,
     private val manager: UserManager,
     @Application private val applicationScope: CoroutineScope,
     telephonyInteractor: TelephonyInteractor,
@@ -107,9 +100,6 @@
         fun onUserStateChanged()
     }
 
-    private val isNewImpl: Boolean
-        get() = !featureFlags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)
-
     private val supervisedUserPackageName: String?
         get() =
             applicationContext.getString(
@@ -122,181 +112,146 @@
     /** List of current on-device users to select from. */
     val users: Flow<List<UserModel>>
         get() =
-            if (isNewImpl) {
-                combine(
-                    repository.userInfos,
-                    repository.selectedUserInfo,
-                    repository.userSwitcherSettings,
-                ) { userInfos, selectedUserInfo, settings ->
-                    toUserModels(
-                        userInfos = userInfos,
-                        selectedUserId = selectedUserInfo.id,
-                        isUserSwitcherEnabled = settings.isUserSwitcherEnabled,
-                    )
-                }
-            } else {
-                repository.users
+            combine(
+                repository.userInfos,
+                repository.selectedUserInfo,
+                repository.userSwitcherSettings,
+            ) { userInfos, selectedUserInfo, settings ->
+                toUserModels(
+                    userInfos = userInfos,
+                    selectedUserId = selectedUserInfo.id,
+                    isUserSwitcherEnabled = settings.isUserSwitcherEnabled,
+                )
             }
 
     /** The currently-selected user. */
     val selectedUser: Flow<UserModel>
         get() =
-            if (isNewImpl) {
-                combine(
-                    repository.selectedUserInfo,
-                    repository.userSwitcherSettings,
-                ) { selectedUserInfo, settings ->
-                    val selectedUserId = selectedUserInfo.id
-                    checkNotNull(
-                        toUserModel(
-                            userInfo = selectedUserInfo,
-                            selectedUserId = selectedUserId,
-                            canSwitchUsers = canSwitchUsers(selectedUserId),
-                            isUserSwitcherEnabled = settings.isUserSwitcherEnabled,
-                        )
+            combine(
+                repository.selectedUserInfo,
+                repository.userSwitcherSettings,
+            ) { selectedUserInfo, settings ->
+                val selectedUserId = selectedUserInfo.id
+                checkNotNull(
+                    toUserModel(
+                        userInfo = selectedUserInfo,
+                        selectedUserId = selectedUserId,
+                        canSwitchUsers = canSwitchUsers(selectedUserId),
+                        isUserSwitcherEnabled = settings.isUserSwitcherEnabled,
                     )
-                }
-            } else {
-                repository.selectedUser
+                )
             }
 
     /** List of user-switcher related actions that are available. */
     val actions: Flow<List<UserActionModel>>
         get() =
-            if (isNewImpl) {
-                combine(
-                    repository.selectedUserInfo,
-                    repository.userInfos,
-                    repository.userSwitcherSettings,
-                    keyguardInteractor.isKeyguardShowing,
-                ) { _, userInfos, settings, isDeviceLocked ->
-                    buildList {
-                        val hasGuestUser = userInfos.any { it.isGuest }
-                        if (
-                            !hasGuestUser &&
-                                (guestUserInteractor.isGuestUserAutoCreated ||
-                                    UserActionsUtil.canCreateGuest(
-                                        manager,
-                                        repository,
-                                        settings.isUserSwitcherEnabled,
-                                        settings.isAddUsersFromLockscreen,
-                                    ))
-                        ) {
-                            add(UserActionModel.ENTER_GUEST_MODE)
-                        }
-
-                        if (!isDeviceLocked || settings.isAddUsersFromLockscreen) {
-                            // The device is locked and our setting to allow actions that add users
-                            // from the lock-screen is not enabled. The guest action from above is
-                            // always allowed, even when the device is locked, but the various "add
-                            // user" actions below are not. We can finish building the list here.
-
-                            val canCreateUsers =
-                                UserActionsUtil.canCreateUser(
+            combine(
+                repository.selectedUserInfo,
+                repository.userInfos,
+                repository.userSwitcherSettings,
+                keyguardInteractor.isKeyguardShowing,
+            ) { _, userInfos, settings, isDeviceLocked ->
+                buildList {
+                    val hasGuestUser = userInfos.any { it.isGuest }
+                    if (
+                        !hasGuestUser &&
+                            (guestUserInteractor.isGuestUserAutoCreated ||
+                                UserActionsUtil.canCreateGuest(
                                     manager,
                                     repository,
                                     settings.isUserSwitcherEnabled,
                                     settings.isAddUsersFromLockscreen,
-                                )
+                                ))
+                    ) {
+                        add(UserActionModel.ENTER_GUEST_MODE)
+                    }
 
-                            if (canCreateUsers) {
-                                add(UserActionModel.ADD_USER)
-                            }
+                    if (!isDeviceLocked || settings.isAddUsersFromLockscreen) {
+                        // The device is locked and our setting to allow actions that add users
+                        // from the lock-screen is not enabled. The guest action from above is
+                        // always allowed, even when the device is locked, but the various "add
+                        // user" actions below are not. We can finish building the list here.
 
-                            if (
-                                UserActionsUtil.canCreateSupervisedUser(
-                                    manager,
-                                    repository,
-                                    settings.isUserSwitcherEnabled,
-                                    settings.isAddUsersFromLockscreen,
-                                    supervisedUserPackageName,
-                                )
-                            ) {
-                                add(UserActionModel.ADD_SUPERVISED_USER)
-                            }
-                        }
-
-                        if (
-                            UserActionsUtil.canManageUsers(
+                        val canCreateUsers =
+                            UserActionsUtil.canCreateUser(
+                                manager,
                                 repository,
                                 settings.isUserSwitcherEnabled,
                                 settings.isAddUsersFromLockscreen,
                             )
-                        ) {
-                            add(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
+
+                        if (canCreateUsers) {
+                            add(UserActionModel.ADD_USER)
                         }
+
+                        if (
+                            UserActionsUtil.canCreateSupervisedUser(
+                                manager,
+                                repository,
+                                settings.isUserSwitcherEnabled,
+                                settings.isAddUsersFromLockscreen,
+                                supervisedUserPackageName,
+                            )
+                        ) {
+                            add(UserActionModel.ADD_SUPERVISED_USER)
+                        }
+                    }
+
+                    if (
+                        UserActionsUtil.canManageUsers(
+                            repository,
+                            settings.isUserSwitcherEnabled,
+                            settings.isAddUsersFromLockscreen,
+                        )
+                    ) {
+                        add(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
                     }
                 }
-            } else {
-                combine(
-                        repository.isActionableWhenLocked,
-                        keyguardInteractor.isKeyguardShowing,
-                    ) { isActionableWhenLocked, isLocked ->
-                        isActionableWhenLocked || !isLocked
-                    }
-                    .flatMapLatest { isActionable ->
-                        if (isActionable) {
-                            repository.actions
-                        } else {
-                            // If not actionable it means that we're not allowed to show actions
-                            // when
-                            // locked and we are locked. Therefore, we should show no actions.
-                            flowOf(emptyList())
-                        }
-                    }
             }
 
     val userRecords: StateFlow<ArrayList<UserRecord>> =
-        if (isNewImpl) {
-            combine(
-                    repository.userInfos,
-                    repository.selectedUserInfo,
-                    actions,
-                    repository.userSwitcherSettings,
-                ) { userInfos, selectedUserInfo, actionModels, settings ->
-                    ArrayList(
-                        userInfos.map {
+        combine(
+                repository.userInfos,
+                repository.selectedUserInfo,
+                actions,
+                repository.userSwitcherSettings,
+            ) { userInfos, selectedUserInfo, actionModels, settings ->
+                ArrayList(
+                    userInfos.map {
+                        toRecord(
+                            userInfo = it,
+                            selectedUserId = selectedUserInfo.id,
+                        )
+                    } +
+                        actionModels.map {
                             toRecord(
-                                userInfo = it,
+                                action = it,
                                 selectedUserId = selectedUserInfo.id,
+                                isRestricted =
+                                    it != UserActionModel.ENTER_GUEST_MODE &&
+                                        it != UserActionModel.NAVIGATE_TO_USER_MANAGEMENT &&
+                                        !settings.isAddUsersFromLockscreen,
                             )
-                        } +
-                            actionModels.map {
-                                toRecord(
-                                    action = it,
-                                    selectedUserId = selectedUserInfo.id,
-                                    isRestricted =
-                                        it != UserActionModel.ENTER_GUEST_MODE &&
-                                            it != UserActionModel.NAVIGATE_TO_USER_MANAGEMENT &&
-                                            !settings.isAddUsersFromLockscreen,
-                                )
-                            }
-                    )
-                }
-                .onEach { notifyCallbacks() }
-                .stateIn(
-                    scope = applicationScope,
-                    started = SharingStarted.Eagerly,
-                    initialValue = ArrayList(),
+                        }
                 )
-        } else {
-            MutableStateFlow(ArrayList())
-        }
+            }
+            .onEach { notifyCallbacks() }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = ArrayList(),
+            )
 
     val selectedUserRecord: StateFlow<UserRecord?> =
-        if (isNewImpl) {
-            repository.selectedUserInfo
-                .map { selectedUserInfo ->
-                    toRecord(userInfo = selectedUserInfo, selectedUserId = selectedUserInfo.id)
-                }
-                .stateIn(
-                    scope = applicationScope,
-                    started = SharingStarted.Eagerly,
-                    initialValue = null,
-                )
-        } else {
-            MutableStateFlow(null)
-        }
+        repository.selectedUserInfo
+            .map { selectedUserInfo ->
+                toRecord(userInfo = selectedUserInfo, selectedUserId = selectedUserInfo.id)
+            }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = null,
+            )
 
     /** Whether the device is configured to always have a guest user available. */
     val isGuestUserAutoCreated: Boolean = guestUserInteractor.isGuestUserAutoCreated
@@ -311,44 +266,37 @@
     val dialogDismissRequests: Flow<Unit?> = _dialogDismissRequests.asStateFlow()
 
     val isSimpleUserSwitcher: Boolean
-        get() =
-            if (isNewImpl) {
-                repository.isSimpleUserSwitcher()
-            } else {
-                error("Not supported in the old implementation!")
-            }
+        get() = repository.isSimpleUserSwitcher()
 
     init {
-        if (isNewImpl) {
-            refreshUsersScheduler.refreshIfNotPaused()
-            telephonyInteractor.callState
-                .distinctUntilChanged()
-                .onEach { refreshUsersScheduler.refreshIfNotPaused() }
-                .launchIn(applicationScope)
+        refreshUsersScheduler.refreshIfNotPaused()
+        telephonyInteractor.callState
+            .distinctUntilChanged()
+            .onEach { refreshUsersScheduler.refreshIfNotPaused() }
+            .launchIn(applicationScope)
 
-            combine(
-                    broadcastDispatcher.broadcastFlow(
-                        filter =
-                            IntentFilter().apply {
-                                addAction(Intent.ACTION_USER_ADDED)
-                                addAction(Intent.ACTION_USER_REMOVED)
-                                addAction(Intent.ACTION_USER_INFO_CHANGED)
-                                addAction(Intent.ACTION_USER_SWITCHED)
-                                addAction(Intent.ACTION_USER_STOPPED)
-                                addAction(Intent.ACTION_USER_UNLOCKED)
-                            },
-                        user = UserHandle.SYSTEM,
-                        map = { intent, _ -> intent },
-                    ),
-                    repository.selectedUserInfo.pairwise(null),
-                ) { intent, selectedUserChange ->
-                    Pair(intent, selectedUserChange.previousValue)
-                }
-                .onEach { (intent, previousSelectedUser) ->
-                    onBroadcastReceived(intent, previousSelectedUser)
-                }
-                .launchIn(applicationScope)
-        }
+        combine(
+                broadcastDispatcher.broadcastFlow(
+                    filter =
+                        IntentFilter().apply {
+                            addAction(Intent.ACTION_USER_ADDED)
+                            addAction(Intent.ACTION_USER_REMOVED)
+                            addAction(Intent.ACTION_USER_INFO_CHANGED)
+                            addAction(Intent.ACTION_USER_SWITCHED)
+                            addAction(Intent.ACTION_USER_STOPPED)
+                            addAction(Intent.ACTION_USER_UNLOCKED)
+                        },
+                    user = UserHandle.SYSTEM,
+                    map = { intent, _ -> intent },
+                ),
+                repository.selectedUserInfo.pairwise(null),
+            ) { intent, selectedUserChange ->
+                Pair(intent, selectedUserChange.previousValue)
+            }
+            .onEach { (intent, previousSelectedUser) ->
+                onBroadcastReceived(intent, previousSelectedUser)
+            }
+            .launchIn(applicationScope)
     }
 
     fun addCallback(callback: UserCallback) {
@@ -414,48 +362,43 @@
         newlySelectedUserId: Int,
         dialogShower: UserSwitchDialogController.DialogShower? = null,
     ) {
-        if (isNewImpl) {
-            val currentlySelectedUserInfo = repository.getSelectedUserInfo()
-            if (
-                newlySelectedUserId == currentlySelectedUserInfo.id &&
-                    currentlySelectedUserInfo.isGuest
-            ) {
-                // Here when clicking on the currently-selected guest user to leave guest mode
-                // and return to the previously-selected non-guest user.
-                showDialog(
-                    ShowDialogRequestModel.ShowExitGuestDialog(
-                        guestUserId = currentlySelectedUserInfo.id,
-                        targetUserId = repository.lastSelectedNonGuestUserId,
-                        isGuestEphemeral = currentlySelectedUserInfo.isEphemeral,
-                        isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
-                        onExitGuestUser = this::exitGuestUser,
-                        dialogShower = dialogShower,
-                    )
+        val currentlySelectedUserInfo = repository.getSelectedUserInfo()
+        if (
+            newlySelectedUserId == currentlySelectedUserInfo.id && currentlySelectedUserInfo.isGuest
+        ) {
+            // Here when clicking on the currently-selected guest user to leave guest mode
+            // and return to the previously-selected non-guest user.
+            showDialog(
+                ShowDialogRequestModel.ShowExitGuestDialog(
+                    guestUserId = currentlySelectedUserInfo.id,
+                    targetUserId = repository.lastSelectedNonGuestUserId,
+                    isGuestEphemeral = currentlySelectedUserInfo.isEphemeral,
+                    isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
+                    onExitGuestUser = this::exitGuestUser,
+                    dialogShower = dialogShower,
                 )
-                return
-            }
-
-            if (currentlySelectedUserInfo.isGuest) {
-                // Here when switching from guest to a non-guest user.
-                showDialog(
-                    ShowDialogRequestModel.ShowExitGuestDialog(
-                        guestUserId = currentlySelectedUserInfo.id,
-                        targetUserId = newlySelectedUserId,
-                        isGuestEphemeral = currentlySelectedUserInfo.isEphemeral,
-                        isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
-                        onExitGuestUser = this::exitGuestUser,
-                        dialogShower = dialogShower,
-                    )
-                )
-                return
-            }
-
-            dialogShower?.dismiss()
-
-            switchUser(newlySelectedUserId)
-        } else {
-            controller.onUserSelected(newlySelectedUserId, dialogShower)
+            )
+            return
         }
+
+        if (currentlySelectedUserInfo.isGuest) {
+            // Here when switching from guest to a non-guest user.
+            showDialog(
+                ShowDialogRequestModel.ShowExitGuestDialog(
+                    guestUserId = currentlySelectedUserInfo.id,
+                    targetUserId = newlySelectedUserId,
+                    isGuestEphemeral = currentlySelectedUserInfo.isEphemeral,
+                    isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
+                    onExitGuestUser = this::exitGuestUser,
+                    dialogShower = dialogShower,
+                )
+            )
+            return
+        }
+
+        dialogShower?.dismiss()
+
+        switchUser(newlySelectedUserId)
     }
 
     /** Executes the given action. */
@@ -463,51 +406,38 @@
         action: UserActionModel,
         dialogShower: UserSwitchDialogController.DialogShower? = null,
     ) {
-        if (isNewImpl) {
-            when (action) {
-                UserActionModel.ENTER_GUEST_MODE ->
-                    guestUserInteractor.createAndSwitchTo(
-                        this::showDialog,
-                        this::dismissDialog,
-                    ) { userId ->
-                        selectUser(userId, dialogShower)
-                    }
-                UserActionModel.ADD_USER -> {
-                    val currentUser = repository.getSelectedUserInfo()
-                    showDialog(
-                        ShowDialogRequestModel.ShowAddUserDialog(
-                            userHandle = currentUser.userHandle,
-                            isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
-                            showEphemeralMessage = currentUser.isGuest && currentUser.isEphemeral,
-                            dialogShower = dialogShower,
-                        )
-                    )
+        when (action) {
+            UserActionModel.ENTER_GUEST_MODE ->
+                guestUserInteractor.createAndSwitchTo(
+                    this::showDialog,
+                    this::dismissDialog,
+                ) { userId ->
+                    selectUser(userId, dialogShower)
                 }
-                UserActionModel.ADD_SUPERVISED_USER ->
-                    activityStarter.startActivity(
-                        Intent()
-                            .setAction(UserManager.ACTION_CREATE_SUPERVISED_USER)
-                            .setPackage(supervisedUserPackageName)
-                            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
-                        /* dismissShade= */ true,
+            UserActionModel.ADD_USER -> {
+                val currentUser = repository.getSelectedUserInfo()
+                showDialog(
+                    ShowDialogRequestModel.ShowAddUserDialog(
+                        userHandle = currentUser.userHandle,
+                        isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
+                        showEphemeralMessage = currentUser.isGuest && currentUser.isEphemeral,
+                        dialogShower = dialogShower,
                     )
-                UserActionModel.NAVIGATE_TO_USER_MANAGEMENT ->
-                    activityStarter.startActivity(
-                        Intent(Settings.ACTION_USER_SETTINGS),
-                        /* dismissShade= */ true,
-                    )
+                )
             }
-        } else {
-            when (action) {
-                UserActionModel.ENTER_GUEST_MODE -> controller.createAndSwitchToGuestUser(null)
-                UserActionModel.ADD_USER -> controller.showAddUserDialog(null)
-                UserActionModel.ADD_SUPERVISED_USER -> controller.startSupervisedUserActivity()
-                UserActionModel.NAVIGATE_TO_USER_MANAGEMENT ->
-                    activityStarter.startActivity(
-                        Intent(Settings.ACTION_USER_SETTINGS),
-                        /* dismissShade= */ false,
-                    )
-            }
+            UserActionModel.ADD_SUPERVISED_USER ->
+                activityStarter.startActivity(
+                    Intent()
+                        .setAction(UserManager.ACTION_CREATE_SUPERVISED_USER)
+                        .setPackage(supervisedUserPackageName)
+                        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
+                    /* dismissShade= */ true,
+                )
+            UserActionModel.NAVIGATE_TO_USER_MANAGEMENT ->
+                activityStarter.startActivity(
+                    Intent(Settings.ACTION_USER_SETTINGS),
+                    /* dismissShade= */ true,
+                )
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
index e921720..58a4473 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
@@ -27,15 +27,12 @@
 import com.android.systemui.broadcast.BroadcastSender
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.user.domain.interactor.UserInteractor
 import com.android.systemui.user.domain.model.ShowDialogRequestModel
 import dagger.Lazy
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.launch
 
@@ -50,16 +47,11 @@
     private val broadcastSender: Lazy<BroadcastSender>,
     private val dialogLaunchAnimator: Lazy<DialogLaunchAnimator>,
     private val interactor: Lazy<UserInteractor>,
-    private val featureFlags: Lazy<FeatureFlags>,
 ) : CoreStartable {
 
     private var currentDialog: Dialog? = null
 
     override fun start() {
-        if (featureFlags.get().isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)) {
-            return
-        }
-
         startHandlingDialogShowRequests()
         startHandlingDialogDismissRequests()
     }
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
index d857e85..0910ea3 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
@@ -20,8 +20,6 @@
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.ViewModelProvider
 import com.android.systemui.common.ui.drawable.CircularDrawable
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.user.domain.interactor.GuestUserInteractor
 import com.android.systemui.user.domain.interactor.UserInteractor
@@ -41,12 +39,8 @@
     private val userInteractor: UserInteractor,
     private val guestUserInteractor: GuestUserInteractor,
     private val powerInteractor: PowerInteractor,
-    private val featureFlags: FeatureFlags,
 ) : ViewModel() {
 
-    private val isNewImpl: Boolean
-        get() = !featureFlags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)
-
     /** On-device users. */
     val users: Flow<List<UserViewModel>> =
         userInteractor.users.map { models -> models.map { user -> toViewModel(user) } }
@@ -216,7 +210,6 @@
         private val userInteractor: UserInteractor,
         private val guestUserInteractor: GuestUserInteractor,
         private val powerInteractor: PowerInteractor,
-        private val featureFlags: FeatureFlags,
     ) : ViewModelProvider.Factory {
         override fun <T : ViewModel> create(modelClass: Class<T>): T {
             @Suppress("UNCHECKED_CAST")
@@ -224,7 +217,6 @@
                 userInteractor = userInteractor,
                 guestUserInteractor = guestUserInteractor,
                 powerInteractor = powerInteractor,
-                featureFlags = featureFlags,
             )
                 as T
         }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 52b6b38..e8f8e25 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -155,7 +155,8 @@
         verify(configurationController).addCallback(capture(captor))
         captor.value.onDensityOrFontScaleChanged()
 
-        verify(events).onFontSettingChanged()
+        verify(smallClockEvents, times(2)).onFontSettingChanged(anyFloat())
+        verify(largeClockEvents, times(2)).onFontSettingChanged(anyFloat())
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index 69ccc8b..57ca9c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -903,6 +903,97 @@
     }
 
     @Test
+    public void changeMagnificationSize_expectedWindowSize() {
+        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+
+        final float magnificationScaleLarge = 2.5f;
+        final int initSize = Math.min(bounds.width(), bounds.height()) / 3;
+        final int magnificationSize = (int) (initSize * magnificationScaleLarge);
+
+        final int expectedWindowHeight = magnificationSize;
+        final int expectedWindowWidth = magnificationSize;
+
+        mInstrumentation.runOnMainSync(
+                () ->
+                        mWindowMagnificationController.enableWindowMagnificationInternal(
+                                Float.NaN, Float.NaN, Float.NaN));
+
+        final AtomicInteger actualWindowHeight = new AtomicInteger();
+        final AtomicInteger actualWindowWidth = new AtomicInteger();
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    mWindowMagnificationController.changeMagnificationSize(
+                            WindowMagnificationSettings.MagnificationSize.LARGE);
+                    actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
+                    actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
+                });
+
+        assertEquals(expectedWindowHeight, actualWindowHeight.get());
+        assertEquals(expectedWindowWidth, actualWindowWidth.get());
+    }
+
+    @Test
+    public void editModeOnDragCorner_resizesWindow() {
+        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+
+        final int startingSize = (int) (bounds.width() / 2);
+
+        mInstrumentation.runOnMainSync(
+                () ->
+                        mWindowMagnificationController.enableWindowMagnificationInternal(
+                                Float.NaN, Float.NaN, Float.NaN));
+
+        final AtomicInteger actualWindowHeight = new AtomicInteger();
+        final AtomicInteger actualWindowWidth = new AtomicInteger();
+
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    mWindowMagnificationController.setWindowSize(startingSize, startingSize);
+                    mWindowMagnificationController.setEditMagnifierSizeMode(true);
+                });
+
+        waitForIdleSync();
+
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    mWindowMagnificationController
+                            .onDrag(getInternalView(R.id.bottom_right_corner), 2f, 1f);
+                    actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
+                    actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
+                });
+
+        assertEquals(startingSize + 1, actualWindowHeight.get());
+        assertEquals(startingSize + 2, actualWindowWidth.get());
+    }
+
+    @Test
+    public void editModeOnDragEdge_resizesWindowInOnlyOneDirection() {
+        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+
+        final int startingSize = (int) (bounds.width() / 2f);
+
+        mInstrumentation.runOnMainSync(
+                () ->
+                        mWindowMagnificationController.enableWindowMagnificationInternal(
+                                Float.NaN, Float.NaN, Float.NaN));
+
+        final AtomicInteger actualWindowHeight = new AtomicInteger();
+        final AtomicInteger actualWindowWidth = new AtomicInteger();
+
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    mWindowMagnificationController.setWindowSize(startingSize, startingSize);
+                    mWindowMagnificationController.setEditMagnifierSizeMode(true);
+                    mWindowMagnificationController
+                            .onDrag(getInternalView(R.id.bottom_handle), 2f, 1f);
+                    actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
+                    actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
+                });
+        assertEquals(startingSize + 1, actualWindowHeight.get());
+        assertEquals(startingSize, actualWindowWidth.get());
+    }
+
+    @Test
     public void setWindowCenterOutOfScreen_enabled_magnificationCenterIsInsideTheScreen() {
 
         final int minimumWindowSize = mResources.getDimensionPixelSize(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
index d20eeaf..2d5188f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
@@ -19,12 +19,22 @@
 import static android.view.View.GONE;
 import static android.view.View.VISIBLE;
 
+import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
 import static com.android.systemui.accessibility.floatingmenu.MenuViewLayer.LayerIndex;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.content.ComponentName;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.Build;
+import android.os.UserHandle;
+import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.View;
@@ -35,6 +45,7 @@
 
 import com.android.systemui.SysuiTestCase;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -43,12 +54,23 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /** Tests for {@link MenuViewLayer}. */
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 @SmallTest
 public class MenuViewLayerTest extends SysuiTestCase {
+    private static final String SELECT_TO_SPEAK_PACKAGE_NAME = "com.google.android.marvin.talkback";
+    private static final String SELECT_TO_SPEAK_SERVICE_NAME =
+            "com.google.android.accessibility.selecttospeak.SelectToSpeakService";
+    private static final ComponentName TEST_SELECT_TO_SPEAK_COMPONENT_NAME = new ComponentName(
+            SELECT_TO_SPEAK_PACKAGE_NAME, SELECT_TO_SPEAK_SERVICE_NAME);
+
     private MenuViewLayer mMenuViewLayer;
+    private String mLastAccessibilityButtonTargets;
+    private String mLastEnabledAccessibilityServices;
 
     @Rule
     public MockitoRule mockito = MockitoJUnit.rule();
@@ -56,13 +78,31 @@
     @Mock
     private IAccessibilityFloatingMenu mFloatingMenu;
 
+    @Mock
+    private AccessibilityManager mStubAccessibilityManager;
+
     @Before
     public void setUp() throws Exception {
         final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
-        final AccessibilityManager stubAccessibilityManager = mContext.getSystemService(
-                AccessibilityManager.class);
-        mMenuViewLayer = new MenuViewLayer(mContext, stubWindowManager, stubAccessibilityManager,
+        mMenuViewLayer = new MenuViewLayer(mContext, stubWindowManager, mStubAccessibilityManager,
                 mFloatingMenu);
+
+        mLastAccessibilityButtonTargets =
+                Settings.Secure.getStringForUser(mContext.getContentResolver(),
+                        Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, UserHandle.USER_CURRENT);
+        mLastEnabledAccessibilityServices =
+                Settings.Secure.getStringForUser(mContext.getContentResolver(),
+                        Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, UserHandle.USER_CURRENT);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        Settings.Secure.putStringForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, mLastAccessibilityButtonTargets,
+                UserHandle.USER_CURRENT);
+        Settings.Secure.putStringForUser(mContext.getContentResolver(),
+                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, mLastEnabledAccessibilityServices,
+                UserHandle.USER_CURRENT);
     }
 
     @Test
@@ -87,4 +127,45 @@
 
         verify(mFloatingMenu).hide();
     }
+
+    @Test
+    public void tiggerDismissMenuAction_matchA11yButtonTargetsResult() {
+        Settings.Secure.putStringForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
+                MAGNIFICATION_COMPONENT_NAME.flattenToString(), UserHandle.USER_CURRENT);
+
+        mMenuViewLayer.mDismissMenuAction.run();
+        final String value =
+                Settings.Secure.getStringForUser(mContext.getContentResolver(),
+                        Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, UserHandle.USER_CURRENT);
+
+        assertThat(value).isEqualTo("");
+    }
+
+    @Test
+    public void tiggerDismissMenuAction_matchEnabledA11yServicesResult() {
+        Settings.Secure.putString(mContext.getContentResolver(),
+                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+                TEST_SELECT_TO_SPEAK_COMPONENT_NAME.flattenToString());
+        final ResolveInfo resolveInfo = new ResolveInfo();
+        final ServiceInfo serviceInfo = new ServiceInfo();
+        final ApplicationInfo applicationInfo = new ApplicationInfo();
+        resolveInfo.serviceInfo = serviceInfo;
+        serviceInfo.applicationInfo = applicationInfo;
+        applicationInfo.targetSdkVersion = Build.VERSION_CODES.R;
+        final AccessibilityServiceInfo accessibilityServiceInfo = new AccessibilityServiceInfo();
+        accessibilityServiceInfo.setResolveInfo(resolveInfo);
+        accessibilityServiceInfo.flags = AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON;
+        final List<AccessibilityServiceInfo> serviceInfoList = new ArrayList<>();
+        accessibilityServiceInfo.setComponentName(TEST_SELECT_TO_SPEAK_COMPONENT_NAME);
+        serviceInfoList.add(accessibilityServiceInfo);
+        when(mStubAccessibilityManager.getEnabledAccessibilityServiceList(
+                AccessibilityServiceInfo.FEEDBACK_ALL_MASK)).thenReturn(serviceInfoList);
+
+        mMenuViewLayer.mDismissMenuAction.run();
+        final String value = Settings.Secure.getString(mContext.getContentResolver(),
+                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
+
+        assertThat(value).isEqualTo("");
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 12c2bbf..898f370 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -125,6 +125,21 @@
     }
 
     @Test
+    fun testCredentialPasswordDismissesOnBack() {
+        val container = initializeCredentialPasswordContainer(addToView = true)
+        assertThat(container.parent).isNotNull()
+        val root = container.rootView
+
+        // Simulate back invocation
+        container.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK))
+        container.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK))
+        waitForIdleSync()
+
+        assertThat(container.parent).isNull()
+        assertThat(root.isAttachedToWindow).isFalse()
+    }
+
+    @Test
     fun testIgnoresAnimatedInWhenDismissed() {
         val container = initializeFingerprintContainer(addToView = false)
         container.dismissFromSystemServer()
@@ -369,20 +384,7 @@
 
     @Test
     fun testCredentialUI_disablesClickingOnBackground() {
-        whenever(userManager.getCredentialOwnerProfile(anyInt())).thenReturn(20)
-        whenever(lockPatternUtils.getKeyguardStoredPasswordQuality(eq(20))).thenReturn(
-            DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
-        )
-
-        // In the credential view, clicking on the background (to cancel authentication) is not
-        // valid. Thus, the listener should be null, and it should not be in the accessibility
-        // hierarchy.
-        val container = initializeFingerprintContainer(
-            authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
-        )
-        waitForIdleSync()
-
-        assertThat(container.hasCredentialPasswordView()).isTrue()
+        val container = initializeCredentialPasswordContainer()
         assertThat(container.hasBiometricPrompt()).isFalse()
         assertThat(
             container.findViewById<View>(R.id.background)?.isImportantForAccessibility
@@ -442,6 +444,27 @@
         verify(callback).onTryAgainPressed(authContainer?.requestId ?: 0L)
     }
 
+    private fun initializeCredentialPasswordContainer(
+            addToView: Boolean = true,
+    ): TestAuthContainerView {
+        whenever(userManager.getCredentialOwnerProfile(anyInt())).thenReturn(20)
+        whenever(lockPatternUtils.getKeyguardStoredPasswordQuality(eq(20))).thenReturn(
+            DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
+        )
+
+        // In the credential view, clicking on the background (to cancel authentication) is not
+        // valid. Thus, the listener should be null, and it should not be in the accessibility
+        // hierarchy.
+        val container = initializeFingerprintContainer(
+                authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL,
+                addToView = addToView,
+        )
+        waitForIdleSync()
+
+        assertThat(container.hasCredentialPasswordView()).isTrue()
+        return container
+    }
+
     private fun initializeFingerprintContainer(
         authenticators: Int = BiometricManager.Authenticators.BIOMETRIC_WEAK,
         addToView: Boolean = true
diff --git a/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
index 2af0557..d159714 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
@@ -24,7 +24,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
-import com.android.systemui.ripple.RippleView
+import com.android.systemui.surfaceeffects.ripple.RippleView
 import com.android.systemui.statusbar.commandline.CommandRegistry
 import com.android.systemui.statusbar.policy.BatteryController
 import com.android.systemui.statusbar.policy.ConfigurationController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
index f8579ff..0fadc13 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
@@ -120,6 +120,7 @@
 
         mGestureFinalizedListener = gestureCompleteListenerCaptor.getValue();
         mFakeFeatureFlags.set(Flags.FALSING_FOR_LONG_TAPS, true);
+        mFakeFeatureFlags.set(Flags.MEDIA_FALSING_PENALTY, true);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
deleted file mode 100644
index 4d66a16..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
+++ /dev/null
@@ -1,302 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.keyguard
-
-import android.content.pm.PackageManager
-import android.content.pm.ProviderInfo
-import androidx.test.filters.SmallTest
-import com.android.internal.widget.LockPatternUtils
-import com.android.systemui.SystemUIAppComponentFactoryBase
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderClient as Client
-import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract as Contract
-import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
-import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.util.mockito.mock
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.runBlocking
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(JUnit4::class)
-class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
-
-    @Mock private lateinit var lockPatternUtils: LockPatternUtils
-    @Mock private lateinit var keyguardStateController: KeyguardStateController
-    @Mock private lateinit var userTracker: UserTracker
-    @Mock private lateinit var activityStarter: ActivityStarter
-
-    private lateinit var underTest: KeyguardQuickAffordanceProvider
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-
-        underTest = KeyguardQuickAffordanceProvider()
-        val quickAffordanceRepository =
-            KeyguardQuickAffordanceRepository(
-                scope = CoroutineScope(IMMEDIATE),
-                backgroundDispatcher = IMMEDIATE,
-                selectionManager = KeyguardQuickAffordanceSelectionManager(),
-                configs =
-                    setOf(
-                        FakeKeyguardQuickAffordanceConfig(
-                            key = AFFORDANCE_1,
-                            pickerIconResourceId = 1,
-                        ),
-                        FakeKeyguardQuickAffordanceConfig(
-                            key = AFFORDANCE_2,
-                            pickerIconResourceId = 2,
-                        ),
-                    ),
-            )
-        underTest.interactor =
-            KeyguardQuickAffordanceInteractor(
-                keyguardInteractor =
-                    KeyguardInteractor(
-                        repository = FakeKeyguardRepository(),
-                    ),
-                registry = mock(),
-                lockPatternUtils = lockPatternUtils,
-                keyguardStateController = keyguardStateController,
-                userTracker = userTracker,
-                activityStarter = activityStarter,
-                featureFlags =
-                    FakeFeatureFlags().apply {
-                        set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
-                    },
-                repository = { quickAffordanceRepository },
-            )
-
-        underTest.attachInfoForTesting(
-            context,
-            ProviderInfo().apply { authority = Contract.AUTHORITY },
-        )
-        context.contentResolver.addProvider(Contract.AUTHORITY, underTest)
-        context.testablePermissions.setPermission(
-            Contract.PERMISSION,
-            PackageManager.PERMISSION_GRANTED,
-        )
-    }
-
-    @Test
-    fun `onAttachInfo - reportsContext`() {
-        val callback: SystemUIAppComponentFactoryBase.ContextAvailableCallback = mock()
-        underTest.setContextAvailableCallback(callback)
-
-        underTest.attachInfo(context, null)
-
-        verify(callback).onContextAvailable(context)
-    }
-
-    @Test
-    fun getType() {
-        assertThat(underTest.getType(Contract.AffordanceTable.URI))
-            .isEqualTo(
-                "vnd.android.cursor.dir/vnd." +
-                    "${Contract.AUTHORITY}.${Contract.AffordanceTable.TABLE_NAME}"
-            )
-        assertThat(underTest.getType(Contract.SlotTable.URI))
-            .isEqualTo(
-                "vnd.android.cursor.dir/vnd.${Contract.AUTHORITY}.${Contract.SlotTable.TABLE_NAME}"
-            )
-        assertThat(underTest.getType(Contract.SelectionTable.URI))
-            .isEqualTo(
-                "vnd.android.cursor.dir/vnd." +
-                    "${Contract.AUTHORITY}.${Contract.SelectionTable.TABLE_NAME}"
-            )
-    }
-
-    @Test
-    fun `insert and query selection`() =
-        runBlocking(IMMEDIATE) {
-            val slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
-            val affordanceId = AFFORDANCE_2
-
-            Client.insertSelection(
-                context = context,
-                slotId = slotId,
-                affordanceId = affordanceId,
-                dispatcher = IMMEDIATE,
-            )
-
-            assertThat(
-                    Client.querySelections(
-                        context = context,
-                        dispatcher = IMMEDIATE,
-                    )
-                )
-                .isEqualTo(
-                    listOf(
-                        Client.Selection(
-                            slotId = slotId,
-                            affordanceId = affordanceId,
-                        )
-                    )
-                )
-        }
-
-    @Test
-    fun `query slots`() =
-        runBlocking(IMMEDIATE) {
-            assertThat(
-                    Client.querySlots(
-                        context = context,
-                        dispatcher = IMMEDIATE,
-                    )
-                )
-                .isEqualTo(
-                    listOf(
-                        Client.Slot(
-                            id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
-                            capacity = 1,
-                        ),
-                        Client.Slot(
-                            id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
-                            capacity = 1,
-                        ),
-                    )
-                )
-        }
-
-    @Test
-    fun `query affordances`() =
-        runBlocking(IMMEDIATE) {
-            assertThat(
-                    Client.queryAffordances(
-                        context = context,
-                        dispatcher = IMMEDIATE,
-                    )
-                )
-                .isEqualTo(
-                    listOf(
-                        Client.Affordance(
-                            id = AFFORDANCE_1,
-                            name = AFFORDANCE_1,
-                            iconResourceId = 1,
-                        ),
-                        Client.Affordance(
-                            id = AFFORDANCE_2,
-                            name = AFFORDANCE_2,
-                            iconResourceId = 2,
-                        ),
-                    )
-                )
-        }
-
-    @Test
-    fun `delete and query selection`() =
-        runBlocking(IMMEDIATE) {
-            Client.insertSelection(
-                context = context,
-                slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
-                affordanceId = AFFORDANCE_1,
-                dispatcher = IMMEDIATE,
-            )
-            Client.insertSelection(
-                context = context,
-                slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
-                affordanceId = AFFORDANCE_2,
-                dispatcher = IMMEDIATE,
-            )
-
-            Client.deleteSelection(
-                context = context,
-                slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
-                affordanceId = AFFORDANCE_2,
-                dispatcher = IMMEDIATE,
-            )
-
-            assertThat(
-                    Client.querySelections(
-                        context = context,
-                        dispatcher = IMMEDIATE,
-                    )
-                )
-                .isEqualTo(
-                    listOf(
-                        Client.Selection(
-                            slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
-                            affordanceId = AFFORDANCE_1,
-                        )
-                    )
-                )
-        }
-
-    @Test
-    fun `delete all selections in a slot`() =
-        runBlocking(IMMEDIATE) {
-            Client.insertSelection(
-                context = context,
-                slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
-                affordanceId = AFFORDANCE_1,
-                dispatcher = IMMEDIATE,
-            )
-            Client.insertSelection(
-                context = context,
-                slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
-                affordanceId = AFFORDANCE_2,
-                dispatcher = IMMEDIATE,
-            )
-
-            Client.deleteAllSelections(
-                context = context,
-                slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
-                dispatcher = IMMEDIATE,
-            )
-
-            assertThat(
-                    Client.querySelections(
-                        context = context,
-                        dispatcher = IMMEDIATE,
-                    )
-                )
-                .isEqualTo(
-                    listOf(
-                        Client.Selection(
-                            slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
-                            affordanceId = AFFORDANCE_1,
-                        )
-                    )
-                )
-        }
-
-    companion object {
-        private val IMMEDIATE = Dispatchers.Main.immediate
-        private const val AFFORDANCE_1 = "affordance_1"
-        private const val AFFORDANCE_2 = "affordance_2"
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarViewModelTest.kt
index 7cd8e74..56c91bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarViewModelTest.kt
@@ -42,6 +42,7 @@
 import org.mockito.ArgumentCaptor
 import org.mockito.Mock
 import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.eq
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
@@ -464,7 +465,7 @@
     fun onFalseTapOrTouch() {
         whenever(mockController.getTransportControls()).thenReturn(mockTransport)
         whenever(falsingManager.isFalseTouch(Classifier.MEDIA_SEEKBAR)).thenReturn(true)
-        whenever(falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)).thenReturn(true)
+        whenever(falsingManager.isFalseTap(anyInt())).thenReturn(true)
         viewModel.updateController(mockController)
         val pos = 169
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/ColorSchemeTransitionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/ColorSchemeTransitionTest.kt
index a8f4138..a943746 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/ColorSchemeTransitionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/ColorSchemeTransitionTest.kt
@@ -25,7 +25,8 @@
 import com.android.systemui.media.controls.models.GutsViewHolder
 import com.android.systemui.media.controls.models.player.MediaViewHolder
 import com.android.systemui.monet.ColorScheme
-import com.android.systemui.ripple.MultiRippleController
+import com.android.systemui.surfaceeffects.ripple.MultiRippleController
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseController
 import junit.framework.Assert.assertEquals
 import org.junit.After
 import org.junit.Before
@@ -62,6 +63,7 @@
     @Mock private lateinit var mediaViewHolder: MediaViewHolder
     @Mock private lateinit var gutsViewHolder: GutsViewHolder
     @Mock private lateinit var multiRippleController: MultiRippleController
+    @Mock private lateinit var turbulenceNoiseController: TurbulenceNoiseController
 
     @JvmField @Rule val mockitoRule = MockitoJUnit.rule()
 
@@ -76,6 +78,7 @@
                 context,
                 mediaViewHolder,
                 multiRippleController,
+                turbulenceNoiseController,
                 animatingColorTransitionFactory
             )
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index 1ad2ca9b..761773b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -78,9 +78,10 @@
 import com.android.systemui.media.dialog.MediaOutputDialogFactory
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.ripple.MultiRippleView
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.surfaceeffects.ripple.MultiRippleView
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseView
 import com.android.systemui.util.animation.TransitionLayout
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.KotlinArgumentCaptor
@@ -178,6 +179,7 @@
     private lateinit var dismiss: FrameLayout
     private lateinit var dismissText: TextView
     private lateinit var multiRippleView: MultiRippleView
+    private lateinit var turbulenceNoiseView: TurbulenceNoiseView
 
     private lateinit var session: MediaSession
     private lateinit var device: MediaDeviceData
@@ -210,7 +212,10 @@
     private lateinit var recSubtitle3: TextView
     private var shouldShowBroadcastButton: Boolean = false
     private val fakeFeatureFlag =
-        FakeFeatureFlags().apply { this.set(Flags.UMO_SURFACE_RIPPLE, false) }
+        FakeFeatureFlags().apply {
+            this.set(Flags.UMO_SURFACE_RIPPLE, false)
+            this.set(Flags.MEDIA_FALSING_PENALTY, true)
+        }
 
     @JvmField @Rule val mockito = MockitoJUnit.rule()
 
@@ -382,6 +387,7 @@
             }
 
         multiRippleView = MultiRippleView(context, null)
+        turbulenceNoiseView = TurbulenceNoiseView(context, null)
 
         whenever(viewHolder.player).thenReturn(view)
         whenever(viewHolder.appIcon).thenReturn(appIcon)
@@ -425,6 +431,7 @@
         whenever(viewHolder.actionsTopBarrier).thenReturn(actionsTopBarrier)
 
         whenever(viewHolder.multiRippleView).thenReturn(multiRippleView)
+        whenever(viewHolder.turbulenceNoiseView).thenReturn(turbulenceNoiseView)
     }
 
     /** Initialize elements for the recommendation view holder */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
index 3131f60..08a90b7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
@@ -42,27 +42,21 @@
 import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
 class UserDetailViewAdapterTest : SysuiTestCase() {
 
-    @Mock
-    private lateinit var mUserSwitcherController: UserSwitcherController
-    @Mock
-    private lateinit var mParent: ViewGroup
-    @Mock
-    private lateinit var mUserDetailItemView: UserDetailItemView
-    @Mock
-    private lateinit var mOtherView: View
-    @Mock
-    private lateinit var mInflatedUserDetailItemView: UserDetailItemView
-    @Mock
-    private lateinit var mLayoutInflater: LayoutInflater
+    @Mock private lateinit var mUserSwitcherController: UserSwitcherController
+    @Mock private lateinit var mParent: ViewGroup
+    @Mock private lateinit var mUserDetailItemView: UserDetailItemView
+    @Mock private lateinit var mOtherView: View
+    @Mock private lateinit var mInflatedUserDetailItemView: UserDetailItemView
+    @Mock private lateinit var mLayoutInflater: LayoutInflater
     private var falsingManagerFake: FalsingManagerFake = FalsingManagerFake()
     private lateinit var adapter: UserDetailView.Adapter
     private lateinit var uiEventLogger: UiEventLoggerFake
@@ -77,10 +71,13 @@
         `when`(mLayoutInflater.inflate(anyInt(), any(ViewGroup::class.java), anyBoolean()))
             .thenReturn(mInflatedUserDetailItemView)
         `when`(mParent.context).thenReturn(mContext)
-        adapter = UserDetailView.Adapter(
-            mContext, mUserSwitcherController, uiEventLogger,
-            falsingManagerFake
-        )
+        adapter =
+            UserDetailView.Adapter(
+                mContext,
+                mUserSwitcherController,
+                uiEventLogger,
+                falsingManagerFake
+            )
         mPicture = UserIcons.convertToBitmap(mContext.getDrawable(R.drawable.ic_avatar_user))
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
index 2ef7312..48a53bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
@@ -60,8 +60,8 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.UnreleasedFlag;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.statusbar.connectivity.AccessPointController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -169,8 +169,8 @@
     private WifiStateWorker mWifiStateWorker;
     @Mock
     private SignalStrength mSignalStrength;
-    @Mock
-    private FeatureFlags mFlags;
+
+    private FakeFeatureFlags mFlags = new FakeFeatureFlags();
 
     private TestableResources mTestableResources;
     private InternetDialogController mInternetDialogController;
@@ -221,6 +221,7 @@
         mInternetDialogController.onAccessPointsChanged(mAccessPoints);
         mInternetDialogController.mActivityStarter = mActivityStarter;
         mInternetDialogController.mWifiIconInjector = mWifiIconInjector;
+        mFlags.set(Flags.QS_SECONDARY_DATA_SUB_INFO, false);
     }
 
     @After
@@ -410,7 +411,7 @@
 
     @Test
     public void getSubtitleText_withNoService_returnNoNetworksAvailable() {
-        when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+        mFlags.set(Flags.QS_SECONDARY_DATA_SUB_INFO, true);
         InternetDialogController spyController = spy(mInternetDialogController);
         fakeAirplaneModeEnabled(false);
         when(mWifiStateWorker.isWifiEnabled()).thenReturn(true);
@@ -767,7 +768,7 @@
 
     @Test
     public void getSignalStrengthIcon_differentSubId() {
-        when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+        mFlags.set(Flags.QS_SECONDARY_DATA_SUB_INFO, true);
         InternetDialogController spyController = spy(mInternetDialogController);
         Drawable icons = spyController.getSignalStrengthIcon(SUB_ID, mContext, 1, 1, 0, false);
         Drawable icons2 = spyController.getSignalStrengthIcon(SUB_ID2, mContext, 1, 1, 0, false);
@@ -777,7 +778,7 @@
 
     @Test
     public void getActiveAutoSwitchNonDdsSubId() {
-        when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+        mFlags.set(Flags.QS_SECONDARY_DATA_SUB_INFO, true);
         // active on non-DDS
         SubscriptionInfo info = mock(SubscriptionInfo.class);
         doReturn(SUB_ID2).when(info).getSubscriptionId();
@@ -813,7 +814,7 @@
 
     @Test
     public void getMobileNetworkSummary() {
-        when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+        mFlags.set(Flags.QS_SECONDARY_DATA_SUB_INFO, true);
         InternetDialogController spyController = spy(mInternetDialogController);
         doReturn(SUB_ID2).when(spyController).getActiveAutoSwitchNonDdsSubId();
         doReturn(true).when(spyController).isMobileDataEnabled();
@@ -837,7 +838,7 @@
 
     @Test
     public void launchMobileNetworkSettings_validSubId() {
-        when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+        mFlags.set(Flags.QS_SECONDARY_DATA_SUB_INFO, true);
         InternetDialogController spyController = spy(mInternetDialogController);
         doReturn(SUB_ID2).when(spyController).getActiveAutoSwitchNonDdsSubId();
         spyController.launchMobileNetworkSettings(mDialogLaunchView);
@@ -848,7 +849,7 @@
 
     @Test
     public void launchMobileNetworkSettings_invalidSubId() {
-        when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+        mFlags.set(Flags.QS_SECONDARY_DATA_SUB_INFO, true);
         InternetDialogController spyController = spy(mInternetDialogController);
         doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
                 .when(spyController).getActiveAutoSwitchNonDdsSubId();
@@ -860,7 +861,7 @@
 
     @Test
     public void setAutoDataSwitchMobileDataPolicy() {
-        when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+        mFlags.set(Flags.QS_SECONDARY_DATA_SUB_INFO, true);
         mInternetDialogController.setAutoDataSwitchMobileDataPolicy(SUB_ID, true);
 
         verify(mTelephonyManager).setMobileDataPolicyEnabled(eq(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
index d7eb337..bc17c19 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
@@ -363,6 +363,22 @@
         }
     }
 
+    @Test
+    fun testEmptyCutoutDateIconsAreConstrainedWidth() {
+        CombinedShadeHeadersConstraintManagerImpl.emptyCutoutConstraints()()
+
+        assertThat(qqsConstraint.getConstraint(R.id.date).layout.constrainedWidth).isTrue()
+        assertThat(qqsConstraint.getConstraint(R.id.statusIcons).layout.constrainedWidth).isTrue()
+    }
+
+    @Test
+    fun testCenterCutoutDateIconsAreConstrainedWidth() {
+        CombinedShadeHeadersConstraintManagerImpl.centerCutoutConstraints(false, 10)()
+
+        assertThat(qqsConstraint.getConstraint(R.id.date).layout.constrainedWidth).isTrue()
+        assertThat(qqsConstraint.getConstraint(R.id.statusIcons).layout.constrainedWidth).isTrue()
+    }
+
     private operator fun ConstraintsChanges.invoke() {
         qqsConstraintsChanges?.invoke(qqsConstraint)
         qsConstraintsChanges?.invoke(qsConstraint)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
index 539a54b..f5bed79 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
@@ -139,12 +139,19 @@
     }
 
     @Test
-    fun defaultClock_events_onFontSettingChanged() {
+    fun defaultSmallClock_events_onFontSettingChanged() {
         val clock = provider.createClock(DEFAULT_CLOCK_ID)
-        clock.events.onFontSettingChanged()
+        clock.smallClock.events.onFontSettingChanged(100f)
 
-        verify(mockSmallClockView).setTextSize(eq(TypedValue.COMPLEX_UNIT_PX), anyFloat())
-        verify(mockLargeClockView).setTextSize(eq(TypedValue.COMPLEX_UNIT_PX), anyFloat())
+        verify(mockSmallClockView).setTextSize(eq(TypedValue.COMPLEX_UNIT_PX), eq(100f))
+    }
+
+    @Test
+    fun defaultLargeClock_events_onFontSettingChanged() {
+        val clock = provider.createClock(DEFAULT_CLOCK_ID)
+        clock.largeClock.events.onFontSettingChanged(200f)
+
+        verify(mockLargeClockView).setTextSize(eq(TypedValue.COMPLEX_UNIT_PX), eq(200f))
         verify(mockLargeClockView).setLayoutParams(any())
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 808abc8..de71e2c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.phone;
 
-import static com.android.systemui.statusbar.phone.ScrimController.KEYGUARD_SCRIM_ALPHA;
 import static com.android.systemui.statusbar.phone.ScrimController.OPAQUE;
 import static com.android.systemui.statusbar.phone.ScrimController.SEMI_TRANSPARENT;
 import static com.android.systemui.statusbar.phone.ScrimController.TRANSPARENT;
@@ -59,7 +58,6 @@
 import com.android.systemui.animation.ShadeInterpolation;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.scrim.ScrimView;
 import com.android.systemui.statusbar.policy.FakeConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -119,7 +117,6 @@
     // TODO(b/204991468): Use a real PanelExpansionStateManager object once this bug is fixed. (The
     //   event-dispatch-on-registration pattern caused some of these unit tests to fail.)
     @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
-    @Mock private KeyguardViewMediator mKeyguardViewMediator;
 
     private static class AnimatorListener implements Animator.AnimatorListener {
         private int mNumStarts;
@@ -233,8 +230,7 @@
                 mDockManager, mConfigurationController, new FakeExecutor(new FakeSystemClock()),
                 mScreenOffAnimationController,
                 mKeyguardUnlockAnimationController,
-                mStatusBarKeyguardViewManager,
-                mKeyguardViewMediator);
+                mStatusBarKeyguardViewManager);
         mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
         mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
         mScrimController.setAnimatorListener(mAnimatorListener);
@@ -243,8 +239,6 @@
         mScrimController.setWallpaperSupportsAmbientMode(false);
         mScrimController.transitionTo(ScrimState.KEYGUARD);
         finishAnimationsImmediately();
-
-        mScrimController.setLaunchingAffordanceWithPreview(false);
     }
 
     @After
@@ -858,8 +852,7 @@
                 mDockManager, mConfigurationController, new FakeExecutor(new FakeSystemClock()),
                 mScreenOffAnimationController,
                 mKeyguardUnlockAnimationController,
-                mStatusBarKeyguardViewManager,
-                mKeyguardViewMediator);
+                mStatusBarKeyguardViewManager);
         mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
         mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
         mScrimController.setAnimatorListener(mAnimatorListener);
@@ -1638,30 +1631,6 @@
         assertScrimAlpha(mScrimBehind, 0);
     }
 
-    @Test
-    public void keyguardAlpha_whenUnlockedForOcclusion_ifPlayingOcclusionAnimation() {
-        mScrimController.transitionTo(ScrimState.KEYGUARD);
-
-        when(mKeyguardViewMediator.isOccludeAnimationPlaying()).thenReturn(true);
-
-        mScrimController.transitionTo(ScrimState.UNLOCKED);
-        finishAnimationsImmediately();
-
-        assertScrimAlpha(mNotificationsScrim, (int) (KEYGUARD_SCRIM_ALPHA * 255f));
-    }
-
-    @Test
-    public void keyguardAlpha_whenUnlockedForLaunch_ifLaunchingAffordance() {
-        mScrimController.transitionTo(ScrimState.KEYGUARD);
-        when(mKeyguardViewMediator.isOccludeAnimationPlaying()).thenReturn(true);
-        mScrimController.setLaunchingAffordanceWithPreview(true);
-
-        mScrimController.transitionTo(ScrimState.UNLOCKED);
-        finishAnimationsImmediately();
-
-        assertScrimAlpha(mNotificationsScrim, (int) (KEYGUARD_SCRIM_ALPHA * 255f));
-    }
-
     private void assertAlphaAfterExpansion(ScrimView scrim, float expectedAlpha, float expansion) {
         mScrimController.setRawPanelExpansionFraction(expansion);
         finishAnimationsImmediately();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt
index f304647..0a3da0b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt
@@ -237,7 +237,7 @@
     fun refresh() {
         underTest.refresh()
 
-        verify(controller).refreshUsers(UserHandle.USER_NULL)
+        verify(controller).refreshUsers()
     }
 
     private fun createUserRecord(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt
deleted file mode 100644
index 169f4fb..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt
+++ /dev/null
@@ -1,727 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.policy
-
-import android.app.IActivityManager
-import android.app.NotificationManager
-import android.app.admin.DevicePolicyManager
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.DialogInterface
-import android.content.Intent
-import android.content.pm.UserInfo
-import android.graphics.Bitmap
-import android.hardware.face.FaceManager
-import android.hardware.fingerprint.FingerprintManager
-import android.os.Handler
-import android.os.UserHandle
-import android.os.UserManager
-import android.provider.Settings
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import android.view.ThreadedRenderer
-import androidx.test.filters.SmallTest
-import com.android.internal.jank.InteractionJankMonitor
-import com.android.internal.logging.testing.UiEventLoggerFake
-import com.android.internal.util.LatencyTracker
-import com.android.internal.util.UserIcons
-import com.android.systemui.GuestResetOrExitSessionReceiver
-import com.android.systemui.GuestResumeSessionReceiver
-import com.android.systemui.GuestSessionNotification
-import com.android.systemui.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.DialogCuj
-import com.android.systemui.animation.DialogLaunchAnimator
-import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.broadcast.BroadcastSender
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.qs.QSUserSwitcherEvent
-import com.android.systemui.qs.user.UserSwitchDialogController
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.shade.NotificationShadeWindowView
-import com.android.systemui.telephony.TelephonyListenerManager
-import com.android.systemui.user.data.source.UserRecord
-import com.android.systemui.user.legacyhelper.data.LegacyUserDataHelper
-import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.capture
-import com.android.systemui.util.mockito.kotlinArgumentCaptor
-import com.android.systemui.util.mockito.nullable
-import com.android.systemui.util.settings.GlobalSettings
-import com.android.systemui.util.settings.SecureSettings
-import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertFalse
-import org.junit.Assert.assertNotNull
-import org.junit.Assert.assertTrue
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mock
-import org.mockito.Mockito.doNothing
-import org.mockito.Mockito.doReturn
-import org.mockito.Mockito.eq
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
-import org.mockito.MockitoAnnotations
-
-@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-@SmallTest
-class UserSwitcherControllerOldImplTest : SysuiTestCase() {
-    @Mock private lateinit var keyguardStateController: KeyguardStateController
-    @Mock private lateinit var activityManager: IActivityManager
-    @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
-    @Mock private lateinit var devicePolicyManager: DevicePolicyManager
-    @Mock private lateinit var handler: Handler
-    @Mock private lateinit var userTracker: UserTracker
-    @Mock private lateinit var userManager: UserManager
-    @Mock private lateinit var activityStarter: ActivityStarter
-    @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
-    @Mock private lateinit var broadcastSender: BroadcastSender
-    @Mock private lateinit var telephonyListenerManager: TelephonyListenerManager
-    @Mock private lateinit var secureSettings: SecureSettings
-    @Mock private lateinit var falsingManager: FalsingManager
-    @Mock private lateinit var dumpManager: DumpManager
-    @Mock private lateinit var interactionJankMonitor: InteractionJankMonitor
-    @Mock private lateinit var latencyTracker: LatencyTracker
-    @Mock private lateinit var dialogShower: UserSwitchDialogController.DialogShower
-    @Mock private lateinit var notificationShadeWindowView: NotificationShadeWindowView
-    @Mock private lateinit var threadedRenderer: ThreadedRenderer
-    @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
-    @Mock private lateinit var globalSettings: GlobalSettings
-    @Mock private lateinit var guestSessionNotification: GuestSessionNotification
-    @Mock private lateinit var guestResetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
-    private lateinit var resetSessionDialogFactory:
-                            GuestResumeSessionReceiver.ResetSessionDialog.Factory
-    private lateinit var guestResumeSessionReceiver: GuestResumeSessionReceiver
-    private lateinit var testableLooper: TestableLooper
-    private lateinit var bgExecutor: FakeExecutor
-    private lateinit var longRunningExecutor: FakeExecutor
-    private lateinit var uiExecutor: FakeExecutor
-    private lateinit var uiEventLogger: UiEventLoggerFake
-    private lateinit var userSwitcherController: UserSwitcherControllerOldImpl
-    private lateinit var picture: Bitmap
-    private val ownerId = UserHandle.USER_SYSTEM
-    private val ownerInfo = UserInfo(ownerId, "Owner", null,
-            UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL or UserInfo.FLAG_INITIALIZED or
-                    UserInfo.FLAG_PRIMARY or UserInfo.FLAG_SYSTEM or UserInfo.FLAG_ADMIN,
-            UserManager.USER_TYPE_FULL_SYSTEM)
-    private val guestId = 1234
-    private val guestInfo = UserInfo(guestId, "Guest", null,
-            UserInfo.FLAG_FULL or UserInfo.FLAG_GUEST, UserManager.USER_TYPE_FULL_GUEST)
-    private val secondaryUser =
-            UserInfo(10, "Secondary", null, 0, UserManager.USER_TYPE_FULL_SECONDARY)
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        testableLooper = TestableLooper.get(this)
-        bgExecutor = FakeExecutor(FakeSystemClock())
-        longRunningExecutor = FakeExecutor(FakeSystemClock())
-        uiExecutor = FakeExecutor(FakeSystemClock())
-        uiEventLogger = UiEventLoggerFake()
-
-        mContext.orCreateTestableResources.addOverride(
-                com.android.internal.R.bool.config_guestUserAutoCreated, false)
-
-        mContext.addMockSystemService(Context.FACE_SERVICE, mock(FaceManager::class.java))
-        mContext.addMockSystemService(Context.NOTIFICATION_SERVICE,
-                mock(NotificationManager::class.java))
-        mContext.addMockSystemService(Context.FINGERPRINT_SERVICE,
-                mock(FingerprintManager::class.java))
-
-        resetSessionDialogFactory = object : GuestResumeSessionReceiver.ResetSessionDialog.Factory {
-                override fun create(userId: Int): GuestResumeSessionReceiver.ResetSessionDialog {
-                    return GuestResumeSessionReceiver.ResetSessionDialog(
-                                mContext,
-                                mock(UserSwitcherController::class.java),
-                                uiEventLogger,
-                                userId
-                            )
-                }
-            }
-
-        guestResumeSessionReceiver = GuestResumeSessionReceiver(userTracker,
-                                        secureSettings,
-                                        broadcastDispatcher,
-                                        guestSessionNotification,
-                                        resetSessionDialogFactory)
-
-        `when`(userManager.canAddMoreUsers(eq(UserManager.USER_TYPE_FULL_SECONDARY)))
-                .thenReturn(true)
-        `when`(notificationShadeWindowView.context).thenReturn(context)
-
-        // Since userSwitcherController involves InteractionJankMonitor.
-        // Let's fulfill the dependencies.
-        val mockedContext = mock(Context::class.java)
-        doReturn(mockedContext).`when`(notificationShadeWindowView).context
-        doReturn(true).`when`(notificationShadeWindowView).isAttachedToWindow
-        doNothing().`when`(threadedRenderer).addObserver(any())
-        doNothing().`when`(threadedRenderer).removeObserver(any())
-        doReturn(threadedRenderer).`when`(notificationShadeWindowView).threadedRenderer
-
-        picture = UserIcons.convertToBitmap(context.getDrawable(R.drawable.ic_avatar_user))
-
-        // Create defaults for the current user
-        `when`(userTracker.userId).thenReturn(ownerId)
-        `when`(userTracker.userInfo).thenReturn(ownerInfo)
-
-        `when`(
-            globalSettings.getIntForUser(
-                eq(Settings.Global.ADD_USERS_WHEN_LOCKED),
-                anyInt(),
-                eq(UserHandle.USER_SYSTEM)
-            )
-        ).thenReturn(0)
-
-        `when`(
-            globalSettings.getIntForUser(
-                eq(Settings.Global.USER_SWITCHER_ENABLED),
-                anyInt(),
-                eq(UserHandle.USER_SYSTEM)
-            )
-        ).thenReturn(1)
-
-        setupController()
-    }
-
-    private fun setupController() {
-        userSwitcherController =
-            UserSwitcherControllerOldImpl(
-                mContext,
-                activityManager,
-                userManager,
-                userTracker,
-                keyguardStateController,
-                deviceProvisionedController,
-                devicePolicyManager,
-                handler,
-                activityStarter,
-                broadcastDispatcher,
-                broadcastSender,
-                uiEventLogger,
-                falsingManager,
-                telephonyListenerManager,
-                secureSettings,
-                globalSettings,
-                bgExecutor,
-                longRunningExecutor,
-                uiExecutor,
-                interactionJankMonitor,
-                latencyTracker,
-                dumpManager,
-                dialogLaunchAnimator,
-                guestResumeSessionReceiver,
-                guestResetOrExitSessionReceiver
-            )
-        userSwitcherController.init(notificationShadeWindowView)
-    }
-
-    @Test
-    fun testSwitchUser_parentDialogDismissed() {
-        val otherUserRecord = UserRecord(
-            secondaryUser,
-            picture,
-            false /* guest */,
-            false /* current */,
-            false /* isAddUser */,
-            false /* isRestricted */,
-            true /* isSwitchToEnabled */,
-            false /* isAddSupervisedUser */
-        )
-        `when`(userTracker.userId).thenReturn(ownerId)
-        `when`(userTracker.userInfo).thenReturn(ownerInfo)
-
-        userSwitcherController.onUserListItemClicked(otherUserRecord, dialogShower)
-        testableLooper.processAllMessages()
-
-        verify(dialogShower).dismiss()
-    }
-
-    @Test
-    fun testAddGuest_okButtonPressed() {
-        val emptyGuestUserRecord =
-            UserRecord(
-                null,
-                null,
-                true /* guest */,
-                false /* current */,
-                false /* isAddUser */,
-                false /* isRestricted */,
-                true /* isSwitchToEnabled */,
-                false /* isAddSupervisedUser */
-            )
-        `when`(userTracker.userId).thenReturn(ownerId)
-        `when`(userTracker.userInfo).thenReturn(ownerInfo)
-
-        `when`(userManager.createGuest(any())).thenReturn(guestInfo)
-
-        userSwitcherController.onUserListItemClicked(emptyGuestUserRecord, null)
-        bgExecutor.runAllReady()
-        uiExecutor.runAllReady()
-        testableLooper.processAllMessages()
-        verify(interactionJankMonitor).begin(any())
-        verify(latencyTracker).onActionStart(LatencyTracker.ACTION_USER_SWITCH)
-        verify(activityManager).switchUser(guestInfo.id)
-        assertEquals(1, uiEventLogger.numLogs())
-        assertEquals(QSUserSwitcherEvent.QS_USER_GUEST_ADD.id, uiEventLogger.eventId(0))
-    }
-
-    @Test
-    fun testAddGuest_parentDialogDismissed() {
-        val emptyGuestUserRecord =
-            UserRecord(
-                null,
-                null,
-                true /* guest */,
-                false /* current */,
-                false /* isAddUser */,
-                false /* isRestricted */,
-                true /* isSwitchToEnabled */,
-                false /* isAddSupervisedUser */
-            )
-        `when`(userTracker.userId).thenReturn(ownerId)
-        `when`(userTracker.userInfo).thenReturn(ownerInfo)
-
-        `when`(userManager.createGuest(any())).thenReturn(guestInfo)
-
-        userSwitcherController.onUserListItemClicked(emptyGuestUserRecord, dialogShower)
-        bgExecutor.runAllReady()
-        uiExecutor.runAllReady()
-        testableLooper.processAllMessages()
-        verify(dialogShower).dismiss()
-    }
-
-    @Test
-    fun testRemoveGuest_removeButtonPressed_isLogged() {
-        val currentGuestUserRecord =
-            UserRecord(
-                guestInfo,
-                picture,
-                true /* guest */,
-                true /* current */,
-                false /* isAddUser */,
-                false /* isRestricted */,
-                true /* isSwitchToEnabled */,
-                false /* isAddSupervisedUser */
-            )
-        `when`(userTracker.userId).thenReturn(guestInfo.id)
-        `when`(userTracker.userInfo).thenReturn(guestInfo)
-
-        userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null)
-        assertNotNull(userSwitcherController.mExitGuestDialog)
-        userSwitcherController.mExitGuestDialog
-                .getButton(DialogInterface.BUTTON_POSITIVE).performClick()
-        testableLooper.processAllMessages()
-        assertEquals(1, uiEventLogger.numLogs())
-        assertTrue(
-            QSUserSwitcherEvent.QS_USER_GUEST_REMOVE.id == uiEventLogger.eventId(0) ||
-            QSUserSwitcherEvent.QS_USER_SWITCH.id == uiEventLogger.eventId(0)
-        )
-    }
-
-    @Test
-    fun testRemoveGuest_removeButtonPressed_dialogDismissed() {
-        val currentGuestUserRecord =
-            UserRecord(
-                guestInfo,
-                picture,
-                true /* guest */,
-                true /* current */,
-                false /* isAddUser */,
-                false /* isRestricted */,
-                true /* isSwitchToEnabled */,
-                false /* isAddSupervisedUser */
-            )
-        `when`(userTracker.userId).thenReturn(guestInfo.id)
-        `when`(userTracker.userInfo).thenReturn(guestInfo)
-
-        userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null)
-        assertNotNull(userSwitcherController.mExitGuestDialog)
-        userSwitcherController.mExitGuestDialog
-                .getButton(DialogInterface.BUTTON_POSITIVE).performClick()
-        testableLooper.processAllMessages()
-        assertFalse(userSwitcherController.mExitGuestDialog.isShowing)
-    }
-
-    @Test
-    fun testRemoveGuest_dialogShowerUsed() {
-        val currentGuestUserRecord =
-            UserRecord(
-                guestInfo,
-                picture,
-                true /* guest */,
-                true /* current */,
-                false /* isAddUser */,
-                false /* isRestricted */,
-                true /* isSwitchToEnabled */,
-                false /* isAddSupervisedUser */
-            )
-        `when`(userTracker.userId).thenReturn(guestInfo.id)
-        `when`(userTracker.userInfo).thenReturn(guestInfo)
-
-        userSwitcherController.onUserListItemClicked(currentGuestUserRecord, dialogShower)
-        assertNotNull(userSwitcherController.mExitGuestDialog)
-        testableLooper.processAllMessages()
-        verify(dialogShower)
-            .showDialog(
-                userSwitcherController.mExitGuestDialog,
-                DialogCuj(InteractionJankMonitor.CUJ_USER_DIALOG_OPEN, "exit_guest_mode"))
-    }
-
-    @Test
-    fun testRemoveGuest_cancelButtonPressed_isNotLogged() {
-        val currentGuestUserRecord =
-            UserRecord(
-                guestInfo,
-                picture,
-                true /* guest */,
-                true /* current */,
-                false /* isAddUser */,
-                false /* isRestricted */,
-                true /* isSwitchToEnabled */,
-                false /* isAddSupervisedUser */
-            )
-        `when`(userTracker.userId).thenReturn(guestId)
-        `when`(userTracker.userInfo).thenReturn(guestInfo)
-
-        userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null)
-        assertNotNull(userSwitcherController.mExitGuestDialog)
-        userSwitcherController.mExitGuestDialog
-                .getButton(DialogInterface.BUTTON_NEUTRAL).performClick()
-        testableLooper.processAllMessages()
-        assertEquals(0, uiEventLogger.numLogs())
-    }
-
-    @Test
-    fun testWipeGuest_startOverButtonPressed_isLogged() {
-        val currentGuestUserRecord =
-            UserRecord(
-                guestInfo,
-                picture,
-                true /* guest */,
-                false /* current */,
-                false /* isAddUser */,
-                false /* isRestricted */,
-                true /* isSwitchToEnabled */,
-                false /* isAddSupervisedUser */
-            )
-        `when`(userTracker.userId).thenReturn(guestId)
-        `when`(userTracker.userInfo).thenReturn(guestInfo)
-
-        // Simulate that guest user has already logged in
-        `when`(secureSettings.getIntForUser(
-                eq(GuestResumeSessionReceiver.SETTING_GUEST_HAS_LOGGED_IN), anyInt(), anyInt()))
-                .thenReturn(1)
-
-        userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null)
-
-        // Simulate a user switch event
-        val intent = Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, guestId)
-
-        assertNotNull(userSwitcherController.mGuestResumeSessionReceiver)
-        userSwitcherController.mGuestResumeSessionReceiver.onReceive(context, intent)
-
-        assertNotNull(userSwitcherController.mGuestResumeSessionReceiver.mNewSessionDialog)
-        userSwitcherController.mGuestResumeSessionReceiver.mNewSessionDialog
-                .getButton(GuestResumeSessionReceiver.ResetSessionDialog.BUTTON_WIPE).performClick()
-        testableLooper.processAllMessages()
-        assertEquals(1, uiEventLogger.numLogs())
-        assertEquals(QSUserSwitcherEvent.QS_USER_GUEST_WIPE.id, uiEventLogger.eventId(0))
-    }
-
-    @Test
-    fun testWipeGuest_continueButtonPressed_isLogged() {
-        val currentGuestUserRecord =
-            UserRecord(
-                guestInfo,
-                picture,
-                true /* guest */,
-                false /* current */,
-                false /* isAddUser */,
-                false /* isRestricted */,
-                true /* isSwitchToEnabled */,
-                false /* isAddSupervisedUser */
-            )
-        `when`(userTracker.userId).thenReturn(guestId)
-        `when`(userTracker.userInfo).thenReturn(guestInfo)
-
-        // Simulate that guest user has already logged in
-        `when`(secureSettings.getIntForUser(
-                eq(GuestResumeSessionReceiver.SETTING_GUEST_HAS_LOGGED_IN), anyInt(), anyInt()))
-                .thenReturn(1)
-
-        userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null)
-
-        // Simulate a user switch event
-        val intent = Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, guestId)
-
-        assertNotNull(userSwitcherController.mGuestResumeSessionReceiver)
-        userSwitcherController.mGuestResumeSessionReceiver.onReceive(context, intent)
-
-        assertNotNull(userSwitcherController.mGuestResumeSessionReceiver.mNewSessionDialog)
-        userSwitcherController.mGuestResumeSessionReceiver.mNewSessionDialog
-                .getButton(GuestResumeSessionReceiver.ResetSessionDialog.BUTTON_DONTWIPE)
-                .performClick()
-        testableLooper.processAllMessages()
-        assertEquals(1, uiEventLogger.numLogs())
-        assertEquals(QSUserSwitcherEvent.QS_USER_GUEST_CONTINUE.id, uiEventLogger.eventId(0))
-    }
-
-    @Test
-    fun test_getCurrentUserName_shouldReturnNameOfTheCurrentUser() {
-        fun addUser(id: Int, name: String, isCurrent: Boolean) {
-            userSwitcherController.users.add(
-                UserRecord(
-                    UserInfo(id, name, 0),
-                    null, false, isCurrent, false,
-                    false, false, false
-                )
-            )
-        }
-        val bgUserName = "background_user"
-        val fgUserName = "foreground_user"
-
-        addUser(1, bgUserName, false)
-        addUser(2, fgUserName, true)
-
-        assertEquals(fgUserName, userSwitcherController.currentUserName)
-    }
-
-    @Test
-    fun isSystemUser_currentUserIsSystemUser_shouldReturnTrue() {
-        `when`(userTracker.userId).thenReturn(UserHandle.USER_SYSTEM)
-        assertEquals(true, userSwitcherController.isSystemUser)
-    }
-
-    @Test
-    fun isSystemUser_currentUserIsNotSystemUser_shouldReturnFalse() {
-        `when`(userTracker.userId).thenReturn(1)
-        assertEquals(false, userSwitcherController.isSystemUser)
-    }
-
-    @Test
-    fun testCanCreateSupervisedUserWithConfiguredPackage() {
-        // GIVEN the supervised user creation package is configured
-        `when`(context.getString(
-            com.android.internal.R.string.config_supervisedUserCreationPackage))
-            .thenReturn("some_pkg")
-
-        // AND the current user is allowed to create new users
-        `when`(userTracker.userId).thenReturn(ownerId)
-        `when`(userTracker.userInfo).thenReturn(ownerInfo)
-
-        // WHEN the controller is started with the above config
-        setupController()
-        testableLooper.processAllMessages()
-
-        // THEN a supervised user can be constructed
-        assertTrue(userSwitcherController.canCreateSupervisedUser())
-    }
-
-    @Test
-    fun testCannotCreateSupervisedUserWithConfiguredPackage() {
-        // GIVEN the supervised user creation package is NOT configured
-        `when`(context.getString(
-            com.android.internal.R.string.config_supervisedUserCreationPackage))
-            .thenReturn(null)
-
-        // AND the current user is allowed to create new users
-        `when`(userTracker.userId).thenReturn(ownerId)
-        `when`(userTracker.userInfo).thenReturn(ownerInfo)
-
-        // WHEN the controller is started with the above config
-        setupController()
-        testableLooper.processAllMessages()
-
-        // THEN a supervised user can NOT be constructed
-        assertFalse(userSwitcherController.canCreateSupervisedUser())
-    }
-
-    @Test
-    fun testCannotCreateUserWhenUserSwitcherDisabled() {
-        `when`(
-            globalSettings.getIntForUser(
-                eq(Settings.Global.USER_SWITCHER_ENABLED),
-                anyInt(),
-                eq(UserHandle.USER_SYSTEM)
-            )
-        ).thenReturn(0)
-        setupController()
-        assertFalse(userSwitcherController.canCreateUser())
-    }
-
-    @Test
-    fun testCannotCreateGuestUserWhenUserSwitcherDisabled() {
-        `when`(
-            globalSettings.getIntForUser(
-                eq(Settings.Global.USER_SWITCHER_ENABLED),
-                anyInt(),
-                eq(UserHandle.USER_SYSTEM)
-            )
-        ).thenReturn(0)
-        setupController()
-        assertFalse(userSwitcherController.canCreateGuest(false))
-    }
-
-    @Test
-    fun testCannotCreateSupervisedUserWhenUserSwitcherDisabled() {
-        `when`(
-            globalSettings.getIntForUser(
-                eq(Settings.Global.USER_SWITCHER_ENABLED),
-                anyInt(),
-                eq(UserHandle.USER_SYSTEM)
-            )
-        ).thenReturn(0)
-        setupController()
-        assertFalse(userSwitcherController.canCreateSupervisedUser())
-    }
-
-    @Test
-    fun testCanManageUser_userSwitcherEnabled_addUserWhenLocked() {
-        `when`(
-            globalSettings.getIntForUser(
-                eq(Settings.Global.USER_SWITCHER_ENABLED),
-                anyInt(),
-                eq(UserHandle.USER_SYSTEM)
-            )
-        ).thenReturn(1)
-
-        `when`(
-            globalSettings.getIntForUser(
-                eq(Settings.Global.ADD_USERS_WHEN_LOCKED),
-                anyInt(),
-                eq(UserHandle.USER_SYSTEM)
-            )
-        ).thenReturn(1)
-        setupController()
-        assertTrue(userSwitcherController.canManageUsers())
-    }
-
-    @Test
-    fun testCanManageUser_userSwitcherDisabled_addUserWhenLocked() {
-        `when`(
-            globalSettings.getIntForUser(
-                eq(Settings.Global.USER_SWITCHER_ENABLED),
-                anyInt(),
-                eq(UserHandle.USER_SYSTEM)
-            )
-        ).thenReturn(0)
-
-        `when`(
-            globalSettings.getIntForUser(
-                eq(Settings.Global.ADD_USERS_WHEN_LOCKED),
-                anyInt(),
-                eq(UserHandle.USER_SYSTEM)
-            )
-        ).thenReturn(1)
-        setupController()
-        assertFalse(userSwitcherController.canManageUsers())
-    }
-
-    @Test
-    fun testCanManageUser_userSwitcherEnabled_isAdmin() {
-        `when`(
-            globalSettings.getIntForUser(
-                eq(Settings.Global.USER_SWITCHER_ENABLED),
-                anyInt(),
-                eq(UserHandle.USER_SYSTEM)
-            )
-        ).thenReturn(1)
-
-        setupController()
-        assertTrue(userSwitcherController.canManageUsers())
-    }
-
-    @Test
-    fun testCanManageUser_userSwitcherDisabled_isAdmin() {
-        `when`(
-            globalSettings.getIntForUser(
-                eq(Settings.Global.USER_SWITCHER_ENABLED),
-                anyInt(),
-                eq(UserHandle.USER_SYSTEM)
-            )
-        ).thenReturn(0)
-
-        setupController()
-        assertFalse(userSwitcherController.canManageUsers())
-    }
-
-    @Test
-    fun addUserSwitchCallback() {
-        val broadcastReceiverCaptor = argumentCaptor<BroadcastReceiver>()
-        verify(broadcastDispatcher).registerReceiver(
-                capture(broadcastReceiverCaptor),
-                any(),
-                nullable(), nullable(), anyInt(), nullable())
-
-        val cb = mock(UserSwitcherController.UserSwitchCallback::class.java)
-        userSwitcherController.addUserSwitchCallback(cb)
-
-        val intent = Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, guestId)
-        broadcastReceiverCaptor.value.onReceive(context, intent)
-        verify(cb).onUserSwitched()
-    }
-
-    @Test
-    fun onUserItemClicked_guest_runsOnBgThread() {
-        val dialogShower = mock(UserSwitchDialogController.DialogShower::class.java)
-        val guestUserRecord = UserRecord(
-            null,
-            picture,
-            true /* guest */,
-            false /* current */,
-            false /* isAddUser */,
-            false /* isRestricted */,
-            true /* isSwitchToEnabled */,
-            false /* isAddSupervisedUser */
-        )
-
-        userSwitcherController.onUserListItemClicked(guestUserRecord, dialogShower)
-        assertTrue(bgExecutor.numPending() > 0)
-        verify(userManager, never()).createGuest(context)
-        bgExecutor.runAllReady()
-        verify(userManager).createGuest(context)
-    }
-
-    @Test
-    fun onUserItemClicked_manageUsers() {
-        val manageUserRecord = LegacyUserDataHelper.createRecord(
-            mContext,
-            ownerId,
-            UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
-            isRestricted = false,
-            isSwitchToEnabled = true
-        )
-
-        userSwitcherController.onUserListItemClicked(manageUserRecord, null)
-        val intentCaptor = kotlinArgumentCaptor<Intent>()
-        verify(activityStarter).startActivity(intentCaptor.capture(),
-            eq(true)
-        )
-        Truth.assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_USER_SETTINGS)
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ripple/MultiRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/MultiRippleControllerTest.kt
similarity index 95%
rename from packages/SystemUI/tests/src/com/android/systemui/ripple/MultiRippleControllerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/MultiRippleControllerTest.kt
index 05512e5..0d19ab1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ripple/MultiRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/MultiRippleControllerTest.kt
@@ -14,13 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.systemui.ripple
+package com.android.systemui.surfaceeffects.ripple
 
 import android.graphics.Color
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.ripple.MultiRippleController.Companion.MAX_RIPPLE_NUMBER
+import com.android.systemui.surfaceeffects.ripple.MultiRippleController.Companion.MAX_RIPPLE_NUMBER
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/MultiRippleViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/MultiRippleViewTest.kt
new file mode 100644
index 0000000..2024d53
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/MultiRippleViewTest.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.surfaceeffects.ripple
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class MultiRippleViewTest : SysuiTestCase() {
+    private val fakeSystemClock = FakeSystemClock()
+    // FakeExecutor is needed to run animator.
+    private val fakeExecutor = FakeExecutor(fakeSystemClock)
+
+    @Test
+    fun onRippleFinishes_triggersRippleFinished() {
+        val multiRippleView = MultiRippleView(context, null)
+        val multiRippleController = MultiRippleController(multiRippleView)
+        val rippleAnimationConfig = RippleAnimationConfig(duration = 1000L)
+
+        var isTriggered = false
+        val listener =
+            object : MultiRippleView.Companion.RipplesFinishedListener {
+                override fun onRipplesFinish() {
+                    isTriggered = true
+                }
+            }
+        multiRippleView.addRipplesFinishedListener(listener)
+
+        fakeExecutor.execute {
+            val rippleAnimation = RippleAnimation(rippleAnimationConfig)
+            multiRippleController.play(rippleAnimation)
+
+            fakeSystemClock.advanceTime(rippleAnimationConfig.duration)
+
+            assertThat(isTriggered).isTrue()
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ripple/RippleAnimationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/ripple/RippleAnimationTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationTest.kt
index 7662282..756397a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ripple/RippleAnimationTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.ripple
+package com.android.systemui.surfaceeffects.ripple
 
 import android.graphics.Color
 import android.testing.AndroidTestingRunner
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ripple/RippleViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleViewTest.kt
similarity index 95%
rename from packages/SystemUI/tests/src/com/android/systemui/ripple/RippleViewTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleViewTest.kt
index 2d2f4cc..1e5ab7e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ripple/RippleViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleViewTest.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.ripple
+package com.android.systemui.surfaceeffects.ripple
 
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
@@ -21,12 +21,10 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mock
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 class RippleViewTest : SysuiTestCase() {
-    @Mock
     private lateinit var rippleView: RippleView
 
     @Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseControllerTest.kt
new file mode 100644
index 0000000..d25c8c1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseControllerTest.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.surfaceeffects.turbulencenoise
+
+import android.graphics.Color
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class TurbulenceNoiseControllerTest : SysuiTestCase() {
+    private val fakeSystemClock = FakeSystemClock()
+    // FakeExecutor is needed to run animator.
+    private val fakeExecutor = FakeExecutor(fakeSystemClock)
+
+    @Test
+    fun play_playsTurbulenceNoise() {
+        val config = TurbulenceNoiseAnimationConfig(duration = 1000f)
+        val turbulenceNoiseView = TurbulenceNoiseView(context, null)
+
+        val turbulenceNoiseController = TurbulenceNoiseController(turbulenceNoiseView)
+
+        fakeExecutor.execute {
+            turbulenceNoiseController.play(config)
+
+            assertThat(turbulenceNoiseView.isPlaying).isTrue()
+
+            fakeSystemClock.advanceTime(config.duration.toLong())
+
+            assertThat(turbulenceNoiseView.isPlaying).isFalse()
+        }
+    }
+
+    @Test
+    fun updateColor_updatesCorrectColor() {
+        val config = TurbulenceNoiseAnimationConfig(duration = 1000f, color = Color.WHITE)
+        val turbulenceNoiseView = TurbulenceNoiseView(context, null)
+        val expectedColor = Color.RED
+
+        val turbulenceNoiseController = TurbulenceNoiseController(turbulenceNoiseView)
+
+        fakeExecutor.execute {
+            turbulenceNoiseController.play(config)
+
+            turbulenceNoiseView.updateColor(expectedColor)
+
+            fakeSystemClock.advanceTime(config.duration.toLong())
+
+            assertThat(config.color).isEqualTo(expectedColor)
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseViewTest.kt
new file mode 100644
index 0000000..633aac0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseViewTest.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.surfaceeffects.turbulencenoise
+
+import android.testing.AndroidTestingRunner
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class TurbulenceNoiseViewTest : SysuiTestCase() {
+
+    private val fakeSystemClock = FakeSystemClock()
+    // FakeExecutor is needed to run animator.
+    private val fakeExecutor = FakeExecutor(fakeSystemClock)
+
+    @Test
+    fun play_viewHasCorrectVisibility() {
+        val config = TurbulenceNoiseAnimationConfig(duration = 1000f)
+        val turbulenceNoiseView = TurbulenceNoiseView(context, null)
+
+        assertThat(turbulenceNoiseView.visibility).isEqualTo(View.INVISIBLE)
+
+        fakeExecutor.execute {
+            turbulenceNoiseView.play(config)
+
+            assertThat(turbulenceNoiseView.visibility).isEqualTo(View.VISIBLE)
+
+            fakeSystemClock.advanceTime(config.duration.toLong())
+
+            assertThat(turbulenceNoiseView.visibility).isEqualTo(View.INVISIBLE)
+        }
+    }
+
+    @Test
+    fun play_playsAnimation() {
+        val config = TurbulenceNoiseAnimationConfig(duration = 1000f)
+        val turbulenceNoiseView = TurbulenceNoiseView(context, null)
+
+        fakeExecutor.execute {
+            turbulenceNoiseView.play(config)
+
+            assertThat(turbulenceNoiseView.isPlaying).isTrue()
+        }
+    }
+
+    @Test
+    fun play_onEnd_triggersOnAnimationEnd() {
+        var animationEnd = false
+        val config =
+            TurbulenceNoiseAnimationConfig(
+                duration = 1000f,
+                onAnimationEnd = { animationEnd = true }
+            )
+        val turbulenceNoiseView = TurbulenceNoiseView(context, null)
+
+        fakeExecutor.execute {
+            turbulenceNoiseView.play(config)
+
+            assertThat(turbulenceNoiseView.isPlaying).isTrue()
+
+            fakeSystemClock.advanceTime(config.duration.toLong())
+
+            assertThat(animationEnd).isTrue()
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt
deleted file mode 100644
index 7c7f0e1..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt
+++ /dev/null
@@ -1,248 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.user.data.repository
-
-import android.content.pm.UserInfo
-import android.os.UserHandle
-import android.os.UserManager
-import android.provider.Settings
-import androidx.test.filters.SmallTest
-import com.android.systemui.user.data.model.UserSwitcherSettingsModel
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.cancel
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Mockito.`when` as whenever
-
-@SmallTest
-@RunWith(JUnit4::class)
-class UserRepositoryImplRefactoredTest : UserRepositoryImplTest() {
-
-    @Before
-    fun setUp() {
-        super.setUp(isRefactored = true)
-    }
-
-    @Test
-    fun userSwitcherSettings() = runSelfCancelingTest {
-        setUpGlobalSettings(
-            isSimpleUserSwitcher = true,
-            isAddUsersFromLockscreen = true,
-            isUserSwitcherEnabled = true,
-        )
-        underTest = create(this)
-
-        var value: UserSwitcherSettingsModel? = null
-        underTest.userSwitcherSettings.onEach { value = it }.launchIn(this)
-
-        assertUserSwitcherSettings(
-            model = value,
-            expectedSimpleUserSwitcher = true,
-            expectedAddUsersFromLockscreen = true,
-            expectedUserSwitcherEnabled = true,
-        )
-
-        setUpGlobalSettings(
-            isSimpleUserSwitcher = false,
-            isAddUsersFromLockscreen = true,
-            isUserSwitcherEnabled = true,
-        )
-        assertUserSwitcherSettings(
-            model = value,
-            expectedSimpleUserSwitcher = false,
-            expectedAddUsersFromLockscreen = true,
-            expectedUserSwitcherEnabled = true,
-        )
-    }
-
-    @Test
-    fun refreshUsers() = runSelfCancelingTest {
-        underTest = create(this)
-        val initialExpectedValue =
-            setUpUsers(
-                count = 3,
-                selectedIndex = 0,
-            )
-        var userInfos: List<UserInfo>? = null
-        var selectedUserInfo: UserInfo? = null
-        underTest.userInfos.onEach { userInfos = it }.launchIn(this)
-        underTest.selectedUserInfo.onEach { selectedUserInfo = it }.launchIn(this)
-
-        underTest.refreshUsers()
-        assertThat(userInfos).isEqualTo(initialExpectedValue)
-        assertThat(selectedUserInfo).isEqualTo(initialExpectedValue[0])
-        assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedUserInfo?.id)
-
-        val secondExpectedValue =
-            setUpUsers(
-                count = 4,
-                selectedIndex = 1,
-            )
-        underTest.refreshUsers()
-        assertThat(userInfos).isEqualTo(secondExpectedValue)
-        assertThat(selectedUserInfo).isEqualTo(secondExpectedValue[1])
-        assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedUserInfo?.id)
-
-        val selectedNonGuestUserId = selectedUserInfo?.id
-        val thirdExpectedValue =
-            setUpUsers(
-                count = 2,
-                isLastGuestUser = true,
-                selectedIndex = 1,
-            )
-        underTest.refreshUsers()
-        assertThat(userInfos).isEqualTo(thirdExpectedValue)
-        assertThat(selectedUserInfo).isEqualTo(thirdExpectedValue[1])
-        assertThat(selectedUserInfo?.isGuest).isTrue()
-        assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedNonGuestUserId)
-    }
-
-    @Test
-    fun `refreshUsers - sorts by creation time - guest user last`() = runSelfCancelingTest {
-        underTest = create(this)
-        val unsortedUsers =
-            setUpUsers(
-                count = 3,
-                selectedIndex = 0,
-                isLastGuestUser = true,
-            )
-        unsortedUsers[0].creationTime = 999
-        unsortedUsers[1].creationTime = 900
-        unsortedUsers[2].creationTime = 950
-        val expectedUsers =
-            listOf(
-                unsortedUsers[1],
-                unsortedUsers[0],
-                unsortedUsers[2], // last because this is the guest
-            )
-        var userInfos: List<UserInfo>? = null
-        underTest.userInfos.onEach { userInfos = it }.launchIn(this)
-
-        underTest.refreshUsers()
-        assertThat(userInfos).isEqualTo(expectedUsers)
-    }
-
-    @Test
-    fun `userTrackerCallback - updates selectedUserInfo`() = runSelfCancelingTest {
-        underTest = create(this)
-        var selectedUserInfo: UserInfo? = null
-        underTest.selectedUserInfo.onEach { selectedUserInfo = it }.launchIn(this)
-        setUpUsers(
-            count = 2,
-            selectedIndex = 0,
-        )
-        tracker.onProfileChanged()
-        assertThat(selectedUserInfo?.id == 0)
-        setUpUsers(
-            count = 2,
-            selectedIndex = 1,
-        )
-        tracker.onProfileChanged()
-        assertThat(selectedUserInfo?.id == 1)
-    }
-
-    private fun setUpUsers(
-        count: Int,
-        isLastGuestUser: Boolean = false,
-        selectedIndex: Int = 0,
-    ): List<UserInfo> {
-        val userInfos =
-            (0 until count).map { index ->
-                createUserInfo(
-                    index,
-                    isGuest = isLastGuestUser && index == count - 1,
-                )
-            }
-        whenever(manager.aliveUsers).thenReturn(userInfos)
-        tracker.set(userInfos, selectedIndex)
-        return userInfos
-    }
-
-    private fun createUserInfo(
-        id: Int,
-        isGuest: Boolean,
-    ): UserInfo {
-        val flags = 0
-        return UserInfo(
-            id,
-            "user_$id",
-            /* iconPath= */ "",
-            flags,
-            if (isGuest) UserManager.USER_TYPE_FULL_GUEST else UserInfo.getDefaultUserType(flags),
-        )
-    }
-
-    private fun setUpGlobalSettings(
-        isSimpleUserSwitcher: Boolean = false,
-        isAddUsersFromLockscreen: Boolean = false,
-        isUserSwitcherEnabled: Boolean = true,
-    ) {
-        context.orCreateTestableResources.addOverride(
-            com.android.internal.R.bool.config_expandLockScreenUserSwitcher,
-            true,
-        )
-        globalSettings.putIntForUser(
-            UserRepositoryImpl.SETTING_SIMPLE_USER_SWITCHER,
-            if (isSimpleUserSwitcher) 1 else 0,
-            UserHandle.USER_SYSTEM,
-        )
-        globalSettings.putIntForUser(
-            Settings.Global.ADD_USERS_WHEN_LOCKED,
-            if (isAddUsersFromLockscreen) 1 else 0,
-            UserHandle.USER_SYSTEM,
-        )
-        globalSettings.putIntForUser(
-            Settings.Global.USER_SWITCHER_ENABLED,
-            if (isUserSwitcherEnabled) 1 else 0,
-            UserHandle.USER_SYSTEM,
-        )
-    }
-
-    private fun assertUserSwitcherSettings(
-        model: UserSwitcherSettingsModel?,
-        expectedSimpleUserSwitcher: Boolean,
-        expectedAddUsersFromLockscreen: Boolean,
-        expectedUserSwitcherEnabled: Boolean,
-    ) {
-        checkNotNull(model)
-        assertThat(model.isSimpleUserSwitcher).isEqualTo(expectedSimpleUserSwitcher)
-        assertThat(model.isAddUsersFromLockscreen).isEqualTo(expectedAddUsersFromLockscreen)
-        assertThat(model.isUserSwitcherEnabled).isEqualTo(expectedUserSwitcherEnabled)
-    }
-
-    /**
-     * Executes the given block of execution within the scope of a dedicated [CoroutineScope] which
-     * is then automatically canceled and cleaned-up.
-     */
-    private fun runSelfCancelingTest(
-        block: suspend CoroutineScope.() -> Unit,
-    ) =
-        runBlocking(Dispatchers.Main.immediate) {
-            val scope = CoroutineScope(coroutineContext + Job())
-            block(scope)
-            scope.cancel()
-        }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
index dcea83a..2e527be1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
@@ -17,54 +17,263 @@
 
 package com.android.systemui.user.data.repository
 
+import android.content.pm.UserInfo
+import android.os.UserHandle
 import android.os.UserManager
+import android.provider.Settings
+import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.settings.FakeUserTracker
-import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.user.data.model.UserSwitcherSettingsModel
 import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.test.TestCoroutineScope
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
 import org.mockito.Mock
+import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
-abstract class UserRepositoryImplTest : SysuiTestCase() {
+@SmallTest
+@RunWith(JUnit4::class)
+class UserRepositoryImplTest : SysuiTestCase() {
 
-    @Mock protected lateinit var manager: UserManager
-    @Mock protected lateinit var controller: UserSwitcherController
+    @Mock private lateinit var manager: UserManager
 
-    protected lateinit var underTest: UserRepositoryImpl
+    private lateinit var underTest: UserRepositoryImpl
 
-    protected lateinit var globalSettings: FakeSettings
-    protected lateinit var tracker: FakeUserTracker
-    protected lateinit var featureFlags: FakeFeatureFlags
+    private lateinit var globalSettings: FakeSettings
+    private lateinit var tracker: FakeUserTracker
 
-    protected fun setUp(isRefactored: Boolean) {
+    @Before
+    fun setUp() {
         MockitoAnnotations.initMocks(this)
 
         globalSettings = FakeSettings()
         tracker = FakeUserTracker()
-        featureFlags = FakeFeatureFlags()
-        featureFlags.set(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER, !isRefactored)
     }
 
-    protected fun create(scope: CoroutineScope = TestCoroutineScope()): UserRepositoryImpl {
+    @Test
+    fun userSwitcherSettings() = runSelfCancelingTest {
+        setUpGlobalSettings(
+            isSimpleUserSwitcher = true,
+            isAddUsersFromLockscreen = true,
+            isUserSwitcherEnabled = true,
+        )
+        underTest = create(this)
+
+        var value: UserSwitcherSettingsModel? = null
+        underTest.userSwitcherSettings.onEach { value = it }.launchIn(this)
+
+        assertUserSwitcherSettings(
+            model = value,
+            expectedSimpleUserSwitcher = true,
+            expectedAddUsersFromLockscreen = true,
+            expectedUserSwitcherEnabled = true,
+        )
+
+        setUpGlobalSettings(
+            isSimpleUserSwitcher = false,
+            isAddUsersFromLockscreen = true,
+            isUserSwitcherEnabled = true,
+        )
+        assertUserSwitcherSettings(
+            model = value,
+            expectedSimpleUserSwitcher = false,
+            expectedAddUsersFromLockscreen = true,
+            expectedUserSwitcherEnabled = true,
+        )
+    }
+
+    @Test
+    fun refreshUsers() = runSelfCancelingTest {
+        underTest = create(this)
+        val initialExpectedValue =
+            setUpUsers(
+                count = 3,
+                selectedIndex = 0,
+            )
+        var userInfos: List<UserInfo>? = null
+        var selectedUserInfo: UserInfo? = null
+        underTest.userInfos.onEach { userInfos = it }.launchIn(this)
+        underTest.selectedUserInfo.onEach { selectedUserInfo = it }.launchIn(this)
+
+        underTest.refreshUsers()
+        assertThat(userInfos).isEqualTo(initialExpectedValue)
+        assertThat(selectedUserInfo).isEqualTo(initialExpectedValue[0])
+        assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedUserInfo?.id)
+
+        val secondExpectedValue =
+            setUpUsers(
+                count = 4,
+                selectedIndex = 1,
+            )
+        underTest.refreshUsers()
+        assertThat(userInfos).isEqualTo(secondExpectedValue)
+        assertThat(selectedUserInfo).isEqualTo(secondExpectedValue[1])
+        assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedUserInfo?.id)
+
+        val selectedNonGuestUserId = selectedUserInfo?.id
+        val thirdExpectedValue =
+            setUpUsers(
+                count = 2,
+                isLastGuestUser = true,
+                selectedIndex = 1,
+            )
+        underTest.refreshUsers()
+        assertThat(userInfos).isEqualTo(thirdExpectedValue)
+        assertThat(selectedUserInfo).isEqualTo(thirdExpectedValue[1])
+        assertThat(selectedUserInfo?.isGuest).isTrue()
+        assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedNonGuestUserId)
+    }
+
+    @Test
+    fun `refreshUsers - sorts by creation time - guest user last`() = runSelfCancelingTest {
+        underTest = create(this)
+        val unsortedUsers =
+            setUpUsers(
+                count = 3,
+                selectedIndex = 0,
+                isLastGuestUser = true,
+            )
+        unsortedUsers[0].creationTime = 999
+        unsortedUsers[1].creationTime = 900
+        unsortedUsers[2].creationTime = 950
+        val expectedUsers =
+            listOf(
+                unsortedUsers[1],
+                unsortedUsers[0],
+                unsortedUsers[2], // last because this is the guest
+            )
+        var userInfos: List<UserInfo>? = null
+        underTest.userInfos.onEach { userInfos = it }.launchIn(this)
+
+        underTest.refreshUsers()
+        assertThat(userInfos).isEqualTo(expectedUsers)
+    }
+
+    private fun setUpUsers(
+        count: Int,
+        isLastGuestUser: Boolean = false,
+        selectedIndex: Int = 0,
+    ): List<UserInfo> {
+        val userInfos =
+            (0 until count).map { index ->
+                createUserInfo(
+                    index,
+                    isGuest = isLastGuestUser && index == count - 1,
+                )
+            }
+        whenever(manager.aliveUsers).thenReturn(userInfos)
+        tracker.set(userInfos, selectedIndex)
+        return userInfos
+    }
+    @Test
+    fun `userTrackerCallback - updates selectedUserInfo`() = runSelfCancelingTest {
+        underTest = create(this)
+        var selectedUserInfo: UserInfo? = null
+        underTest.selectedUserInfo.onEach { selectedUserInfo = it }.launchIn(this)
+        setUpUsers(
+            count = 2,
+            selectedIndex = 0,
+        )
+        tracker.onProfileChanged()
+        assertThat(selectedUserInfo?.id).isEqualTo(0)
+        setUpUsers(
+            count = 2,
+            selectedIndex = 1,
+        )
+        tracker.onProfileChanged()
+        assertThat(selectedUserInfo?.id).isEqualTo(1)
+    }
+
+    private fun createUserInfo(
+        id: Int,
+        isGuest: Boolean,
+    ): UserInfo {
+        val flags = 0
+        return UserInfo(
+            id,
+            "user_$id",
+            /* iconPath= */ "",
+            flags,
+            if (isGuest) UserManager.USER_TYPE_FULL_GUEST else UserInfo.getDefaultUserType(flags),
+        )
+    }
+
+    private fun setUpGlobalSettings(
+        isSimpleUserSwitcher: Boolean = false,
+        isAddUsersFromLockscreen: Boolean = false,
+        isUserSwitcherEnabled: Boolean = true,
+    ) {
+        context.orCreateTestableResources.addOverride(
+            com.android.internal.R.bool.config_expandLockScreenUserSwitcher,
+            true,
+        )
+        globalSettings.putIntForUser(
+            UserRepositoryImpl.SETTING_SIMPLE_USER_SWITCHER,
+            if (isSimpleUserSwitcher) 1 else 0,
+            UserHandle.USER_SYSTEM,
+        )
+        globalSettings.putIntForUser(
+            Settings.Global.ADD_USERS_WHEN_LOCKED,
+            if (isAddUsersFromLockscreen) 1 else 0,
+            UserHandle.USER_SYSTEM,
+        )
+        globalSettings.putIntForUser(
+            Settings.Global.USER_SWITCHER_ENABLED,
+            if (isUserSwitcherEnabled) 1 else 0,
+            UserHandle.USER_SYSTEM,
+        )
+    }
+
+    private fun assertUserSwitcherSettings(
+        model: UserSwitcherSettingsModel?,
+        expectedSimpleUserSwitcher: Boolean,
+        expectedAddUsersFromLockscreen: Boolean,
+        expectedUserSwitcherEnabled: Boolean,
+    ) {
+        checkNotNull(model)
+        assertThat(model.isSimpleUserSwitcher).isEqualTo(expectedSimpleUserSwitcher)
+        assertThat(model.isAddUsersFromLockscreen).isEqualTo(expectedAddUsersFromLockscreen)
+        assertThat(model.isUserSwitcherEnabled).isEqualTo(expectedUserSwitcherEnabled)
+    }
+
+    /**
+     * Executes the given block of execution within the scope of a dedicated [CoroutineScope] which
+     * is then automatically canceled and cleaned-up.
+     */
+    private fun runSelfCancelingTest(
+        block: suspend CoroutineScope.() -> Unit,
+    ) =
+        runBlocking(Dispatchers.Main.immediate) {
+            val scope = CoroutineScope(coroutineContext + Job())
+            block(scope)
+            scope.cancel()
+        }
+
+    private fun create(scope: CoroutineScope = TestCoroutineScope()): UserRepositoryImpl {
         return UserRepositoryImpl(
             appContext = context,
             manager = manager,
-            controller = controller,
             applicationScope = scope,
             mainDispatcher = IMMEDIATE,
             backgroundDispatcher = IMMEDIATE,
             globalSettings = globalSettings,
             tracker = tracker,
-            featureFlags = featureFlags,
         )
     }
 
     companion object {
-        @JvmStatic protected val IMMEDIATE = Dispatchers.Main.immediate
+        @JvmStatic private val IMMEDIATE = Dispatchers.Main.immediate
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt
deleted file mode 100644
index a363a03..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.user.data.repository
-
-import android.content.pm.UserInfo
-import androidx.test.filters.SmallTest
-import com.android.systemui.statusbar.policy.UserSwitcherController
-import com.android.systemui.user.data.source.UserRecord
-import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.user.shared.model.UserModel
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.capture
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.ArgumentCaptor
-import org.mockito.Captor
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
-
-@SmallTest
-@RunWith(JUnit4::class)
-class UserRepositoryImplUnrefactoredTest : UserRepositoryImplTest() {
-
-    companion object {
-        private val IMMEDIATE = Dispatchers.Main.immediate
-    }
-
-    @Captor
-    private lateinit var userSwitchCallbackCaptor:
-        ArgumentCaptor<UserSwitcherController.UserSwitchCallback>
-
-    @Before
-    fun setUp() {
-        super.setUp(isRefactored = false)
-
-        whenever(controller.isAddUsersFromLockScreenEnabled).thenReturn(MutableStateFlow(false))
-        whenever(controller.isGuestUserAutoCreated).thenReturn(false)
-        whenever(controller.isGuestUserResetting).thenReturn(false)
-
-        underTest = create()
-    }
-
-    @Test
-    fun `users - registers for updates`() =
-        runBlocking(IMMEDIATE) {
-            val job = underTest.users.onEach {}.launchIn(this)
-
-            verify(controller).addUserSwitchCallback(any())
-
-            job.cancel()
-        }
-
-    @Test
-    fun `users - unregisters from updates`() =
-        runBlocking(IMMEDIATE) {
-            val job = underTest.users.onEach {}.launchIn(this)
-            verify(controller).addUserSwitchCallback(capture(userSwitchCallbackCaptor))
-
-            job.cancel()
-
-            verify(controller).removeUserSwitchCallback(userSwitchCallbackCaptor.value)
-        }
-
-    @Test
-    fun `users - does not include actions`() =
-        runBlocking(IMMEDIATE) {
-            whenever(controller.users)
-                .thenReturn(
-                    arrayListOf(
-                        createUserRecord(0, isSelected = true),
-                        createActionRecord(UserActionModel.ADD_USER),
-                        createUserRecord(1),
-                        createUserRecord(2),
-                        createActionRecord(UserActionModel.ADD_SUPERVISED_USER),
-                        createActionRecord(UserActionModel.ENTER_GUEST_MODE),
-                        createActionRecord(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT),
-                    )
-                )
-            var models: List<UserModel>? = null
-            val job = underTest.users.onEach { models = it }.launchIn(this)
-
-            assertThat(models).hasSize(3)
-            assertThat(models?.get(0)?.id).isEqualTo(0)
-            assertThat(models?.get(0)?.isSelected).isTrue()
-            assertThat(models?.get(1)?.id).isEqualTo(1)
-            assertThat(models?.get(1)?.isSelected).isFalse()
-            assertThat(models?.get(2)?.id).isEqualTo(2)
-            assertThat(models?.get(2)?.isSelected).isFalse()
-            job.cancel()
-        }
-
-    @Test
-    fun selectedUser() =
-        runBlocking(IMMEDIATE) {
-            whenever(controller.users)
-                .thenReturn(
-                    arrayListOf(
-                        createUserRecord(0, isSelected = true),
-                        createUserRecord(1),
-                        createUserRecord(2),
-                    )
-                )
-            var id: Int? = null
-            val job = underTest.selectedUser.map { it.id }.onEach { id = it }.launchIn(this)
-
-            assertThat(id).isEqualTo(0)
-
-            whenever(controller.users)
-                .thenReturn(
-                    arrayListOf(
-                        createUserRecord(0),
-                        createUserRecord(1),
-                        createUserRecord(2, isSelected = true),
-                    )
-                )
-            verify(controller).addUserSwitchCallback(capture(userSwitchCallbackCaptor))
-            userSwitchCallbackCaptor.value.onUserSwitched()
-            assertThat(id).isEqualTo(2)
-
-            job.cancel()
-        }
-
-    @Test
-    fun `actions - unregisters from updates`() =
-        runBlocking(IMMEDIATE) {
-            val job = underTest.actions.onEach {}.launchIn(this)
-            verify(controller).addUserSwitchCallback(capture(userSwitchCallbackCaptor))
-
-            job.cancel()
-
-            verify(controller).removeUserSwitchCallback(userSwitchCallbackCaptor.value)
-        }
-
-    @Test
-    fun `actions - registers for updates`() =
-        runBlocking(IMMEDIATE) {
-            val job = underTest.actions.onEach {}.launchIn(this)
-
-            verify(controller).addUserSwitchCallback(any())
-
-            job.cancel()
-        }
-
-    @Test
-    fun `actions - does not include users`() =
-        runBlocking(IMMEDIATE) {
-            whenever(controller.users)
-                .thenReturn(
-                    arrayListOf(
-                        createUserRecord(0, isSelected = true),
-                        createActionRecord(UserActionModel.ADD_USER),
-                        createUserRecord(1),
-                        createUserRecord(2),
-                        createActionRecord(UserActionModel.ADD_SUPERVISED_USER),
-                        createActionRecord(UserActionModel.ENTER_GUEST_MODE),
-                        createActionRecord(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT),
-                    )
-                )
-            var models: List<UserActionModel>? = null
-            val job = underTest.actions.onEach { models = it }.launchIn(this)
-
-            assertThat(models).hasSize(4)
-            assertThat(models?.get(0)).isEqualTo(UserActionModel.ADD_USER)
-            assertThat(models?.get(1)).isEqualTo(UserActionModel.ADD_SUPERVISED_USER)
-            assertThat(models?.get(2)).isEqualTo(UserActionModel.ENTER_GUEST_MODE)
-            assertThat(models?.get(3)).isEqualTo(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
-            job.cancel()
-        }
-
-    private fun createUserRecord(id: Int, isSelected: Boolean = false): UserRecord {
-        return UserRecord(
-            info = UserInfo(id, "name$id", 0),
-            isCurrent = isSelected,
-        )
-    }
-
-    private fun createActionRecord(action: UserActionModel): UserRecord {
-        return UserRecord(
-            isAddUser = action == UserActionModel.ADD_USER,
-            isAddSupervisedUser = action == UserActionModel.ADD_SUPERVISED_USER,
-            isGuest = action == UserActionModel.ENTER_GUEST_MODE,
-            isManageUsers = action == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
-        )
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt
deleted file mode 100644
index f682e31..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt
+++ /dev/null
@@ -1,740 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.user.domain.interactor
-
-import android.content.Intent
-import android.content.pm.UserInfo
-import android.graphics.Bitmap
-import android.graphics.drawable.Drawable
-import android.os.UserHandle
-import android.os.UserManager
-import android.provider.Settings
-import androidx.test.filters.SmallTest
-import com.android.internal.R.drawable.ic_account_circle
-import com.android.systemui.R
-import com.android.systemui.common.shared.model.Text
-import com.android.systemui.qs.user.UserSwitchDialogController
-import com.android.systemui.user.data.model.UserSwitcherSettingsModel
-import com.android.systemui.user.data.source.UserRecord
-import com.android.systemui.user.domain.model.ShowDialogRequestModel
-import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.user.shared.model.UserModel
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.kotlinArgumentCaptor
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.advanceUntilIdle
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.ArgumentMatchers.anyBoolean
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-
-@SmallTest
-@RunWith(JUnit4::class)
-class UserInteractorRefactoredTest : UserInteractorTest() {
-
-    override fun isRefactored(): Boolean {
-        return true
-    }
-
-    @Before
-    override fun setUp() {
-        super.setUp()
-
-        overrideResource(R.drawable.ic_account_circle, GUEST_ICON)
-        overrideResource(R.dimen.max_avatar_size, 10)
-        overrideResource(
-            com.android.internal.R.string.config_supervisedUserCreationPackage,
-            SUPERVISED_USER_CREATION_APP_PACKAGE,
-        )
-        whenever(manager.getUserIcon(anyInt())).thenReturn(ICON)
-        whenever(manager.canAddMoreUsers(any())).thenReturn(true)
-    }
-
-    @Test
-    fun `onRecordSelected - user`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 3, includeGuest = false)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-
-            underTest.onRecordSelected(UserRecord(info = userInfos[1]), dialogShower)
-
-            verify(dialogShower).dismiss()
-            verify(activityManager).switchUser(userInfos[1].id)
-            Unit
-        }
-
-    @Test
-    fun `onRecordSelected - switch to guest user`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 3, includeGuest = true)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-
-            underTest.onRecordSelected(UserRecord(info = userInfos.last()))
-
-            verify(activityManager).switchUser(userInfos.last().id)
-            Unit
-        }
-
-    @Test
-    fun `onRecordSelected - enter guest mode`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 3, includeGuest = false)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-            val guestUserInfo = createUserInfo(id = 1337, name = "guest", isGuest = true)
-            whenever(manager.createGuest(any())).thenReturn(guestUserInfo)
-
-            underTest.onRecordSelected(UserRecord(isGuest = true), dialogShower)
-
-            verify(dialogShower).dismiss()
-            verify(manager).createGuest(any())
-            Unit
-        }
-
-    @Test
-    fun `onRecordSelected - action`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 3, includeGuest = true)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-
-            underTest.onRecordSelected(UserRecord(isAddSupervisedUser = true), dialogShower)
-
-            verify(dialogShower, never()).dismiss()
-            verify(activityStarter).startActivity(any(), anyBoolean())
-        }
-
-    @Test
-    fun `users - switcher enabled`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 3, includeGuest = true)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-
-            var value: List<UserModel>? = null
-            val job = underTest.users.onEach { value = it }.launchIn(this)
-            assertUsers(models = value, count = 3, includeGuest = true)
-
-            job.cancel()
-        }
-
-    @Test
-    fun `users - switches to second user`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = false)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-
-            var value: List<UserModel>? = null
-            val job = underTest.users.onEach { value = it }.launchIn(this)
-            userRepository.setSelectedUserInfo(userInfos[1])
-
-            assertUsers(models = value, count = 2, selectedIndex = 1)
-            job.cancel()
-        }
-
-    @Test
-    fun `users - switcher not enabled`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = false)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = false))
-
-            var value: List<UserModel>? = null
-            val job = underTest.users.onEach { value = it }.launchIn(this)
-            assertUsers(models = value, count = 1)
-
-            job.cancel()
-        }
-
-    @Test
-    fun selectedUser() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = false)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-
-            var value: UserModel? = null
-            val job = underTest.selectedUser.onEach { value = it }.launchIn(this)
-            assertUser(value, id = 0, isSelected = true)
-
-            userRepository.setSelectedUserInfo(userInfos[1])
-            assertUser(value, id = 1, isSelected = true)
-
-            job.cancel()
-        }
-
-    @Test
-    fun `actions - device unlocked`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = false)
-
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-            keyguardRepository.setKeyguardShowing(false)
-            var value: List<UserActionModel>? = null
-            val job = underTest.actions.onEach { value = it }.launchIn(this)
-
-            assertThat(value)
-                .isEqualTo(
-                    listOf(
-                        UserActionModel.ENTER_GUEST_MODE,
-                        UserActionModel.ADD_USER,
-                        UserActionModel.ADD_SUPERVISED_USER,
-                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
-                    )
-                )
-
-            job.cancel()
-        }
-
-    @Test
-    fun `actions - device unlocked user not primary - empty list`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = false)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[1])
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-            keyguardRepository.setKeyguardShowing(false)
-            var value: List<UserActionModel>? = null
-            val job = underTest.actions.onEach { value = it }.launchIn(this)
-
-            assertThat(value).isEqualTo(emptyList<UserActionModel>())
-
-            job.cancel()
-        }
-
-    @Test
-    fun `actions - device unlocked user is guest - empty list`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = true)
-            assertThat(userInfos[1].isGuest).isTrue()
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[1])
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-            keyguardRepository.setKeyguardShowing(false)
-            var value: List<UserActionModel>? = null
-            val job = underTest.actions.onEach { value = it }.launchIn(this)
-
-            assertThat(value).isEqualTo(emptyList<UserActionModel>())
-
-            job.cancel()
-        }
-
-    @Test
-    fun `actions - device locked add from lockscreen set - full list`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = false)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            userRepository.setSettings(
-                UserSwitcherSettingsModel(
-                    isUserSwitcherEnabled = true,
-                    isAddUsersFromLockscreen = true,
-                )
-            )
-            keyguardRepository.setKeyguardShowing(false)
-            var value: List<UserActionModel>? = null
-            val job = underTest.actions.onEach { value = it }.launchIn(this)
-
-            assertThat(value)
-                .isEqualTo(
-                    listOf(
-                        UserActionModel.ENTER_GUEST_MODE,
-                        UserActionModel.ADD_USER,
-                        UserActionModel.ADD_SUPERVISED_USER,
-                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
-                    )
-                )
-
-            job.cancel()
-        }
-
-    @Test
-    fun `actions - device locked - only guest action and manage user is shown`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = false)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-            keyguardRepository.setKeyguardShowing(true)
-            var value: List<UserActionModel>? = null
-            val job = underTest.actions.onEach { value = it }.launchIn(this)
-
-            assertThat(value)
-                .isEqualTo(
-                    listOf(
-                        UserActionModel.ENTER_GUEST_MODE,
-                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT
-                    )
-                )
-
-            job.cancel()
-        }
-
-    @Test
-    fun `executeAction - add user - dialog shown`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = false)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            keyguardRepository.setKeyguardShowing(false)
-            var dialogRequest: ShowDialogRequestModel? = null
-            val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
-            val dialogShower: UserSwitchDialogController.DialogShower = mock()
-
-            underTest.executeAction(UserActionModel.ADD_USER, dialogShower)
-            assertThat(dialogRequest)
-                .isEqualTo(
-                    ShowDialogRequestModel.ShowAddUserDialog(
-                        userHandle = userInfos[0].userHandle,
-                        isKeyguardShowing = false,
-                        showEphemeralMessage = false,
-                        dialogShower = dialogShower,
-                    )
-                )
-
-            underTest.onDialogShown()
-            assertThat(dialogRequest).isNull()
-
-            job.cancel()
-        }
-
-    @Test
-    fun `executeAction - add supervised user - starts activity`() =
-        runBlocking(IMMEDIATE) {
-            underTest.executeAction(UserActionModel.ADD_SUPERVISED_USER)
-
-            val intentCaptor = kotlinArgumentCaptor<Intent>()
-            verify(activityStarter).startActivity(intentCaptor.capture(), eq(true))
-            assertThat(intentCaptor.value.action)
-                .isEqualTo(UserManager.ACTION_CREATE_SUPERVISED_USER)
-            assertThat(intentCaptor.value.`package`).isEqualTo(SUPERVISED_USER_CREATION_APP_PACKAGE)
-        }
-
-    @Test
-    fun `executeAction - navigate to manage users`() =
-        runBlocking(IMMEDIATE) {
-            underTest.executeAction(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
-
-            val intentCaptor = kotlinArgumentCaptor<Intent>()
-            verify(activityStarter).startActivity(intentCaptor.capture(), eq(true))
-            assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_USER_SETTINGS)
-        }
-
-    @Test
-    fun `executeAction - guest mode`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = false)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-            val guestUserInfo = createUserInfo(id = 1337, name = "guest", isGuest = true)
-            whenever(manager.createGuest(any())).thenReturn(guestUserInfo)
-            val dialogRequests = mutableListOf<ShowDialogRequestModel?>()
-            val showDialogsJob =
-                underTest.dialogShowRequests
-                    .onEach {
-                        dialogRequests.add(it)
-                        if (it != null) {
-                            underTest.onDialogShown()
-                        }
-                    }
-                    .launchIn(this)
-            val dismissDialogsJob =
-                underTest.dialogDismissRequests
-                    .onEach {
-                        if (it != null) {
-                            underTest.onDialogDismissed()
-                        }
-                    }
-                    .launchIn(this)
-
-            underTest.executeAction(UserActionModel.ENTER_GUEST_MODE)
-
-            assertThat(dialogRequests)
-                .contains(
-                    ShowDialogRequestModel.ShowUserCreationDialog(isGuest = true),
-                )
-            verify(activityManager).switchUser(guestUserInfo.id)
-
-            showDialogsJob.cancel()
-            dismissDialogsJob.cancel()
-        }
-
-    @Test
-    fun `selectUser - already selected guest re-selected - exit guest dialog`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = true)
-            val guestUserInfo = userInfos[1]
-            assertThat(guestUserInfo.isGuest).isTrue()
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(guestUserInfo)
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-            var dialogRequest: ShowDialogRequestModel? = null
-            val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
-
-            underTest.selectUser(
-                newlySelectedUserId = guestUserInfo.id,
-                dialogShower = dialogShower,
-            )
-
-            assertThat(dialogRequest)
-                .isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java)
-            verify(dialogShower, never()).dismiss()
-            job.cancel()
-        }
-
-    @Test
-    fun `selectUser - currently guest non-guest selected - exit guest dialog`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = true)
-            val guestUserInfo = userInfos[1]
-            assertThat(guestUserInfo.isGuest).isTrue()
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(guestUserInfo)
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-            var dialogRequest: ShowDialogRequestModel? = null
-            val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
-
-            underTest.selectUser(newlySelectedUserId = userInfos[0].id, dialogShower = dialogShower)
-
-            assertThat(dialogRequest)
-                .isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java)
-            verify(dialogShower, never()).dismiss()
-            job.cancel()
-        }
-
-    @Test
-    fun `selectUser - not currently guest - switches users`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = false)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-            var dialogRequest: ShowDialogRequestModel? = null
-            val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
-
-            underTest.selectUser(newlySelectedUserId = userInfos[1].id, dialogShower = dialogShower)
-
-            assertThat(dialogRequest).isNull()
-            verify(activityManager).switchUser(userInfos[1].id)
-            verify(dialogShower).dismiss()
-            job.cancel()
-        }
-
-    @Test
-    fun `Telephony call state changes - refreshes users`() =
-        runBlocking(IMMEDIATE) {
-            val refreshUsersCallCount = userRepository.refreshUsersCallCount
-
-            telephonyRepository.setCallState(1)
-
-            assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
-        }
-
-    @Test
-    fun `User switched broadcast`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = false)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-            val callback1: UserInteractor.UserCallback = mock()
-            val callback2: UserInteractor.UserCallback = mock()
-            underTest.addCallback(callback1)
-            underTest.addCallback(callback2)
-            val refreshUsersCallCount = userRepository.refreshUsersCallCount
-
-            userRepository.setSelectedUserInfo(userInfos[1])
-            fakeBroadcastDispatcher.registeredReceivers.forEach {
-                it.onReceive(
-                    context,
-                    Intent(Intent.ACTION_USER_SWITCHED)
-                        .putExtra(Intent.EXTRA_USER_HANDLE, userInfos[1].id),
-                )
-            }
-
-            verify(callback1).onUserStateChanged()
-            verify(callback2).onUserStateChanged()
-            assertThat(userRepository.secondaryUserId).isEqualTo(userInfos[1].id)
-            assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
-        }
-
-    @Test
-    fun `User info changed broadcast`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = false)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            val refreshUsersCallCount = userRepository.refreshUsersCallCount
-
-            fakeBroadcastDispatcher.registeredReceivers.forEach {
-                it.onReceive(
-                    context,
-                    Intent(Intent.ACTION_USER_INFO_CHANGED),
-                )
-            }
-
-            assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
-        }
-
-    @Test
-    fun `System user unlocked broadcast - refresh users`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = false)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            val refreshUsersCallCount = userRepository.refreshUsersCallCount
-
-            fakeBroadcastDispatcher.registeredReceivers.forEach {
-                it.onReceive(
-                    context,
-                    Intent(Intent.ACTION_USER_UNLOCKED)
-                        .putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_SYSTEM),
-                )
-            }
-
-            assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
-        }
-
-    @Test
-    fun `Non-system user unlocked broadcast - do not refresh users`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = false)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            val refreshUsersCallCount = userRepository.refreshUsersCallCount
-
-            fakeBroadcastDispatcher.registeredReceivers.forEach {
-                it.onReceive(
-                    context,
-                    Intent(Intent.ACTION_USER_UNLOCKED).putExtra(Intent.EXTRA_USER_HANDLE, 1337),
-                )
-            }
-
-            assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount)
-        }
-
-    @Test
-    fun userRecords() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 3, includeGuest = false)
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            keyguardRepository.setKeyguardShowing(false)
-
-            testCoroutineScope.advanceUntilIdle()
-
-            assertRecords(
-                records = underTest.userRecords.value,
-                userIds = listOf(0, 1, 2),
-                selectedUserIndex = 0,
-                includeGuest = false,
-                expectedActions =
-                    listOf(
-                        UserActionModel.ENTER_GUEST_MODE,
-                        UserActionModel.ADD_USER,
-                        UserActionModel.ADD_SUPERVISED_USER,
-                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
-                    ),
-            )
-        }
-
-    @Test
-    fun selectedUserRecord() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 3, includeGuest = true)
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            keyguardRepository.setKeyguardShowing(false)
-
-            assertRecordForUser(
-                record = underTest.selectedUserRecord.value,
-                id = 0,
-                hasPicture = true,
-                isCurrent = true,
-                isSwitchToEnabled = true,
-            )
-        }
-
-    private fun assertUsers(
-        models: List<UserModel>?,
-        count: Int,
-        selectedIndex: Int = 0,
-        includeGuest: Boolean = false,
-    ) {
-        checkNotNull(models)
-        assertThat(models.size).isEqualTo(count)
-        models.forEachIndexed { index, model ->
-            assertUser(
-                model = model,
-                id = index,
-                isSelected = index == selectedIndex,
-                isGuest = includeGuest && index == count - 1
-            )
-        }
-    }
-
-    private fun assertUser(
-        model: UserModel?,
-        id: Int,
-        isSelected: Boolean = false,
-        isGuest: Boolean = false,
-    ) {
-        checkNotNull(model)
-        assertThat(model.id).isEqualTo(id)
-        assertThat(model.name).isEqualTo(Text.Loaded(if (isGuest) "guest" else "user_$id"))
-        assertThat(model.isSelected).isEqualTo(isSelected)
-        assertThat(model.isSelectable).isTrue()
-        assertThat(model.isGuest).isEqualTo(isGuest)
-    }
-
-    private fun assertRecords(
-        records: List<UserRecord>,
-        userIds: List<Int>,
-        selectedUserIndex: Int = 0,
-        includeGuest: Boolean = false,
-        expectedActions: List<UserActionModel> = emptyList(),
-    ) {
-        assertThat(records.size >= userIds.size).isTrue()
-        userIds.indices.forEach { userIndex ->
-            val record = records[userIndex]
-            assertThat(record.info).isNotNull()
-            val isGuest = includeGuest && userIndex == userIds.size - 1
-            assertRecordForUser(
-                record = record,
-                id = userIds[userIndex],
-                hasPicture = !isGuest,
-                isCurrent = userIndex == selectedUserIndex,
-                isGuest = isGuest,
-                isSwitchToEnabled = true,
-            )
-        }
-
-        assertThat(records.size - userIds.size).isEqualTo(expectedActions.size)
-        (userIds.size until userIds.size + expectedActions.size).forEach { actionIndex ->
-            val record = records[actionIndex]
-            assertThat(record.info).isNull()
-            assertRecordForAction(
-                record = record,
-                type = expectedActions[actionIndex - userIds.size],
-            )
-        }
-    }
-
-    private fun assertRecordForUser(
-        record: UserRecord?,
-        id: Int? = null,
-        hasPicture: Boolean = false,
-        isCurrent: Boolean = false,
-        isGuest: Boolean = false,
-        isSwitchToEnabled: Boolean = false,
-    ) {
-        checkNotNull(record)
-        assertThat(record.info?.id).isEqualTo(id)
-        assertThat(record.picture != null).isEqualTo(hasPicture)
-        assertThat(record.isCurrent).isEqualTo(isCurrent)
-        assertThat(record.isGuest).isEqualTo(isGuest)
-        assertThat(record.isSwitchToEnabled).isEqualTo(isSwitchToEnabled)
-    }
-
-    private fun assertRecordForAction(
-        record: UserRecord,
-        type: UserActionModel,
-    ) {
-        assertThat(record.isGuest).isEqualTo(type == UserActionModel.ENTER_GUEST_MODE)
-        assertThat(record.isAddUser).isEqualTo(type == UserActionModel.ADD_USER)
-        assertThat(record.isAddSupervisedUser)
-            .isEqualTo(type == UserActionModel.ADD_SUPERVISED_USER)
-    }
-
-    private fun createUserInfos(
-        count: Int,
-        includeGuest: Boolean,
-    ): List<UserInfo> {
-        return (0 until count).map { index ->
-            val isGuest = includeGuest && index == count - 1
-            createUserInfo(
-                id = index,
-                name =
-                    if (isGuest) {
-                        "guest"
-                    } else {
-                        "user_$index"
-                    },
-                isPrimary = !isGuest && index == 0,
-                isGuest = isGuest,
-            )
-        }
-    }
-
-    private fun createUserInfo(
-        id: Int,
-        name: String,
-        isPrimary: Boolean = false,
-        isGuest: Boolean = false,
-    ): UserInfo {
-        return UserInfo(
-            id,
-            name,
-            /* iconPath= */ "",
-            /* flags= */ if (isPrimary) {
-                UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN
-            } else {
-                0
-            },
-            if (isGuest) {
-                UserManager.USER_TYPE_FULL_GUEST
-            } else {
-                UserManager.USER_TYPE_FULL_SYSTEM
-            },
-        )
-    }
-
-    companion object {
-        private val IMMEDIATE = Dispatchers.Main.immediate
-        private val ICON = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
-        private val GUEST_ICON: Drawable = mock()
-        private const val SUPERVISED_USER_CREATION_APP_PACKAGE = "supervisedUserCreation"
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
index 58f5531..8fb98c1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
@@ -19,51 +19,90 @@
 
 import android.app.ActivityManager
 import android.app.admin.DevicePolicyManager
+import android.content.Intent
+import android.content.pm.UserInfo
+import android.graphics.Bitmap
+import android.graphics.drawable.Drawable
+import android.os.UserHandle
 import android.os.UserManager
+import android.provider.Settings
+import androidx.test.filters.SmallTest
+import com.android.internal.R.drawable.ic_account_circle
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.GuestResetOrExitSessionReceiver
 import com.android.systemui.GuestResumeSessionReceiver
+import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
+import com.android.systemui.common.shared.model.Text
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.qs.user.UserSwitchDialogController
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
-import com.android.systemui.statusbar.policy.UserSwitcherController
 import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
 import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
+import com.android.systemui.user.data.model.UserSwitcherSettingsModel
 import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.data.source.UserRecord
+import com.android.systemui.user.domain.model.ShowDialogRequestModel
+import com.android.systemui.user.shared.model.UserActionModel
+import com.android.systemui.user.shared.model.UserModel
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.kotlinArgumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.test.TestCoroutineScope
+import kotlinx.coroutines.test.advanceUntilIdle
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
-abstract class UserInteractorTest : SysuiTestCase() {
+@SmallTest
+@RunWith(JUnit4::class)
+class UserInteractorTest : SysuiTestCase() {
 
-    @Mock protected lateinit var controller: UserSwitcherController
-    @Mock protected lateinit var activityStarter: ActivityStarter
-    @Mock protected lateinit var manager: UserManager
-    @Mock protected lateinit var activityManager: ActivityManager
-    @Mock protected lateinit var deviceProvisionedController: DeviceProvisionedController
-    @Mock protected lateinit var devicePolicyManager: DevicePolicyManager
-    @Mock protected lateinit var uiEventLogger: UiEventLogger
-    @Mock protected lateinit var dialogShower: UserSwitchDialogController.DialogShower
+    @Mock private lateinit var activityStarter: ActivityStarter
+    @Mock private lateinit var manager: UserManager
+    @Mock private lateinit var activityManager: ActivityManager
+    @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
+    @Mock private lateinit var devicePolicyManager: DevicePolicyManager
+    @Mock private lateinit var uiEventLogger: UiEventLogger
+    @Mock private lateinit var dialogShower: UserSwitchDialogController.DialogShower
     @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
     @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
 
-    protected lateinit var underTest: UserInteractor
+    private lateinit var underTest: UserInteractor
 
-    protected lateinit var testCoroutineScope: TestCoroutineScope
-    protected lateinit var userRepository: FakeUserRepository
-    protected lateinit var keyguardRepository: FakeKeyguardRepository
-    protected lateinit var telephonyRepository: FakeTelephonyRepository
+    private lateinit var testCoroutineScope: TestCoroutineScope
+    private lateinit var userRepository: FakeUserRepository
+    private lateinit var keyguardRepository: FakeKeyguardRepository
+    private lateinit var telephonyRepository: FakeTelephonyRepository
 
-    abstract fun isRefactored(): Boolean
-
-    open fun setUp() {
+    @Before
+    fun setUp() {
         MockitoAnnotations.initMocks(this)
+        whenever(manager.getUserIcon(anyInt())).thenReturn(ICON)
+        whenever(manager.canAddMoreUsers(any())).thenReturn(true)
+
+        overrideResource(R.drawable.ic_account_circle, GUEST_ICON)
+        overrideResource(R.dimen.max_avatar_size, 10)
+        overrideResource(
+            com.android.internal.R.string.config_supervisedUserCreationPackage,
+            SUPERVISED_USER_CREATION_APP_PACKAGE,
+        )
 
         userRepository = FakeUserRepository()
         keyguardRepository = FakeKeyguardRepository()
@@ -79,16 +118,11 @@
             UserInteractor(
                 applicationContext = context,
                 repository = userRepository,
-                controller = controller,
                 activityStarter = activityStarter,
                 keyguardInteractor =
                     KeyguardInteractor(
                         repository = keyguardRepository,
                     ),
-                featureFlags =
-                    FakeFeatureFlags().apply {
-                        set(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER, !isRefactored())
-                    },
                 manager = manager,
                 applicationScope = testCoroutineScope,
                 telephonyInteractor =
@@ -117,7 +151,665 @@
             )
     }
 
+    @Test
+    fun `onRecordSelected - user`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 3, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+            underTest.onRecordSelected(UserRecord(info = userInfos[1]), dialogShower)
+
+            verify(dialogShower).dismiss()
+            verify(activityManager).switchUser(userInfos[1].id)
+            Unit
+        }
+
+    @Test
+    fun `onRecordSelected - switch to guest user`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 3, includeGuest = true)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+            underTest.onRecordSelected(UserRecord(info = userInfos.last()))
+
+            verify(activityManager).switchUser(userInfos.last().id)
+            Unit
+        }
+
+    @Test
+    fun `onRecordSelected - enter guest mode`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 3, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            val guestUserInfo = createUserInfo(id = 1337, name = "guest", isGuest = true)
+            whenever(manager.createGuest(any())).thenReturn(guestUserInfo)
+
+            underTest.onRecordSelected(UserRecord(isGuest = true), dialogShower)
+
+            verify(dialogShower).dismiss()
+            verify(manager).createGuest(any())
+            Unit
+        }
+
+    @Test
+    fun `onRecordSelected - action`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 3, includeGuest = true)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+            underTest.onRecordSelected(UserRecord(isAddSupervisedUser = true), dialogShower)
+
+            verify(dialogShower, never()).dismiss()
+            verify(activityStarter).startActivity(any(), anyBoolean())
+        }
+
+    @Test
+    fun `users - switcher enabled`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 3, includeGuest = true)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+            var value: List<UserModel>? = null
+            val job = underTest.users.onEach { value = it }.launchIn(this)
+            assertUsers(models = value, count = 3, includeGuest = true)
+
+            job.cancel()
+        }
+
+    @Test
+    fun `users - switches to second user`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+            var value: List<UserModel>? = null
+            val job = underTest.users.onEach { value = it }.launchIn(this)
+            userRepository.setSelectedUserInfo(userInfos[1])
+
+            assertUsers(models = value, count = 2, selectedIndex = 1)
+            job.cancel()
+        }
+
+    @Test
+    fun `users - switcher not enabled`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = false))
+
+            var value: List<UserModel>? = null
+            val job = underTest.users.onEach { value = it }.launchIn(this)
+            assertUsers(models = value, count = 1)
+
+            job.cancel()
+        }
+
+    @Test
+    fun selectedUser() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+            var value: UserModel? = null
+            val job = underTest.selectedUser.onEach { value = it }.launchIn(this)
+            assertUser(value, id = 0, isSelected = true)
+
+            userRepository.setSelectedUserInfo(userInfos[1])
+            assertUser(value, id = 1, isSelected = true)
+
+            job.cancel()
+        }
+
+    @Test
+    fun `actions - device unlocked`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            keyguardRepository.setKeyguardShowing(false)
+            var value: List<UserActionModel>? = null
+            val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+            assertThat(value)
+                .isEqualTo(
+                    listOf(
+                        UserActionModel.ENTER_GUEST_MODE,
+                        UserActionModel.ADD_USER,
+                        UserActionModel.ADD_SUPERVISED_USER,
+                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+                    )
+                )
+
+            job.cancel()
+        }
+
+    @Test
+    fun `actions - device unlocked user not primary - empty list`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[1])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            keyguardRepository.setKeyguardShowing(false)
+            var value: List<UserActionModel>? = null
+            val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+            assertThat(value).isEqualTo(emptyList<UserActionModel>())
+
+            job.cancel()
+        }
+
+    @Test
+    fun `actions - device unlocked user is guest - empty list`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = true)
+            assertThat(userInfos[1].isGuest).isTrue()
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[1])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            keyguardRepository.setKeyguardShowing(false)
+            var value: List<UserActionModel>? = null
+            val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+            assertThat(value).isEqualTo(emptyList<UserActionModel>())
+
+            job.cancel()
+        }
+
+    @Test
+    fun `actions - device locked add from lockscreen set - full list`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(
+                UserSwitcherSettingsModel(
+                    isUserSwitcherEnabled = true,
+                    isAddUsersFromLockscreen = true,
+                )
+            )
+            keyguardRepository.setKeyguardShowing(false)
+            var value: List<UserActionModel>? = null
+            val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+            assertThat(value)
+                .isEqualTo(
+                    listOf(
+                        UserActionModel.ENTER_GUEST_MODE,
+                        UserActionModel.ADD_USER,
+                        UserActionModel.ADD_SUPERVISED_USER,
+                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+                    )
+                )
+
+            job.cancel()
+        }
+
+    @Test
+    fun `actions - device locked - only guest action and manage user is shown`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            keyguardRepository.setKeyguardShowing(true)
+            var value: List<UserActionModel>? = null
+            val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+            assertThat(value)
+                .isEqualTo(
+                    listOf(
+                        UserActionModel.ENTER_GUEST_MODE,
+                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT
+                    )
+                )
+
+            job.cancel()
+        }
+
+    @Test
+    fun `executeAction - add user - dialog shown`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            keyguardRepository.setKeyguardShowing(false)
+            var dialogRequest: ShowDialogRequestModel? = null
+            val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+            val dialogShower: UserSwitchDialogController.DialogShower = mock()
+
+            underTest.executeAction(UserActionModel.ADD_USER, dialogShower)
+            assertThat(dialogRequest)
+                .isEqualTo(
+                    ShowDialogRequestModel.ShowAddUserDialog(
+                        userHandle = userInfos[0].userHandle,
+                        isKeyguardShowing = false,
+                        showEphemeralMessage = false,
+                        dialogShower = dialogShower,
+                    )
+                )
+
+            underTest.onDialogShown()
+            assertThat(dialogRequest).isNull()
+
+            job.cancel()
+        }
+
+    @Test
+    fun `executeAction - add supervised user - starts activity`() =
+        runBlocking(IMMEDIATE) {
+            underTest.executeAction(UserActionModel.ADD_SUPERVISED_USER)
+
+            val intentCaptor = kotlinArgumentCaptor<Intent>()
+            verify(activityStarter).startActivity(intentCaptor.capture(), eq(true))
+            assertThat(intentCaptor.value.action)
+                .isEqualTo(UserManager.ACTION_CREATE_SUPERVISED_USER)
+            assertThat(intentCaptor.value.`package`).isEqualTo(SUPERVISED_USER_CREATION_APP_PACKAGE)
+        }
+
+    @Test
+    fun `executeAction - navigate to manage users`() =
+        runBlocking(IMMEDIATE) {
+            underTest.executeAction(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
+
+            val intentCaptor = kotlinArgumentCaptor<Intent>()
+            verify(activityStarter).startActivity(intentCaptor.capture(), eq(true))
+            assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_USER_SETTINGS)
+        }
+
+    @Test
+    fun `executeAction - guest mode`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            val guestUserInfo = createUserInfo(id = 1337, name = "guest", isGuest = true)
+            whenever(manager.createGuest(any())).thenReturn(guestUserInfo)
+            val dialogRequests = mutableListOf<ShowDialogRequestModel?>()
+            val showDialogsJob =
+                underTest.dialogShowRequests
+                    .onEach {
+                        dialogRequests.add(it)
+                        if (it != null) {
+                            underTest.onDialogShown()
+                        }
+                    }
+                    .launchIn(this)
+            val dismissDialogsJob =
+                underTest.dialogDismissRequests
+                    .onEach {
+                        if (it != null) {
+                            underTest.onDialogDismissed()
+                        }
+                    }
+                    .launchIn(this)
+
+            underTest.executeAction(UserActionModel.ENTER_GUEST_MODE)
+
+            assertThat(dialogRequests)
+                .contains(
+                    ShowDialogRequestModel.ShowUserCreationDialog(isGuest = true),
+                )
+            verify(activityManager).switchUser(guestUserInfo.id)
+
+            showDialogsJob.cancel()
+            dismissDialogsJob.cancel()
+        }
+
+    @Test
+    fun `selectUser - already selected guest re-selected - exit guest dialog`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = true)
+            val guestUserInfo = userInfos[1]
+            assertThat(guestUserInfo.isGuest).isTrue()
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(guestUserInfo)
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            var dialogRequest: ShowDialogRequestModel? = null
+            val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+
+            underTest.selectUser(
+                newlySelectedUserId = guestUserInfo.id,
+                dialogShower = dialogShower,
+            )
+
+            assertThat(dialogRequest)
+                .isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java)
+            verify(dialogShower, never()).dismiss()
+            job.cancel()
+        }
+
+    @Test
+    fun `selectUser - currently guest non-guest selected - exit guest dialog`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = true)
+            val guestUserInfo = userInfos[1]
+            assertThat(guestUserInfo.isGuest).isTrue()
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(guestUserInfo)
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            var dialogRequest: ShowDialogRequestModel? = null
+            val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+
+            underTest.selectUser(newlySelectedUserId = userInfos[0].id, dialogShower = dialogShower)
+
+            assertThat(dialogRequest)
+                .isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java)
+            verify(dialogShower, never()).dismiss()
+            job.cancel()
+        }
+
+    @Test
+    fun `selectUser - not currently guest - switches users`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            var dialogRequest: ShowDialogRequestModel? = null
+            val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+
+            underTest.selectUser(newlySelectedUserId = userInfos[1].id, dialogShower = dialogShower)
+
+            assertThat(dialogRequest).isNull()
+            verify(activityManager).switchUser(userInfos[1].id)
+            verify(dialogShower).dismiss()
+            job.cancel()
+        }
+
+    @Test
+    fun `Telephony call state changes - refreshes users`() =
+        runBlocking(IMMEDIATE) {
+            val refreshUsersCallCount = userRepository.refreshUsersCallCount
+
+            telephonyRepository.setCallState(1)
+
+            assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
+        }
+
+    @Test
+    fun `User switched broadcast`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            val callback1: UserInteractor.UserCallback = mock()
+            val callback2: UserInteractor.UserCallback = mock()
+            underTest.addCallback(callback1)
+            underTest.addCallback(callback2)
+            val refreshUsersCallCount = userRepository.refreshUsersCallCount
+
+            userRepository.setSelectedUserInfo(userInfos[1])
+            fakeBroadcastDispatcher.registeredReceivers.forEach {
+                it.onReceive(
+                    context,
+                    Intent(Intent.ACTION_USER_SWITCHED)
+                        .putExtra(Intent.EXTRA_USER_HANDLE, userInfos[1].id),
+                )
+            }
+
+            verify(callback1).onUserStateChanged()
+            verify(callback2).onUserStateChanged()
+            assertThat(userRepository.secondaryUserId).isEqualTo(userInfos[1].id)
+            assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
+        }
+
+    @Test
+    fun `User info changed broadcast`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            val refreshUsersCallCount = userRepository.refreshUsersCallCount
+
+            fakeBroadcastDispatcher.registeredReceivers.forEach {
+                it.onReceive(
+                    context,
+                    Intent(Intent.ACTION_USER_INFO_CHANGED),
+                )
+            }
+
+            assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
+        }
+
+    @Test
+    fun `System user unlocked broadcast - refresh users`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            val refreshUsersCallCount = userRepository.refreshUsersCallCount
+
+            fakeBroadcastDispatcher.registeredReceivers.forEach {
+                it.onReceive(
+                    context,
+                    Intent(Intent.ACTION_USER_UNLOCKED)
+                        .putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_SYSTEM),
+                )
+            }
+
+            assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
+        }
+
+    @Test
+    fun `Non-system user unlocked broadcast - do not refresh users`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            val refreshUsersCallCount = userRepository.refreshUsersCallCount
+
+            fakeBroadcastDispatcher.registeredReceivers.forEach {
+                it.onReceive(
+                    context,
+                    Intent(Intent.ACTION_USER_UNLOCKED).putExtra(Intent.EXTRA_USER_HANDLE, 1337),
+                )
+            }
+
+            assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount)
+        }
+
+    @Test
+    fun userRecords() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 3, includeGuest = false)
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            keyguardRepository.setKeyguardShowing(false)
+
+            testCoroutineScope.advanceUntilIdle()
+
+            assertRecords(
+                records = underTest.userRecords.value,
+                userIds = listOf(0, 1, 2),
+                selectedUserIndex = 0,
+                includeGuest = false,
+                expectedActions =
+                    listOf(
+                        UserActionModel.ENTER_GUEST_MODE,
+                        UserActionModel.ADD_USER,
+                        UserActionModel.ADD_SUPERVISED_USER,
+                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+                    ),
+            )
+        }
+
+    @Test
+    fun selectedUserRecord() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 3, includeGuest = true)
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            keyguardRepository.setKeyguardShowing(false)
+
+            assertRecordForUser(
+                record = underTest.selectedUserRecord.value,
+                id = 0,
+                hasPicture = true,
+                isCurrent = true,
+                isSwitchToEnabled = true,
+            )
+        }
+
+    private fun assertUsers(
+        models: List<UserModel>?,
+        count: Int,
+        selectedIndex: Int = 0,
+        includeGuest: Boolean = false,
+    ) {
+        checkNotNull(models)
+        assertThat(models.size).isEqualTo(count)
+        models.forEachIndexed { index, model ->
+            assertUser(
+                model = model,
+                id = index,
+                isSelected = index == selectedIndex,
+                isGuest = includeGuest && index == count - 1
+            )
+        }
+    }
+
+    private fun assertUser(
+        model: UserModel?,
+        id: Int,
+        isSelected: Boolean = false,
+        isGuest: Boolean = false,
+    ) {
+        checkNotNull(model)
+        assertThat(model.id).isEqualTo(id)
+        assertThat(model.name).isEqualTo(Text.Loaded(if (isGuest) "guest" else "user_$id"))
+        assertThat(model.isSelected).isEqualTo(isSelected)
+        assertThat(model.isSelectable).isTrue()
+        assertThat(model.isGuest).isEqualTo(isGuest)
+    }
+
+    private fun assertRecords(
+        records: List<UserRecord>,
+        userIds: List<Int>,
+        selectedUserIndex: Int = 0,
+        includeGuest: Boolean = false,
+        expectedActions: List<UserActionModel> = emptyList(),
+    ) {
+        assertThat(records.size >= userIds.size).isTrue()
+        userIds.indices.forEach { userIndex ->
+            val record = records[userIndex]
+            assertThat(record.info).isNotNull()
+            val isGuest = includeGuest && userIndex == userIds.size - 1
+            assertRecordForUser(
+                record = record,
+                id = userIds[userIndex],
+                hasPicture = !isGuest,
+                isCurrent = userIndex == selectedUserIndex,
+                isGuest = isGuest,
+                isSwitchToEnabled = true,
+            )
+        }
+
+        assertThat(records.size - userIds.size).isEqualTo(expectedActions.size)
+        (userIds.size until userIds.size + expectedActions.size).forEach { actionIndex ->
+            val record = records[actionIndex]
+            assertThat(record.info).isNull()
+            assertRecordForAction(
+                record = record,
+                type = expectedActions[actionIndex - userIds.size],
+            )
+        }
+    }
+
+    private fun assertRecordForUser(
+        record: UserRecord?,
+        id: Int? = null,
+        hasPicture: Boolean = false,
+        isCurrent: Boolean = false,
+        isGuest: Boolean = false,
+        isSwitchToEnabled: Boolean = false,
+    ) {
+        checkNotNull(record)
+        assertThat(record.info?.id).isEqualTo(id)
+        assertThat(record.picture != null).isEqualTo(hasPicture)
+        assertThat(record.isCurrent).isEqualTo(isCurrent)
+        assertThat(record.isGuest).isEqualTo(isGuest)
+        assertThat(record.isSwitchToEnabled).isEqualTo(isSwitchToEnabled)
+    }
+
+    private fun assertRecordForAction(
+        record: UserRecord,
+        type: UserActionModel,
+    ) {
+        assertThat(record.isGuest).isEqualTo(type == UserActionModel.ENTER_GUEST_MODE)
+        assertThat(record.isAddUser).isEqualTo(type == UserActionModel.ADD_USER)
+        assertThat(record.isAddSupervisedUser)
+            .isEqualTo(type == UserActionModel.ADD_SUPERVISED_USER)
+    }
+
+    private fun createUserInfos(
+        count: Int,
+        includeGuest: Boolean,
+    ): List<UserInfo> {
+        return (0 until count).map { index ->
+            val isGuest = includeGuest && index == count - 1
+            createUserInfo(
+                id = index,
+                name =
+                    if (isGuest) {
+                        "guest"
+                    } else {
+                        "user_$index"
+                    },
+                isPrimary = !isGuest && index == 0,
+                isGuest = isGuest,
+            )
+        }
+    }
+
+    private fun createUserInfo(
+        id: Int,
+        name: String,
+        isPrimary: Boolean = false,
+        isGuest: Boolean = false,
+    ): UserInfo {
+        return UserInfo(
+            id,
+            name,
+            /* iconPath= */ "",
+            /* flags= */ if (isPrimary) {
+                UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN
+            } else {
+                0
+            },
+            if (isGuest) {
+                UserManager.USER_TYPE_FULL_GUEST
+            } else {
+                UserManager.USER_TYPE_FULL_SYSTEM
+            },
+        )
+    }
+
     companion object {
         private val IMMEDIATE = Dispatchers.Main.immediate
+        private val ICON = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+        private val GUEST_ICON: Drawable = mock()
+        private const val SUPERVISED_USER_CREATION_APP_PACKAGE = "supervisedUserCreation"
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt
deleted file mode 100644
index 6a17c8d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.user.domain.interactor
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.nullable
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Mockito.anyBoolean
-import org.mockito.Mockito.verify
-
-@SmallTest
-@RunWith(JUnit4::class)
-open class UserInteractorUnrefactoredTest : UserInteractorTest() {
-
-    override fun isRefactored(): Boolean {
-        return false
-    }
-
-    @Before
-    override fun setUp() {
-        super.setUp()
-    }
-
-    @Test
-    fun `actions - not actionable when locked and locked - no actions`() =
-        runBlocking(IMMEDIATE) {
-            userRepository.setActions(UserActionModel.values().toList())
-            userRepository.setActionableWhenLocked(false)
-            keyguardRepository.setKeyguardShowing(true)
-
-            var actions: List<UserActionModel>? = null
-            val job = underTest.actions.onEach { actions = it }.launchIn(this)
-
-            assertThat(actions).isEmpty()
-            job.cancel()
-        }
-
-    @Test
-    fun `actions - not actionable when locked and not locked`() =
-        runBlocking(IMMEDIATE) {
-            setActions()
-            userRepository.setActionableWhenLocked(false)
-            keyguardRepository.setKeyguardShowing(false)
-
-            var actions: List<UserActionModel>? = null
-            val job = underTest.actions.onEach { actions = it }.launchIn(this)
-
-            assertThat(actions)
-                .isEqualTo(
-                    listOf(
-                        UserActionModel.ENTER_GUEST_MODE,
-                        UserActionModel.ADD_USER,
-                        UserActionModel.ADD_SUPERVISED_USER,
-                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
-                    )
-                )
-            job.cancel()
-        }
-
-    @Test
-    fun `actions - actionable when locked and not locked`() =
-        runBlocking(IMMEDIATE) {
-            setActions()
-            userRepository.setActionableWhenLocked(true)
-            keyguardRepository.setKeyguardShowing(false)
-
-            var actions: List<UserActionModel>? = null
-            val job = underTest.actions.onEach { actions = it }.launchIn(this)
-
-            assertThat(actions)
-                .isEqualTo(
-                    listOf(
-                        UserActionModel.ENTER_GUEST_MODE,
-                        UserActionModel.ADD_USER,
-                        UserActionModel.ADD_SUPERVISED_USER,
-                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
-                    )
-                )
-            job.cancel()
-        }
-
-    @Test
-    fun `actions - actionable when locked and locked`() =
-        runBlocking(IMMEDIATE) {
-            setActions()
-            userRepository.setActionableWhenLocked(true)
-            keyguardRepository.setKeyguardShowing(true)
-
-            var actions: List<UserActionModel>? = null
-            val job = underTest.actions.onEach { actions = it }.launchIn(this)
-
-            assertThat(actions)
-                .isEqualTo(
-                    listOf(
-                        UserActionModel.ENTER_GUEST_MODE,
-                        UserActionModel.ADD_USER,
-                        UserActionModel.ADD_SUPERVISED_USER,
-                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
-                    )
-                )
-            job.cancel()
-        }
-
-    @Test
-    fun selectUser() {
-        val userId = 3
-
-        underTest.selectUser(userId)
-
-        verify(controller).onUserSelected(eq(userId), nullable())
-    }
-
-    @Test
-    fun `executeAction - guest`() {
-        underTest.executeAction(UserActionModel.ENTER_GUEST_MODE)
-
-        verify(controller).createAndSwitchToGuestUser(nullable())
-    }
-
-    @Test
-    fun `executeAction - add user`() {
-        underTest.executeAction(UserActionModel.ADD_USER)
-
-        verify(controller).showAddUserDialog(nullable())
-    }
-
-    @Test
-    fun `executeAction - add supervised user`() {
-        underTest.executeAction(UserActionModel.ADD_SUPERVISED_USER)
-
-        verify(controller).startSupervisedUserActivity()
-    }
-
-    @Test
-    fun `executeAction - manage users`() {
-        underTest.executeAction(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
-
-        verify(activityStarter).startActivity(any(), anyBoolean())
-    }
-
-    private fun setActions() {
-        userRepository.setActions(UserActionModel.values().toList())
-    }
-
-    companion object {
-        private val IMMEDIATE = Dispatchers.Main.immediate
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
index 116023a..db13680 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
@@ -19,7 +19,7 @@
 
 import android.app.ActivityManager
 import android.app.admin.DevicePolicyManager
-import android.graphics.drawable.Drawable
+import android.content.pm.UserInfo
 import android.os.UserManager
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.UiEventLogger
@@ -27,32 +27,37 @@
 import com.android.systemui.GuestResumeSessionReceiver
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.Text
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.power.data.repository.FakePowerRepository
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
-import com.android.systemui.statusbar.policy.UserSwitcherController
 import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
 import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
+import com.android.systemui.user.data.model.UserSwitcherSettingsModel
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.user.domain.interactor.GuestUserInteractor
 import com.android.systemui.user.domain.interactor.RefreshUsersScheduler
 import com.android.systemui.user.domain.interactor.UserInteractor
 import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
 import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.user.shared.model.UserModel
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancelAndJoin
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
 import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.TestCoroutineScope
-import kotlinx.coroutines.yield
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.TestResult
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -60,11 +65,11 @@
 import org.mockito.Mock
 import org.mockito.MockitoAnnotations
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(JUnit4::class)
 class UserSwitcherViewModelTest : SysuiTestCase() {
 
-    @Mock private lateinit var controller: UserSwitcherController
     @Mock private lateinit var activityStarter: ActivityStarter
     @Mock private lateinit var activityManager: ActivityManager
     @Mock private lateinit var manager: UserManager
@@ -80,28 +85,47 @@
     private lateinit var keyguardRepository: FakeKeyguardRepository
     private lateinit var powerRepository: FakePowerRepository
 
+    private lateinit var testDispatcher: TestDispatcher
+    private lateinit var testScope: TestScope
+    private lateinit var injectedScope: CoroutineScope
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        whenever(manager.canAddMoreUsers(any())).thenReturn(true)
+        whenever(manager.getUserSwitchability(any()))
+            .thenReturn(UserManager.SWITCHABILITY_STATUS_OK)
+        overrideResource(
+            com.android.internal.R.string.config_supervisedUserCreationPackage,
+            SUPERVISED_USER_CREATION_PACKAGE,
+        )
 
+        testDispatcher = UnconfinedTestDispatcher()
+        testScope = TestScope(testDispatcher)
+        injectedScope = CoroutineScope(testScope.coroutineContext + SupervisorJob())
         userRepository = FakeUserRepository()
+        runBlocking {
+            userRepository.setSettings(
+                UserSwitcherSettingsModel(
+                    isUserSwitcherEnabled = true,
+                )
+            )
+        }
+
         keyguardRepository = FakeKeyguardRepository()
         powerRepository = FakePowerRepository()
-        val featureFlags = FakeFeatureFlags()
-        featureFlags.set(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER, true)
-        val scope = TestCoroutineScope()
         val refreshUsersScheduler =
             RefreshUsersScheduler(
-                applicationScope = scope,
-                mainDispatcher = IMMEDIATE,
+                applicationScope = injectedScope,
+                mainDispatcher = testDispatcher,
                 repository = userRepository,
             )
         val guestUserInteractor =
             GuestUserInteractor(
                 applicationContext = context,
-                applicationScope = scope,
-                mainDispatcher = IMMEDIATE,
-                backgroundDispatcher = IMMEDIATE,
+                applicationScope = injectedScope,
+                mainDispatcher = testDispatcher,
+                backgroundDispatcher = testDispatcher,
                 manager = manager,
                 repository = userRepository,
                 deviceProvisionedController = deviceProvisionedController,
@@ -118,21 +142,19 @@
                         UserInteractor(
                             applicationContext = context,
                             repository = userRepository,
-                            controller = controller,
                             activityStarter = activityStarter,
                             keyguardInteractor =
                                 KeyguardInteractor(
                                     repository = keyguardRepository,
                                 ),
-                            featureFlags = featureFlags,
                             manager = manager,
-                            applicationScope = scope,
+                            applicationScope = injectedScope,
                             telephonyInteractor =
                                 TelephonyInteractor(
                                     repository = FakeTelephonyRepository(),
                                 ),
                             broadcastDispatcher = fakeBroadcastDispatcher,
-                            backgroundDispatcher = IMMEDIATE,
+                            backgroundDispatcher = testDispatcher,
                             activityManager = activityManager,
                             refreshUsersScheduler = refreshUsersScheduler,
                             guestUserInteractor = guestUserInteractor,
@@ -141,222 +163,216 @@
                         PowerInteractor(
                             repository = powerRepository,
                         ),
-                    featureFlags = featureFlags,
                     guestUserInteractor = guestUserInteractor,
                 )
                 .create(UserSwitcherViewModel::class.java)
     }
 
     @Test
-    fun users() =
-        runBlocking(IMMEDIATE) {
-            userRepository.setUsers(
+    fun users() = selfCancelingTest {
+        val userInfos =
+            listOf(
+                UserInfo(
+                    /* id= */ 0,
+                    /* name= */ "zero",
+                    /* iconPath= */ "",
+                    /* flags= */ UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN,
+                    UserManager.USER_TYPE_FULL_SYSTEM,
+                ),
+                UserInfo(
+                    /* id= */ 1,
+                    /* name= */ "one",
+                    /* iconPath= */ "",
+                    /* flags= */ 0,
+                    UserManager.USER_TYPE_FULL_SYSTEM,
+                ),
+                UserInfo(
+                    /* id= */ 2,
+                    /* name= */ "two",
+                    /* iconPath= */ "",
+                    /* flags= */ 0,
+                    UserManager.USER_TYPE_FULL_SYSTEM,
+                ),
+            )
+        userRepository.setUserInfos(userInfos)
+        userRepository.setSelectedUserInfo(userInfos[0])
+
+        val userViewModels = mutableListOf<List<UserViewModel>>()
+        val job = launch(testDispatcher) { underTest.users.toList(userViewModels) }
+
+        assertThat(userViewModels.last()).hasSize(3)
+        assertUserViewModel(
+            viewModel = userViewModels.last()[0],
+            viewKey = 0,
+            name = "zero",
+            isSelectionMarkerVisible = true,
+        )
+        assertUserViewModel(
+            viewModel = userViewModels.last()[1],
+            viewKey = 1,
+            name = "one",
+            isSelectionMarkerVisible = false,
+        )
+        assertUserViewModel(
+            viewModel = userViewModels.last()[2],
+            viewKey = 2,
+            name = "two",
+            isSelectionMarkerVisible = false,
+        )
+        job.cancel()
+    }
+
+    @Test
+    fun `maximumUserColumns - few users`() = selfCancelingTest {
+        setUsers(count = 2)
+        val values = mutableListOf<Int>()
+        val job = launch(testDispatcher) { underTest.maximumUserColumns.toList(values) }
+
+        assertThat(values.last()).isEqualTo(4)
+
+        job.cancel()
+    }
+
+    @Test
+    fun `maximumUserColumns - many users`() = selfCancelingTest {
+        setUsers(count = 5)
+        val values = mutableListOf<Int>()
+        val job = launch(testDispatcher) { underTest.maximumUserColumns.toList(values) }
+
+        assertThat(values.last()).isEqualTo(3)
+        job.cancel()
+    }
+
+    @Test
+    fun `isOpenMenuButtonVisible - has actions - true`() = selfCancelingTest {
+        setUsers(2)
+
+        val isVisible = mutableListOf<Boolean>()
+        val job = launch(testDispatcher) { underTest.isOpenMenuButtonVisible.toList(isVisible) }
+
+        assertThat(isVisible.last()).isTrue()
+        job.cancel()
+    }
+
+    @Test
+    fun `isOpenMenuButtonVisible - no actions - false`() = selfCancelingTest {
+        val userInfos = setUsers(2)
+        userRepository.setSelectedUserInfo(userInfos[1])
+        keyguardRepository.setKeyguardShowing(true)
+        whenever(manager.canAddMoreUsers(any())).thenReturn(false)
+
+        val isVisible = mutableListOf<Boolean>()
+        val job = launch(testDispatcher) { underTest.isOpenMenuButtonVisible.toList(isVisible) }
+
+        assertThat(isVisible.last()).isFalse()
+        job.cancel()
+    }
+
+    @Test
+    fun menu() = selfCancelingTest {
+        val isMenuVisible = mutableListOf<Boolean>()
+        val job = launch(testDispatcher) { underTest.isMenuVisible.toList(isMenuVisible) }
+        assertThat(isMenuVisible.last()).isFalse()
+
+        underTest.onOpenMenuButtonClicked()
+        assertThat(isMenuVisible.last()).isTrue()
+
+        underTest.onMenuClosed()
+        assertThat(isMenuVisible.last()).isFalse()
+
+        job.cancel()
+    }
+
+    @Test
+    fun `menu actions`() = selfCancelingTest {
+        setUsers(2)
+        val actions = mutableListOf<List<UserActionViewModel>>()
+        val job = launch(testDispatcher) { underTest.menu.toList(actions) }
+
+        assertThat(actions.last().map { it.viewKey })
+            .isEqualTo(
                 listOf(
-                    UserModel(
-                        id = 0,
-                        name = Text.Loaded("zero"),
-                        image = USER_IMAGE,
-                        isSelected = true,
-                        isSelectable = true,
-                        isGuest = false,
-                    ),
-                    UserModel(
-                        id = 1,
-                        name = Text.Loaded("one"),
-                        image = USER_IMAGE,
-                        isSelected = false,
-                        isSelectable = true,
-                        isGuest = false,
-                    ),
-                    UserModel(
-                        id = 2,
-                        name = Text.Loaded("two"),
-                        image = USER_IMAGE,
-                        isSelected = false,
-                        isSelectable = false,
-                        isGuest = false,
-                    ),
+                    UserActionModel.ENTER_GUEST_MODE.ordinal.toLong(),
+                    UserActionModel.ADD_USER.ordinal.toLong(),
+                    UserActionModel.ADD_SUPERVISED_USER.ordinal.toLong(),
+                    UserActionModel.NAVIGATE_TO_USER_MANAGEMENT.ordinal.toLong(),
                 )
             )
 
-            var userViewModels: List<UserViewModel>? = null
-            val job = underTest.users.onEach { userViewModels = it }.launchIn(this)
-
-            assertThat(userViewModels).hasSize(3)
-            assertUserViewModel(
-                viewModel = userViewModels?.get(0),
-                viewKey = 0,
-                name = "zero",
-                isSelectionMarkerVisible = true,
-                alpha = LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_SELECTABLE_ALPHA,
-                isClickable = true,
-            )
-            assertUserViewModel(
-                viewModel = userViewModels?.get(1),
-                viewKey = 1,
-                name = "one",
-                isSelectionMarkerVisible = false,
-                alpha = LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_SELECTABLE_ALPHA,
-                isClickable = true,
-            )
-            assertUserViewModel(
-                viewModel = userViewModels?.get(2),
-                viewKey = 2,
-                name = "two",
-                isSelectionMarkerVisible = false,
-                alpha = LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_NOT_SELECTABLE_ALPHA,
-                isClickable = false,
-            )
-            job.cancel()
-        }
+        job.cancel()
+    }
 
     @Test
-    fun `maximumUserColumns - few users`() =
-        runBlocking(IMMEDIATE) {
-            setUsers(count = 2)
-            var value: Int? = null
-            val job = underTest.maximumUserColumns.onEach { value = it }.launchIn(this)
+    fun `isFinishRequested - finishes when user is switched`() = selfCancelingTest {
+        val userInfos = setUsers(count = 2)
+        val isFinishRequested = mutableListOf<Boolean>()
+        val job = launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) }
+        assertThat(isFinishRequested.last()).isFalse()
 
-            assertThat(value).isEqualTo(4)
-            job.cancel()
-        }
+        userRepository.setSelectedUserInfo(userInfos[1])
+
+        assertThat(isFinishRequested.last()).isTrue()
+
+        job.cancel()
+    }
 
     @Test
-    fun `maximumUserColumns - many users`() =
-        runBlocking(IMMEDIATE) {
-            setUsers(count = 5)
-            var value: Int? = null
-            val job = underTest.maximumUserColumns.onEach { value = it }.launchIn(this)
+    fun `isFinishRequested - finishes when the screen turns off`() = selfCancelingTest {
+        setUsers(count = 2)
+        powerRepository.setInteractive(true)
+        val isFinishRequested = mutableListOf<Boolean>()
+        val job = launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) }
+        assertThat(isFinishRequested.last()).isFalse()
 
-            assertThat(value).isEqualTo(3)
-            job.cancel()
-        }
+        powerRepository.setInteractive(false)
+
+        assertThat(isFinishRequested.last()).isTrue()
+
+        job.cancel()
+    }
 
     @Test
-    fun `isOpenMenuButtonVisible - has actions - true`() =
-        runBlocking(IMMEDIATE) {
-            userRepository.setActions(UserActionModel.values().toList())
+    fun `isFinishRequested - finishes when cancel button is clicked`() = selfCancelingTest {
+        setUsers(count = 2)
+        powerRepository.setInteractive(true)
+        val isFinishRequested = mutableListOf<Boolean>()
+        val job = launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) }
+        assertThat(isFinishRequested.last()).isFalse()
 
-            var isVisible: Boolean? = null
-            val job = underTest.isOpenMenuButtonVisible.onEach { isVisible = it }.launchIn(this)
+        underTest.onCancelButtonClicked()
 
-            assertThat(isVisible).isTrue()
-            job.cancel()
-        }
+        assertThat(isFinishRequested.last()).isTrue()
 
-    @Test
-    fun `isOpenMenuButtonVisible - no actions - false`() =
-        runBlocking(IMMEDIATE) {
-            userRepository.setActions(emptyList())
+        underTest.onFinished()
 
-            var isVisible: Boolean? = null
-            val job = underTest.isOpenMenuButtonVisible.onEach { isVisible = it }.launchIn(this)
+        assertThat(isFinishRequested.last()).isFalse()
 
-            assertThat(isVisible).isFalse()
-            job.cancel()
-        }
+        job.cancel()
+    }
 
-    @Test
-    fun menu() =
-        runBlocking(IMMEDIATE) {
-            userRepository.setActions(UserActionModel.values().toList())
-            var isMenuVisible: Boolean? = null
-            val job = underTest.isMenuVisible.onEach { isMenuVisible = it }.launchIn(this)
-            assertThat(isMenuVisible).isFalse()
-
-            underTest.onOpenMenuButtonClicked()
-            assertThat(isMenuVisible).isTrue()
-
-            underTest.onMenuClosed()
-            assertThat(isMenuVisible).isFalse()
-
-            job.cancel()
-        }
-
-    @Test
-    fun `menu actions`() =
-        runBlocking(IMMEDIATE) {
-            userRepository.setActions(UserActionModel.values().toList())
-            var actions: List<UserActionViewModel>? = null
-            val job = underTest.menu.onEach { actions = it }.launchIn(this)
-
-            assertThat(actions?.map { it.viewKey })
-                .isEqualTo(
-                    listOf(
-                        UserActionModel.ENTER_GUEST_MODE.ordinal.toLong(),
-                        UserActionModel.ADD_USER.ordinal.toLong(),
-                        UserActionModel.ADD_SUPERVISED_USER.ordinal.toLong(),
-                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT.ordinal.toLong(),
-                    )
-                )
-
-            job.cancel()
-        }
-
-    @Test
-    fun `isFinishRequested - finishes when user is switched`() =
-        runBlocking(IMMEDIATE) {
-            setUsers(count = 2)
-            var isFinishRequested: Boolean? = null
-            val job = underTest.isFinishRequested.onEach { isFinishRequested = it }.launchIn(this)
-            assertThat(isFinishRequested).isFalse()
-
-            userRepository.setSelectedUser(1)
-            yield()
-            assertThat(isFinishRequested).isTrue()
-
-            job.cancel()
-        }
-
-    @Test
-    fun `isFinishRequested - finishes when the screen turns off`() =
-        runBlocking(IMMEDIATE) {
-            setUsers(count = 2)
-            powerRepository.setInteractive(true)
-            var isFinishRequested: Boolean? = null
-            val job = underTest.isFinishRequested.onEach { isFinishRequested = it }.launchIn(this)
-            assertThat(isFinishRequested).isFalse()
-
-            powerRepository.setInteractive(false)
-            yield()
-            assertThat(isFinishRequested).isTrue()
-
-            job.cancel()
-        }
-
-    @Test
-    fun `isFinishRequested - finishes when cancel button is clicked`() =
-        runBlocking(IMMEDIATE) {
-            setUsers(count = 2)
-            powerRepository.setInteractive(true)
-            var isFinishRequested: Boolean? = null
-            val job = underTest.isFinishRequested.onEach { isFinishRequested = it }.launchIn(this)
-            assertThat(isFinishRequested).isFalse()
-
-            underTest.onCancelButtonClicked()
-            yield()
-            assertThat(isFinishRequested).isTrue()
-
-            underTest.onFinished()
-            yield()
-            assertThat(isFinishRequested).isFalse()
-
-            job.cancel()
-        }
-
-    private suspend fun setUsers(count: Int) {
-        userRepository.setUsers(
+    private suspend fun setUsers(count: Int): List<UserInfo> {
+        val userInfos =
             (0 until count).map { index ->
-                UserModel(
-                    id = index,
-                    name = Text.Loaded("$index"),
-                    image = USER_IMAGE,
-                    isSelected = index == 0,
-                    isSelectable = true,
-                    isGuest = false,
+                UserInfo(
+                    /* id= */ index,
+                    /* name= */ "$index",
+                    /* iconPath= */ "",
+                    /* flags= */ if (index == 0) {
+                        // This is the primary user.
+                        UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN
+                    } else {
+                        // This isn't the primary user.
+                        0
+                    },
+                    UserManager.USER_TYPE_FULL_SYSTEM,
                 )
             }
-        )
+        userRepository.setUserInfos(userInfos)
+
+        if (userInfos.isNotEmpty()) {
+            userRepository.setSelectedUserInfo(userInfos[0])
+        }
+        return userInfos
     }
 
     private fun assertUserViewModel(
@@ -364,19 +380,25 @@
         viewKey: Int,
         name: String,
         isSelectionMarkerVisible: Boolean,
-        alpha: Float,
-        isClickable: Boolean,
     ) {
         checkNotNull(viewModel)
         assertThat(viewModel.viewKey).isEqualTo(viewKey)
         assertThat(viewModel.name).isEqualTo(Text.Loaded(name))
         assertThat(viewModel.isSelectionMarkerVisible).isEqualTo(isSelectionMarkerVisible)
-        assertThat(viewModel.alpha).isEqualTo(alpha)
-        assertThat(viewModel.onClicked != null).isEqualTo(isClickable)
+        assertThat(viewModel.alpha)
+            .isEqualTo(LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_SELECTABLE_ALPHA)
+        assertThat(viewModel.onClicked).isNotNull()
     }
 
+    private fun selfCancelingTest(
+        block: suspend TestScope.() -> Unit,
+    ): TestResult =
+        testScope.runTest {
+            block()
+            injectedScope.coroutineContext[Job.Key]?.cancelAndJoin()
+        }
+
     companion object {
-        private val IMMEDIATE = Dispatchers.Main.immediate
-        private val USER_IMAGE = mock<Drawable>()
+        private const val SUPERVISED_USER_CREATION_PACKAGE = "com.some.package"
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index d42f757..db3b9b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -1331,7 +1331,7 @@
         spyOn(mContext);
         mBubbleController.updateBubble(mBubbleEntry);
         verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
-                mFilterArgumentCaptor.capture());
+                mFilterArgumentCaptor.capture(), eq(Context.RECEIVER_EXPORTED));
         assertThat(mFilterArgumentCaptor.getValue().getAction(0)).isEqualTo(
                 Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
         assertThat(mFilterArgumentCaptor.getValue().getAction(1)).isEqualTo(
@@ -1351,7 +1351,7 @@
         mBubbleController.updateBubble(mBubbleEntry);
         mBubbleData.setExpanded(true);
         verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
-                mFilterArgumentCaptor.capture());
+                mFilterArgumentCaptor.capture(), eq(Context.RECEIVER_EXPORTED));
         Intent i = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
         mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i);
 
@@ -1365,7 +1365,7 @@
         mBubbleData.setExpanded(true);
 
         verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
-                mFilterArgumentCaptor.capture());
+                mFilterArgumentCaptor.capture(), eq(Context.RECEIVER_EXPORTED));
         Intent i = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
         i.putExtra("reason", "gestureNav");
         mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i);
@@ -1379,7 +1379,7 @@
         mBubbleData.setExpanded(true);
 
         verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
-                mFilterArgumentCaptor.capture());
+                mFilterArgumentCaptor.capture(), eq(Context.RECEIVER_EXPORTED));
 
         Intent i = new Intent(Intent.ACTION_SCREEN_OFF);
         mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
index 4df8aa4..b7c8cbf 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
@@ -20,26 +20,15 @@
 import android.content.pm.UserInfo
 import android.os.UserHandle
 import com.android.systemui.user.data.model.UserSwitcherSettingsModel
-import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.user.shared.model.UserModel
 import java.util.concurrent.atomic.AtomicBoolean
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.filterNotNull
-import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.yield
 
 class FakeUserRepository : UserRepository {
 
-    private val _users = MutableStateFlow<List<UserModel>>(emptyList())
-    override val users: Flow<List<UserModel>> = _users.asStateFlow()
-    override val selectedUser: Flow<UserModel> =
-        users.map { models -> models.first { model -> model.isSelected } }
-
-    private val _actions = MutableStateFlow<List<UserActionModel>>(emptyList())
-    override val actions: Flow<List<UserActionModel>> = _actions.asStateFlow()
-
     private val _userSwitcherSettings = MutableStateFlow(UserSwitcherSettingsModel())
     override val userSwitcherSettings: Flow<UserSwitcherSettingsModel> =
         _userSwitcherSettings.asStateFlow()
@@ -52,9 +41,6 @@
 
     override var lastSelectedNonGuestUserId: Int = UserHandle.USER_SYSTEM
 
-    private val _isActionableWhenLocked = MutableStateFlow(false)
-    override val isActionableWhenLocked: Flow<Boolean> = _isActionableWhenLocked.asStateFlow()
-
     private var _isGuestUserAutoCreated: Boolean = false
     override val isGuestUserAutoCreated: Boolean
         get() = _isGuestUserAutoCreated
@@ -100,35 +86,6 @@
         yield()
     }
 
-    fun setUsers(models: List<UserModel>) {
-        _users.value = models
-    }
-
-    suspend fun setSelectedUser(userId: Int) {
-        check(_users.value.find { it.id == userId } != null) {
-            "Cannot select a user with ID $userId - no user with that ID found!"
-        }
-
-        setUsers(
-            _users.value.map { model ->
-                when {
-                    model.isSelected && model.id != userId -> model.copy(isSelected = false)
-                    !model.isSelected && model.id == userId -> model.copy(isSelected = true)
-                    else -> model
-                }
-            }
-        )
-        yield()
-    }
-
-    fun setActions(models: List<UserActionModel>) {
-        _actions.value = models
-    }
-
-    fun setActionableWhenLocked(value: Boolean) {
-        _isActionableWhenLocked.value = value
-    }
-
     fun setGuestUserAutoCreated(value: Boolean) {
         _isGuestUserAutoCreated = value
     }
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index f35de17..c77b597 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -16,6 +16,7 @@
 
 package com.android.server.accessibility;
 
+import static android.accessibilityservice.AccessibilityService.ACCESSIBILITY_TAKE_SCREENSHOT_REQUEST_INTERVAL_TIMES_MS;
 import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE;
 import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER;
 import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_STATUS;
@@ -83,6 +84,7 @@
 import android.view.accessibility.AccessibilityWindowInfo;
 import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
 import android.view.inputmethod.EditorInfo;
+import android.window.ScreenCapture;
 import android.window.ScreenCapture.ScreenshotHardwareBuffer;
 
 import com.android.internal.annotations.GuardedBy;
@@ -211,6 +213,11 @@
 
     /** The timestamp of requesting to take screenshot in milliseconds */
     private long mRequestTakeScreenshotTimestampMs;
+    /**
+     * The timestamps of requesting to take a window screenshot in milliseconds,
+     * mapping from accessibility window id -> timestamp.
+     */
+    private SparseArray<Long> mRequestTakeScreenshotOfWindowTimestampMs = new SparseArray<>();
 
     public interface SystemSupport {
         /**
@@ -1252,6 +1259,51 @@
     }
 
     @Override
+    public void takeScreenshotOfWindow(int accessibilityWindowId, int interactionId,
+            ScreenCapture.ScreenCaptureListener listener,
+            IAccessibilityInteractionConnectionCallback callback) throws RemoteException {
+        final long currentTimestamp = SystemClock.uptimeMillis();
+        if ((currentTimestamp
+                - mRequestTakeScreenshotOfWindowTimestampMs.get(accessibilityWindowId, 0L))
+                <= ACCESSIBILITY_TAKE_SCREENSHOT_REQUEST_INTERVAL_TIMES_MS) {
+            callback.sendTakeScreenshotOfWindowError(
+                    AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERVAL_TIME_SHORT, interactionId);
+            return;
+        }
+        mRequestTakeScreenshotOfWindowTimestampMs.put(accessibilityWindowId, currentTimestamp);
+
+        synchronized (mLock) {
+            if (!hasRightsToCurrentUserLocked()) {
+                callback.sendTakeScreenshotOfWindowError(
+                        AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR, interactionId);
+                return;
+            }
+            if (!mSecurityPolicy.canTakeScreenshotLocked(this)) {
+                callback.sendTakeScreenshotOfWindowError(
+                        AccessibilityService.ERROR_TAKE_SCREENSHOT_NO_ACCESSIBILITY_ACCESS,
+                        interactionId);
+                return;
+            }
+        }
+        if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
+            callback.sendTakeScreenshotOfWindowError(
+                    AccessibilityService.ERROR_TAKE_SCREENSHOT_NO_ACCESSIBILITY_ACCESS,
+                    interactionId);
+            return;
+        }
+
+        RemoteAccessibilityConnection connection = mA11yWindowManager.getConnectionLocked(
+                mSystemSupport.getCurrentUserIdLocked(),
+                resolveAccessibilityWindowIdLocked(accessibilityWindowId));
+        if (connection == null) {
+            callback.sendTakeScreenshotOfWindowError(
+                    AccessibilityService.ERROR_TAKE_SCREENSHOT_INVALID_WINDOW, interactionId);
+            return;
+        }
+        connection.getRemote().takeScreenshotOfWindow(interactionId, listener, callback);
+    }
+
+    @Override
     public void takeScreenshot(int displayId, RemoteCallback callback) {
         if (svcConnTracingEnabled()) {
             logTraceSvcConn("takeScreenshot", "displayId=" + displayId + ";callback=" + callback);
diff --git a/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java b/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java
index 6958b66..c08b6ab 100644
--- a/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java
+++ b/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java
@@ -167,6 +167,12 @@
         mServiceCallback.setPerformAccessibilityActionResult(succeeded, interactionId);
     }
 
+    @Override
+    public void sendTakeScreenshotOfWindowError(int errorCode, int interactionId)
+            throws RemoteException {
+        mServiceCallback.sendTakeScreenshotOfWindowError(errorCode, interactionId);
+    }
+
     private void replaceInfoActionsAndCallService() {
         final AccessibilityNodeInfo nodeToReturn;
         boolean doCallback = false;
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 8055afc..2662e03 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -685,7 +685,9 @@
                         FrameworkStatsLog.write(FrameworkStatsLog.APEX_INFO_GATHERED,
                                 packageInfo.packageName,
                                 packageInfo.getLongVersionCode(),
-                                mBinaryHashes.get(packageInfo.packageName));
+                                mBinaryHashes.get(packageInfo.packageName),
+                                4,  // indicating that the digest is SHA256
+                                null);  // TODO: This is to comform to the extended schema.
                     }
                 }
             } catch (PackageManager.NameNotFoundException e) {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index d0f245f..7fb3765 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -724,7 +724,7 @@
 
         ServiceRecord r = res.record;
         setFgsRestrictionLocked(callingPackage, callingPid, callingUid, service, r, userId,
-                allowBackgroundActivityStarts);
+                allowBackgroundActivityStarts, false /* isBindService */);
 
         if (!mAm.mUserController.exists(r.userId)) {
             Slog.w(TAG, "Trying to start service with non-existent user! " + r.userId);
@@ -1931,7 +1931,9 @@
                             if (delayMs > mAm.mConstants.mFgsStartForegroundTimeoutMs) {
                                 resetFgsRestrictionLocked(r);
                                 setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(),
-                                        r.appInfo.uid, r.intent.getIntent(), r, r.userId, false);
+                                        r.appInfo.uid, r.intent.getIntent(), r, r.userId,
+                                        false /* allowBackgroundActivityStarts */,
+                                        false /* isBindService */);
                                 final String temp = "startForegroundDelayMs:" + delayMs;
                                 if (r.mInfoAllowStartForeground != null) {
                                     r.mInfoAllowStartForeground += "; " + temp;
@@ -1945,7 +1947,9 @@
                         // The second or later time startForeground() is called after service is
                         // started. Check for app state again.
                         setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(),
-                                r.appInfo.uid, r.intent.getIntent(), r, r.userId, false);
+                                r.appInfo.uid, r.intent.getIntent(), r, r.userId,
+                                false /* allowBackgroundActivityStarts */,
+                                false /* isBindService */);
                     }
                     // If the foreground service is not started from TOP process, do not allow it to
                     // have while-in-use location/camera/microphone access.
@@ -2985,7 +2989,7 @@
                 }
             }
             setFgsRestrictionLocked(callingPackage, callingPid, callingUid, service, s, userId,
-                    false);
+                    false /* allowBackgroundActivityStarts */, true /* isBindService */);
 
             if (s.app != null) {
                 ProcessServiceRecord servicePsr = s.app.mServices;
@@ -6500,7 +6504,7 @@
      */
     private void setFgsRestrictionLocked(String callingPackage,
             int callingPid, int callingUid, Intent intent, ServiceRecord r, int userId,
-            boolean allowBackgroundActivityStarts) {
+            boolean allowBackgroundActivityStarts, boolean isBindService) {
         r.mLastSetFgsRestrictionTime = SystemClock.elapsedRealtime();
         // Check DeviceConfig flag.
         if (!mAm.mConstants.mFlagBackgroundFgsStartRestrictionEnabled) {
@@ -6510,14 +6514,15 @@
         if (!r.mAllowWhileInUsePermissionInFgs
                 || (r.mAllowStartForeground == REASON_DENIED)) {
             final @ReasonCode int allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked(
-                    callingPackage, callingPid, callingUid, r, allowBackgroundActivityStarts);
+                    callingPackage, callingPid, callingUid, r, allowBackgroundActivityStarts,
+                    isBindService);
             if (!r.mAllowWhileInUsePermissionInFgs) {
                 r.mAllowWhileInUsePermissionInFgs = (allowWhileInUse != REASON_DENIED);
             }
             if (r.mAllowStartForeground == REASON_DENIED) {
                 r.mAllowStartForeground = shouldAllowFgsStartForegroundWithBindingCheckLocked(
                         allowWhileInUse, callingPackage, callingPid, callingUid, intent, r,
-                        userId);
+                        userId, isBindService);
             }
         }
     }
@@ -6537,9 +6542,10 @@
         }
         final @ReasonCode int allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked(
                 callingPackage, callingPid, callingUid, null /* serviceRecord */,
-                false /* allowBackgroundActivityStarts */);
+                false /* allowBackgroundActivityStarts */, false);
         @ReasonCode int allowStartFgs = shouldAllowFgsStartForegroundNoBindingCheckLocked(
-                allowWhileInUse, callingPid, callingUid, callingPackage, null /* targetService */);
+                allowWhileInUse, callingPid, callingUid, callingPackage, null /* targetService */,
+                false /* isBindService */);
 
         if (allowStartFgs == REASON_DENIED) {
             if (canBindingClientStartFgsLocked(callingUid) != null) {
@@ -6559,7 +6565,7 @@
      */
     private @ReasonCode int shouldAllowFgsWhileInUsePermissionLocked(String callingPackage,
             int callingPid, int callingUid, @Nullable ServiceRecord targetService,
-            boolean allowBackgroundActivityStarts) {
+            boolean allowBackgroundActivityStarts, boolean isBindService) {
         int ret = REASON_DENIED;
 
         final int uidState = mAm.getUidStateLocked(callingUid);
@@ -6713,12 +6719,12 @@
                                         shouldAllowFgsWhileInUsePermissionLocked(
                                                 clientPackageName,
                                                 clientPid, clientUid, null /* serviceRecord */,
-                                                false /* allowBackgroundActivityStarts */);
+                                                false /* allowBackgroundActivityStarts */, false);
                                 final @ReasonCode int allowStartFgs =
                                         shouldAllowFgsStartForegroundNoBindingCheckLocked(
                                                 allowWhileInUse2,
                                                 clientPid, clientUid, clientPackageName,
-                                                null /* targetService */);
+                                                null /* targetService */, false);
                                 if (allowStartFgs != REASON_DENIED) {
                                     return new Pair<>(allowStartFgs, clientPackageName);
                                 } else {
@@ -6750,11 +6756,11 @@
      */
     private @ReasonCode int shouldAllowFgsStartForegroundWithBindingCheckLocked(
             @ReasonCode int allowWhileInUse, String callingPackage, int callingPid,
-            int callingUid, Intent intent, ServiceRecord r, int userId) {
+            int callingUid, Intent intent, ServiceRecord r, int userId, boolean isBindService) {
         ActivityManagerService.FgsTempAllowListItem tempAllowListReason =
                 r.mInfoTempFgsAllowListReason = mAm.isAllowlistedForFgsStartLOSP(callingUid);
         int ret = shouldAllowFgsStartForegroundNoBindingCheckLocked(allowWhileInUse, callingPid,
-                callingUid, callingPackage, r);
+                callingUid, callingPackage, r, isBindService);
 
         String bindFromPackage = null;
         if (ret == REASON_DENIED) {
@@ -6789,6 +6795,7 @@
                         + "; callerTargetSdkVersion:" + callerTargetSdkVersion
                         + "; startForegroundCount:" + r.mStartForegroundCount
                         + "; bindFromPackage:" + bindFromPackage
+                        + ": isBindService:" + isBindService
                         + "]";
         if (!debugInfo.equals(r.mInfoAllowStartForeground)) {
             r.mLoggedInfoAllowStartForeground = false;
@@ -6799,7 +6806,7 @@
 
     private @ReasonCode int shouldAllowFgsStartForegroundNoBindingCheckLocked(
             @ReasonCode int allowWhileInUse, int callingPid, int callingUid, String callingPackage,
-            @Nullable ServiceRecord targetService) {
+            @Nullable ServiceRecord targetService, boolean isBindService) {
         int ret = allowWhileInUse;
 
         if (ret == REASON_DENIED) {
@@ -6981,10 +6988,12 @@
     }
 
     private void logFgsBackgroundStart(ServiceRecord r) {
+        /*
         // Only log if FGS is started from background.
         if (!isFgsBgStart(r.mAllowStartForeground)) {
             return;
         }
+        */
         if (!r.mLoggedInfoAllowStartForeground) {
             final String msg = "Background started FGS: "
                     + ((r.mAllowStartForeground != REASON_DENIED) ? "Allowed " : "Disallowed ")
@@ -6996,10 +7005,10 @@
                 }
                 Slog.i(TAG, msg);
             } else {
-                if (ActivityManagerUtils.shouldSamplePackageForAtom(r.packageName,
-                        mAm.mConstants.mFgsStartDeniedLogSampleRate)) {
+                //if (ActivityManagerUtils.shouldSamplePackageForAtom(r.packageName,
+                //        mAm.mConstants.mFgsStartDeniedLogSampleRate)) {
                     Slog.wtfQuiet(TAG, msg);
-                }
+                //}
                 Slog.w(TAG, msg);
             }
             r.mLoggedInfoAllowStartForeground = true;
@@ -7082,7 +7091,7 @@
             String callingPackage) {
         return shouldAllowFgsWhileInUsePermissionLocked(callingPackage, callingPid, callingUid,
                 /* targetService */ null,
-                /* allowBackgroundActivityStarts */ false)
+                /* allowBackgroundActivityStarts */ false, false)
                 != REASON_DENIED;
     }
 
diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java
index 56909e3..dfac82c 100644
--- a/services/core/java/com/android/server/am/BroadcastConstants.java
+++ b/services/core/java/com/android/server/am/BroadcastConstants.java
@@ -170,7 +170,7 @@
      */
     public long DELAY_NORMAL_MILLIS = DEFAULT_DELAY_NORMAL_MILLIS;
     private static final String KEY_DELAY_NORMAL_MILLIS = "bcast_delay_normal_millis";
-    private static final long DEFAULT_DELAY_NORMAL_MILLIS = 0;
+    private static final long DEFAULT_DELAY_NORMAL_MILLIS = +500;
 
     /**
      * For {@link BroadcastQueueModernImpl}: Delay to apply to broadcasts
@@ -178,7 +178,7 @@
      */
     public long DELAY_CACHED_MILLIS = DEFAULT_DELAY_CACHED_MILLIS;
     private static final String KEY_DELAY_CACHED_MILLIS = "bcast_delay_cached_millis";
-    private static final long DEFAULT_DELAY_CACHED_MILLIS = +30_000;
+    private static final long DEFAULT_DELAY_CACHED_MILLIS = +120_000;
 
     /**
      * For {@link BroadcastQueueModernImpl}: Delay to apply to urgent
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 47ca427..739d277 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -164,6 +164,7 @@
 
     private boolean mProcessCached;
     private boolean mProcessInstrumented;
+    private boolean mProcessPersistent;
 
     private String mCachedToString;
     private String mCachedToShortString;
@@ -323,8 +324,10 @@
         this.app = app;
         if (app != null) {
             setProcessInstrumented(app.getActiveInstrumentation() != null);
+            setProcessPersistent(app.isPersistent());
         } else {
             setProcessInstrumented(false);
+            setProcessPersistent(false);
         }
     }
 
@@ -352,6 +355,17 @@
     }
 
     /**
+     * Update if this process is in the "persistent" state, which signals broadcast dispatch should
+     * bypass all pauses or delays to prevent the system from becoming out of sync with itself.
+     */
+    public void setProcessPersistent(boolean persistent) {
+        if (mProcessPersistent != persistent) {
+            mProcessPersistent = persistent;
+            invalidateRunnableAt();
+        }
+    }
+
+    /**
      * Return if we know of an actively running "warm" process for this queue.
      */
     public boolean isProcessWarm() {
@@ -636,6 +650,7 @@
     static final int REASON_MAX_PENDING = 3;
     static final int REASON_BLOCKED = 4;
     static final int REASON_INSTRUMENTED = 5;
+    static final int REASON_PERSISTENT = 6;
     static final int REASON_CONTAINS_FOREGROUND = 10;
     static final int REASON_CONTAINS_ORDERED = 11;
     static final int REASON_CONTAINS_ALARM = 12;
@@ -652,6 +667,7 @@
             REASON_MAX_PENDING,
             REASON_BLOCKED,
             REASON_INSTRUMENTED,
+            REASON_PERSISTENT,
             REASON_CONTAINS_FOREGROUND,
             REASON_CONTAINS_ORDERED,
             REASON_CONTAINS_ALARM,
@@ -672,6 +688,7 @@
             case REASON_MAX_PENDING: return "MAX_PENDING";
             case REASON_BLOCKED: return "BLOCKED";
             case REASON_INSTRUMENTED: return "INSTRUMENTED";
+            case REASON_PERSISTENT: return "PERSISTENT";
             case REASON_CONTAINS_FOREGROUND: return "CONTAINS_FOREGROUND";
             case REASON_CONTAINS_ORDERED: return "CONTAINS_ORDERED";
             case REASON_CONTAINS_ALARM: return "CONTAINS_ALARM";
@@ -731,6 +748,9 @@
             } else if (mCountManifest > 0) {
                 mRunnableAt = runnableAt;
                 mRunnableAtReason = REASON_CONTAINS_MANIFEST;
+            } else if (mProcessPersistent) {
+                mRunnableAt = runnableAt;
+                mRunnableAtReason = REASON_PERSISTENT;
             } else if (mProcessCached) {
                 mRunnableAt = runnableAt + constants.DELAY_CACHED_MILLIS;
                 mRunnableAtReason = REASON_CACHED;
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 6793876..ce78d1b 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -1453,7 +1453,7 @@
         }
 
         BroadcastProcessQueue created = new BroadcastProcessQueue(mConstants, processName, uid);
-        created.app = mService.getProcessRecordLocked(processName, uid);
+        created.setProcess(mService.getProcessRecordLocked(processName, uid));
 
         if (leaf == null) {
             mProcessQueues.put(uid, created);
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index d2ef479..2ad2077 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -30,6 +30,7 @@
 import android.app.ActivityManager;
 import android.content.ComponentName;
 import android.os.SystemClock;
+import android.os.Trace;
 import android.util.Slog;
 import android.util.TimeUtils;
 
@@ -43,6 +44,9 @@
  * The state info of the process, including proc state, oom adj score, et al.
  */
 final class ProcessStateRecord {
+    // Enable this to trace all OomAdjuster state transitions
+    private static final boolean TRACE_OOM_ADJ = false;
+
     private final ProcessRecord mApp;
     private final ActivityManagerService mService;
     private final ActivityManagerGlobalLock mProcLock;
@@ -916,6 +920,12 @@
 
     @GuardedBy("mService")
     void setAdjType(String adjType) {
+        if (TRACE_OOM_ADJ) {
+            Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                    "oom:" + mApp.processName + "/u" + mApp.uid, 0);
+            Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                    "oom:" + mApp.processName + "/u" + mApp.uid, adjType, 0);
+        }
         mAdjType = adjType;
     }
 
@@ -1153,6 +1163,10 @@
 
     @GuardedBy({"mService", "mProcLock"})
     void onCleanupApplicationRecordLSP() {
+        if (TRACE_OOM_ADJ) {
+            Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                    "oom:" + mApp.processName + "/u" + mApp.uid, 0);
+        }
         setHasForegroundActivities(false);
         mHasShownUi = false;
         mForcingToImportant = null;
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 9213327..4d86140 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -1090,7 +1090,7 @@
             // TODO(b/239982558): for now we're just updating the user's visibility, but most likely
             // we'll need to remove this call and handle that as part of the user state workflow
             // instead.
-            userManagerInternal.unassignUserFromDisplay(userId);
+            userManagerInternal.unassignUserFromDisplayOnStop(userId);
 
             final boolean visibilityChanged;
             boolean visibleBefore;
@@ -1650,13 +1650,30 @@
                 return false;
             }
 
-            if (!userInfo.preCreated) {
-                // TODO(b/244644281): UMI should return whether the user is visible. And if fails,
-                // the user should not be in the mediator's started users structure
-                mInjector.getUserManagerInternal().assignUserToDisplay(userId,
-                        userInfo.profileGroupId, foreground, displayId);
+            t.traceBegin("assignUserToDisplayOnStart");
+            int result = mInjector.getUserManagerInternal().assignUserToDisplayOnStart(userId,
+                    userInfo.profileGroupId, foreground, displayId);
+            t.traceEnd();
+
+            boolean visible;
+            switch (result) {
+                case UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE:
+                    visible = true;
+                    break;
+                case UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE:
+                    visible = false;
+                    break;
+                default:
+                    Slogf.wtf(TAG, "Wrong result from assignUserToDisplayOnStart(): %d", result);
+                    // Fall through
+                case UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE:
+                    Slogf.e(TAG, "%s user(%d) / display (%d) assignment failed: %s",
+                            (foreground ? "fg" : "bg"), userId, displayId,
+                            UserManagerInternal.userAssignmentResultToString(result));
+                    return false;
             }
 
+
             // TODO(b/239982558): might need something similar for bg users on secondary display
             if (foreground && isUserSwitchUiEnabled()) {
                 t.traceBegin("startFreezingScreen");
@@ -1751,19 +1768,6 @@
             }
             t.traceEnd();
 
-            // Need to call UM when user is on background, as there are some cases where the user
-            // cannot be started in background on a secondary display (for example, if user is a
-            // profile).
-            // TODO(b/253103846): it's also explicitly checking if the user is the USER_SYSTEM, as
-            // the UM call would return true during boot (when CarService / BootUserInitializer
-            // calls AM.startUserInBackground() because the system user is still the current user.
-            // TODO(b/244644281): another fragility of this check is that it must wait to call
-            // UMI.isUserVisible() until the user state is check, as that method checks if the
-            // profile of the current user is started. We should fix that dependency so the logic
-            // belongs to just one place (like UserDisplayAssigner)
-            boolean visible = foreground
-                    || userId != UserHandle.USER_SYSTEM
-                            && mInjector.getUserManagerInternal().isUserVisible(userId);
             if (visible) {
                 synchronized (mLock) {
                     addVisibleUserLocked(userId);
@@ -1816,8 +1820,8 @@
                 // user that was started in the background before), so it's necessary to explicitly
                 // notify the services (while when the user starts from BOOTING, USER_START_MSG
                 // takes care of that.
-                mHandler.sendMessage(mHandler.obtainMessage(USER_VISIBILITY_CHANGED_MSG, userId,
-                        visible ? 1 : 0));
+                mHandler.sendMessage(
+                        mHandler.obtainMessage(USER_VISIBILITY_CHANGED_MSG, userId, 1));
             }
 
             t.traceBegin("sendMessages");
diff --git a/services/core/java/com/android/server/cpu/OWNERS b/services/core/java/com/android/server/cpu/OWNERS
new file mode 100644
index 0000000..2f42363
--- /dev/null
+++ b/services/core/java/com/android/server/cpu/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 608533
+
+include platform/packages/services/Car:/OWNERS
+lakshmana@google.com
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index e5c48f9..cb615a9 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -2688,6 +2688,8 @@
     @EnforcePermission(Manifest.permission.MONITOR_INPUT)
     @Override
     public void pilferPointers(IBinder inputChannelToken) {
+        super.pilferPointers_enforcePermission();
+
         Objects.requireNonNull(inputChannelToken);
         mNative.pilferPointers(inputChannelToken);
     }
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 92b685c7..d02faad 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -248,14 +248,14 @@
 
     // Locking order is mUserCreationAndRemovalLock -> mSpManager.
     private final Object mUserCreationAndRemovalLock = new Object();
-    // These two arrays are only used at boot time.  To save memory, they are set to null when
-    // PHASE_BOOT_COMPLETED is reached.
+    // These two arrays are only used at boot time.  To save memory, they are set to null near the
+    // end of the boot, when onThirdPartyAppsStarted() is called.
     @GuardedBy("mUserCreationAndRemovalLock")
     private SparseIntArray mEarlyCreatedUsers = new SparseIntArray();
     @GuardedBy("mUserCreationAndRemovalLock")
     private SparseIntArray mEarlyRemovedUsers = new SparseIntArray();
     @GuardedBy("mUserCreationAndRemovalLock")
-    private boolean mBootComplete;
+    private boolean mThirdPartyAppsStarted;
 
     // Current password metrics for all secured users on the device. Updated when user unlocks the
     // device or changes password. Removed when user is stopped.
@@ -297,16 +297,9 @@
         @Override
         public void onBootPhase(int phase) {
             super.onBootPhase(phase);
-            switch (phase) {
-                case PHASE_ACTIVITY_MANAGER_READY:
-                    mLockSettingsService.migrateOldDataAfterSystemReady();
-                    mLockSettingsService.loadEscrowData();
-                    break;
-                case PHASE_BOOT_COMPLETED:
-                    mLockSettingsService.bootCompleted();
-                    break;
-                default:
-                    break;
+            if (phase == PHASE_ACTIVITY_MANAGER_READY) {
+                mLockSettingsService.migrateOldDataAfterSystemReady();
+                mLockSettingsService.loadEscrowData();
             }
         }
 
@@ -749,8 +742,8 @@
      * <p>
      * This is primarily needed for users that were removed by Android 13 or earlier, which didn't
      * guarantee removal of LSS state as it relied on the {@code ACTION_USER_REMOVED} intent.  It is
-     * also needed because {@link #removeUser()} delays requests to remove LSS state until the
-     * {@code PHASE_BOOT_COMPLETED} boot phase, so they can be lost.
+     * also needed because {@link #removeUser()} delays requests to remove LSS state until Weaver is
+     * guaranteed to be available, so they can be lost.
      * <p>
      * Stale state is detected by checking whether the user serial number changed.  This works
      * because user serial numbers are never reused.
@@ -931,7 +924,9 @@
         return success;
     }
 
-    private void bootCompleted() {
+    // This is called when Weaver is guaranteed to be available (if the device supports Weaver).
+    // It does any synthetic password related work that was delayed from earlier in the boot.
+    private void onThirdPartyAppsStarted() {
         synchronized (mUserCreationAndRemovalLock) {
             // Handle delayed calls to LSS.removeUser() and LSS.createNewUser().
             for (int i = 0; i < mEarlyRemovedUsers.size(); i++) {
@@ -976,7 +971,7 @@
                 setString("migrated_all_users_to_sp_and_bound_ce", "true", 0);
             }
 
-            mBootComplete = true;
+            mThirdPartyAppsStarted = true;
         }
     }
 
@@ -2304,14 +2299,14 @@
 
     private void createNewUser(@UserIdInt int userId, int userSerialNumber) {
         synchronized (mUserCreationAndRemovalLock) {
-            // Before PHASE_BOOT_COMPLETED, don't actually create the synthetic password yet, but
-            // rather automatically delay it to later.  We do this because protecting the synthetic
+            // During early boot, don't actually create the synthetic password yet, but rather
+            // automatically delay it to later.  We do this because protecting the synthetic
             // password requires the Weaver HAL if the device supports it, and some devices don't
             // make Weaver available until fairly late in the boot process.  This logic ensures a
             // consistent flow across all devices, regardless of their Weaver implementation.
-            if (!mBootComplete) {
-                Slogf.i(TAG, "Delaying locksettings state creation for user %d until boot complete",
-                        userId);
+            if (!mThirdPartyAppsStarted) {
+                Slogf.i(TAG, "Delaying locksettings state creation for user %d until third-party " +
+                        "apps are started", userId);
                 mEarlyCreatedUsers.put(userId, userSerialNumber);
                 mEarlyRemovedUsers.delete(userId);
                 return;
@@ -2325,14 +2320,14 @@
 
     private void removeUser(@UserIdInt int userId) {
         synchronized (mUserCreationAndRemovalLock) {
-            // Before PHASE_BOOT_COMPLETED, don't actually remove the LSS state yet, but rather
-            // automatically delay it to later.  We do this because deleting synthetic password
-            // protectors requires the Weaver HAL if the device supports it, and some devices don't
-            // make Weaver available until fairly late in the boot process.  This logic ensures a
-            // consistent flow across all devices, regardless of their Weaver implementation.
-            if (!mBootComplete) {
-                Slogf.i(TAG, "Delaying locksettings state removal for user %d until boot complete",
-                        userId);
+            // During early boot, don't actually remove the LSS state yet, but rather automatically
+            // delay it to later.  We do this because deleting synthetic password protectors
+            // requires the Weaver HAL if the device supports it, and some devices don't make Weaver
+            // available until fairly late in the boot process.  This logic ensures a consistent
+            // flow across all devices, regardless of their Weaver implementation.
+            if (!mThirdPartyAppsStarted) {
+                Slogf.i(TAG, "Delaying locksettings state removal for user %d until third-party " +
+                        "apps are started", userId);
                 if (mEarlyCreatedUsers.indexOfKey(userId) >= 0) {
                     mEarlyCreatedUsers.delete(userId);
                 } else {
@@ -2634,9 +2629,8 @@
      * protects the user's CE key with a key derived from the SP.
      * <p>
      * This is called just once in the lifetime of the user: at user creation time (possibly delayed
-     * until {@code PHASE_BOOT_COMPLETED} to ensure that the Weaver HAL is available if the device
-     * supports it), or when upgrading from Android 13 or earlier where users with no LSKF didn't
-     * necessarily have an SP.
+     * until the time when Weaver is guaranteed to be available), or when upgrading from Android 13
+     * or earlier where users with no LSKF didn't necessarily have an SP.
      */
     @GuardedBy("mSpManager")
     @VisibleForTesting
@@ -3159,7 +3153,7 @@
 
         pw.println("PasswordHandleCount: " + mGatekeeperPasswords.size());
         synchronized (mUserCreationAndRemovalLock) {
-            pw.println("BootComplete: " + mBootComplete);
+            pw.println("ThirdPartyAppsStarted: " + mThirdPartyAppsStarted);
         }
     }
 
@@ -3317,6 +3311,11 @@
     private final class LocalService extends LockSettingsInternal {
 
         @Override
+        public void onThirdPartyAppsStarted() {
+            LockSettingsService.this.onThirdPartyAppsStarted();
+        }
+
+        @Override
         public void unlockUserKeyIfUnsecured(@UserIdInt int userId) {
             LockSettingsService.this.unlockUserKeyIfUnsecured(userId);
         }
diff --git a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
index dcdb881..72ce38b 100644
--- a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
+++ b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
@@ -275,6 +275,10 @@
                 String.valueOf(mComponentType));
     }
 
+    public ComponentName getComponentName() {
+        return mComponentName;
+    }
+
     @ComponentType
     private static int getComponentType(PendingIntent pendingIntent) {
         if (pendingIntent.isBroadcast()) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index c3a5558..90135ad 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -4967,7 +4967,16 @@
             }
             enforcePolicyAccess(Binder.getCallingUid(), "addAutomaticZenRule");
 
-            return mZenModeHelper.addAutomaticZenRule(pkg, automaticZenRule,
+            // If the calling app is the system (from any user), take the package name from the
+            // rule's owner rather than from the caller's package.
+            String rulePkg = pkg;
+            if (isCallingAppIdSystem()) {
+                if (automaticZenRule.getOwner() != null) {
+                    rulePkg = automaticZenRule.getOwner().getPackageName();
+                }
+            }
+
+            return mZenModeHelper.addAutomaticZenRule(rulePkg, automaticZenRule,
                     "addAutomaticZenRule");
         }
 
@@ -9764,6 +9773,12 @@
         return uid == Process.SYSTEM_UID;
     }
 
+    protected boolean isCallingAppIdSystem() {
+        final int uid = Binder.getCallingUid();
+        final int appid = UserHandle.getAppId(uid);
+        return appid == Process.SYSTEM_UID;
+    }
+
     protected boolean isUidSystemOrPhone(int uid) {
         final int appid = UserHandle.getAppId(uid);
         return (appid == Process.SYSTEM_UID || appid == Process.PHONE_UID
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index f2c78ad..4b2c88c 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -326,7 +326,7 @@
 
     public String addAutomaticZenRule(String pkg, AutomaticZenRule automaticZenRule,
             String reason) {
-        if (!isSystemRule(automaticZenRule)) {
+        if (!ZenModeConfig.SYSTEM_AUTHORITY.equals(pkg)) {
             PackageItemInfo component = getServiceInfo(automaticZenRule.getOwner());
             if (component == null) {
                 component = getActivityInfo(automaticZenRule.getConfigurationActivity());
@@ -582,11 +582,6 @@
         }
     }
 
-    private boolean isSystemRule(AutomaticZenRule rule) {
-        return rule.getOwner() != null
-                && ZenModeConfig.SYSTEM_AUTHORITY.equals(rule.getOwner().getPackageName());
-    }
-
     private ServiceInfo getServiceInfo(ComponentName owner) {
         Intent queryIntent = new Intent();
         queryIntent.setComponent(owner);
diff --git a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
index 5e98cc0..978e436 100644
--- a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
+++ b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
@@ -29,6 +29,7 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.os.Binder;
+import android.os.ParcelFileDescriptor;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ShellCommand;
@@ -64,7 +65,8 @@
     private final IOverlayManager mInterface;
     private static final Map<String, Integer> TYPE_MAP = Map.of(
             "color", TypedValue.TYPE_FIRST_COLOR_INT,
-            "string", TypedValue.TYPE_STRING);
+            "string", TypedValue.TYPE_STRING,
+            "drawable", -1);
 
     OverlayManagerShellCommand(@NonNull final Context ctx, @NonNull final IOverlayManager iom) {
         mContext = ctx;
@@ -258,7 +260,7 @@
         String name = "";
         String filename = null;
         String opt;
-        String configuration = null;
+        String config = null;
         while ((opt = getNextOption()) != null) {
             switch (opt) {
                 case "--user":
@@ -277,7 +279,7 @@
                     filename = getNextArgRequired();
                     break;
                 case "--config":
-                    configuration = getNextArgRequired();
+                    config = getNextArgRequired();
                     break;
                 default:
                     err.println("Error: Unknown option: " + opt);
@@ -312,7 +314,9 @@
             final String resourceName = getNextArgRequired();
             final String typeStr = getNextArgRequired();
             final String strData = String.join(" ", peekRemainingArgs());
-            addOverlayValue(overlayBuilder, resourceName, typeStr, strData, configuration);
+            if (addOverlayValue(overlayBuilder, resourceName, typeStr, strData, config) != 0) {
+                return 1;
+            }
         }
 
         mInterface.commit(new OverlayManagerTransaction.Builder()
@@ -369,8 +373,10 @@
                             return 1;
                         }
                         String config = parser.getAttributeValue(null, "config");
-                        addOverlayValue(overlayBuilder, targetPackage + ':' + target,
-                                overlayType, value, config);
+                        if (addOverlayValue(overlayBuilder, targetPackage + ':' + target,
+                                  overlayType, value, config) != 0) {
+                            return 1;
+                        }
                     }
                 }
             }
@@ -384,7 +390,7 @@
         return 0;
     }
 
-    private void addOverlayValue(FabricatedOverlay.Builder overlayBuilder,
+    private int addOverlayValue(FabricatedOverlay.Builder overlayBuilder,
             String resourceName, String typeString, String valueString, String configuration) {
         final int type;
         typeString = typeString.toLowerCase(Locale.getDefault());
@@ -399,6 +405,9 @@
         }
         if (type == TypedValue.TYPE_STRING) {
             overlayBuilder.setResourceValue(resourceName, type, valueString, configuration);
+        } else if (type < 0) {
+            ParcelFileDescriptor pfd =  openFileForSystem(valueString, "r");
+            overlayBuilder.setResourceValue(resourceName, pfd, configuration);
         } else {
             final int intData;
             if (valueString.startsWith("0x")) {
@@ -408,6 +417,7 @@
             }
             overlayBuilder.setResourceValue(resourceName, type, intData, configuration);
         }
+        return 0;
     }
 
     private int runEnableExclusive() throws RemoteException {
diff --git a/services/core/java/com/android/server/pm/AppsFilterBase.java b/services/core/java/com/android/server/pm/AppsFilterBase.java
index 3b676c65..b4792c6 100644
--- a/services/core/java/com/android/server/pm/AppsFilterBase.java
+++ b/services/core/java/com/android/server/pm/AppsFilterBase.java
@@ -44,7 +44,6 @@
 import com.android.server.pm.snapshot.PackageDataSnapshot;
 import com.android.server.utils.SnapshotCache;
 import com.android.server.utils.Watched;
-import com.android.server.utils.WatchedArrayList;
 import com.android.server.utils.WatchedArrayMap;
 import com.android.server.utils.WatchedArraySet;
 import com.android.server.utils.WatchedSparseBooleanMatrix;
@@ -179,9 +178,9 @@
 
     @NonNull
     @Watched
-    protected WatchedArrayList<String> mProtectedBroadcasts;
+    protected WatchedArraySet<String> mProtectedBroadcasts;
     @NonNull
-    protected SnapshotCache<WatchedArrayList<String>> mProtectedBroadcastsSnapshot;
+    protected SnapshotCache<WatchedArraySet<String>> mProtectedBroadcastsSnapshot;
 
     /**
      * This structure maps uid -> uid and indicates whether access from the first should be
diff --git a/services/core/java/com/android/server/pm/AppsFilterImpl.java b/services/core/java/com/android/server/pm/AppsFilterImpl.java
index 2e67bf2..c97711b 100644
--- a/services/core/java/com/android/server/pm/AppsFilterImpl.java
+++ b/services/core/java/com/android/server/pm/AppsFilterImpl.java
@@ -73,7 +73,6 @@
 import com.android.server.utils.SnapshotCache;
 import com.android.server.utils.Watchable;
 import com.android.server.utils.WatchableImpl;
-import com.android.server.utils.WatchedArrayList;
 import com.android.server.utils.WatchedArraySet;
 import com.android.server.utils.WatchedSparseBooleanMatrix;
 import com.android.server.utils.WatchedSparseSetArray;
@@ -223,7 +222,7 @@
         mForceQueryable = new WatchedArraySet<>();
         mForceQueryableSnapshot = new SnapshotCache.Auto<>(
                 mForceQueryable, mForceQueryable, "AppsFilter.mForceQueryable");
-        mProtectedBroadcasts = new WatchedArrayList<>();
+        mProtectedBroadcasts = new WatchedArraySet<>();
         mProtectedBroadcastsSnapshot = new SnapshotCache.Auto<>(
                 mProtectedBroadcasts, mProtectedBroadcasts, "AppsFilter.mProtectedBroadcasts");
         mPermissionToUids = new HashMap<>();
@@ -573,13 +572,17 @@
             return null;
         }
 
-        final boolean protectedBroadcastsChanged;
-        synchronized (mProtectedBroadcastsLock) {
-            protectedBroadcastsChanged =
-                    mProtectedBroadcasts.addAll(newPkg.getProtectedBroadcasts());
-        }
-        if (protectedBroadcastsChanged) {
-            mQueriesViaComponentRequireRecompute.set(true);
+        final List<String> newBroadcasts = newPkg.getProtectedBroadcasts();
+        if (newBroadcasts.size() != 0) {
+            final boolean protectedBroadcastsChanged;
+            synchronized (mProtectedBroadcastsLock) {
+                final int oldSize = mProtectedBroadcasts.size();
+                mProtectedBroadcasts.addAll(newBroadcasts);
+                protectedBroadcastsChanged = mProtectedBroadcasts.size() != oldSize;
+            }
+            if (protectedBroadcastsChanged) {
+                mQueriesViaComponentRequireRecompute.set(true);
+            }
         }
 
         final boolean newIsForceQueryable;
@@ -1149,7 +1152,12 @@
                 final ArrayList<String> protectedBroadcasts = new ArrayList<>(
                         mProtectedBroadcasts.untrackedStorage());
                 collectProtectedBroadcasts(settings, removingPackageName);
-                protectedBroadcastsChanged = !mProtectedBroadcasts.containsAll(protectedBroadcasts);
+                for (int i = 0; i < protectedBroadcasts.size(); ++i) {
+                    if (!mProtectedBroadcasts.contains(protectedBroadcasts.get(i))) {
+                        protectedBroadcastsChanged = true;
+                        break;
+                    }
+                }
             }
         }
 
diff --git a/services/core/java/com/android/server/pm/AppsFilterUtils.java b/services/core/java/com/android/server/pm/AppsFilterUtils.java
index 7daa0b9..483fa8a 100644
--- a/services/core/java/com/android/server/pm/AppsFilterUtils.java
+++ b/services/core/java/com/android/server/pm/AppsFilterUtils.java
@@ -29,7 +29,7 @@
 import com.android.server.pm.pkg.component.ParsedIntentInfo;
 import com.android.server.pm.pkg.component.ParsedMainComponent;
 import com.android.server.pm.pkg.component.ParsedProvider;
-import com.android.server.utils.WatchedArrayList;
+import com.android.server.utils.WatchedArraySet;
 
 import java.util.List;
 import java.util.Set;
@@ -45,7 +45,7 @@
 
     /** Returns true if the querying package may query for the potential target package */
     public static boolean canQueryViaComponents(AndroidPackage querying,
-            AndroidPackage potentialTarget, WatchedArrayList<String> protectedBroadcasts) {
+            AndroidPackage potentialTarget, WatchedArraySet<String> protectedBroadcasts) {
         if (!querying.getQueriesIntents().isEmpty()) {
             for (Intent intent : querying.getQueriesIntents()) {
                 if (matchesPackage(intent, potentialTarget, protectedBroadcasts)) {
@@ -117,7 +117,7 @@
     }
 
     private static boolean matchesPackage(Intent intent, AndroidPackage potentialTarget,
-            WatchedArrayList<String> protectedBroadcasts) {
+            WatchedArraySet<String> protectedBroadcasts) {
         if (matchesAnyComponents(
                 intent, potentialTarget.getServices(), null /*protectedBroadcasts*/)) {
             return true;
@@ -138,7 +138,7 @@
 
     private static boolean matchesAnyComponents(Intent intent,
             List<? extends ParsedMainComponent> components,
-            WatchedArrayList<String> protectedBroadcasts) {
+            WatchedArraySet<String> protectedBroadcasts) {
         for (int i = ArrayUtils.size(components) - 1; i >= 0; i--) {
             ParsedMainComponent component = components.get(i);
             if (!component.isExported()) {
@@ -152,7 +152,7 @@
     }
 
     private static boolean matchesAnyFilter(Intent intent, ParsedComponent component,
-            WatchedArrayList<String> protectedBroadcasts) {
+            WatchedArraySet<String> protectedBroadcasts) {
         List<ParsedIntentInfo> intents = component.getIntents();
         for (int i = ArrayUtils.size(intents) - 1; i >= 0; i--) {
             IntentFilter intentFilter = intents.get(i).getIntentFilter();
@@ -164,7 +164,7 @@
     }
 
     private static boolean matchesIntentFilter(Intent intent, IntentFilter intentFilter,
-            @Nullable WatchedArrayList<String> protectedBroadcasts) {
+            @Nullable WatchedArraySet<String> protectedBroadcasts) {
         return intentFilter.match(intent.getAction(), intent.getType(), intent.getScheme(),
                 intent.getData(), intent.getCategories(), "AppsFilter", true,
                 protectedBroadcasts != null ? protectedBroadcasts.untrackedStorage() : null) > 0;
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index 9155830..9dafcce 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -25,6 +25,7 @@
 import android.graphics.Bitmap;
 import android.os.Bundle;
 import android.os.UserManager;
+import android.util.DebugUtils;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -46,6 +47,18 @@
     public @interface OwnerType {
     }
 
+    public static final int USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE = 1;
+    public static final int USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE = 2;
+    public static final int USER_ASSIGNMENT_RESULT_FAILURE = -1;
+
+    private static final String PREFIX_USER_ASSIGNMENT_RESULT = "USER_ASSIGNMENT_RESULT";
+    @IntDef(flag = false, prefix = {PREFIX_USER_ASSIGNMENT_RESULT}, value = {
+            USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE,
+            USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE,
+            USER_ASSIGNMENT_RESULT_FAILURE
+    })
+    public @interface UserAssignmentResult {}
+
     public interface UserRestrictionsListener {
         /**
          * Called when a user restriction changes.
@@ -343,34 +356,28 @@
     public abstract @Nullable UserProperties getUserProperties(@UserIdInt int userId);
 
     /**
-     * Assigns a user to a display.
-     *
-     * <p>On most devices this call will be a no-op, but it will be used on devices that support
-     * multiple users on multiple displays (like automotives with passenger displays).
+     * Assigns a user to a display when it's starting, returning whether the assignment succeeded
+     * and the user is {@link UserManager#isUserVisible() visible}.
      *
      * <p><b>NOTE: </b>this method is meant to be used only by {@code UserController} (when a user
-     * is started)
+     * is started). If other clients (like {@code CarService} need to explicitly change the user /
+     * display assignment, we'll need to provide other APIs.
      *
      * <p><b>NOTE: </b>this method doesn't validate if the display exists, it's up to the caller to
-     * check it. In fact, one of the intended clients for this method is
-     * {@code DisplayManagerService}, which will call it when a virtual display is created (another
-     * client is {@code UserController}, which will call it when a user is started).
+     * pass a valid display id.
      */
-    // TODO(b/244644281): rename to assignUserToDisplayOnStart() and make sure it's called on boot
-    // as well
-    public abstract void assignUserToDisplay(@UserIdInt int userId, @UserIdInt int profileGroupId,
+    public abstract @UserAssignmentResult int assignUserToDisplayOnStart(@UserIdInt int userId,
+            @UserIdInt int profileGroupId,
             boolean foreground, int displayId);
 
     /**
-     * Unassigns a user from its current display.
-     *
-     * <p>On most devices this call will be a no-op, but it will be used on devices that support
-     * multiple users on multiple displays (like automotives with passenger displays).
+     * Unassigns a user from its current display when it's stopping.
      *
      * <p><b>NOTE: </b>this method is meant to be used only by {@code UserController} (when a user
-     * is stopped).
+     * is stopped). If other clients (like {@code CarService} need to explicitly change the user /
+     * display assignment, we'll need to provide other APIs.
      */
-    public abstract void unassignUserFromDisplay(@UserIdInt int userId);
+    public abstract void unassignUserFromDisplayOnStop(@UserIdInt int userId);
 
     /**
      * Returns {@code true} if the user is visible (as defined by
@@ -413,6 +420,15 @@
      */
     public abstract @UserIdInt int getUserAssignedToDisplay(int displayId);
 
+    /**
+     * Gets the user-friendly representation of the {@code result} of a
+     * {@link #assignUserToDisplayOnStart(int, int, boolean, int)} call.
+     */
+    public static String userAssignmentResultToString(@UserAssignmentResult int result) {
+        return DebugUtils.constantToString(UserManagerInternal.class, PREFIX_USER_ASSIGNMENT_RESULT,
+                result);
+    }
+
     /** Adds a {@link UserVisibilityListener}. */
     public abstract void addUserVisibilityListener(UserVisibilityListener listener);
 
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index d255669..ac487fa 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -18,6 +18,7 @@
 
 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.os.UserManager.DEV_CREATE_OVERRIDE_PROPERTY;
 import static android.os.UserManager.DISALLOW_USER_SWITCH;
 import static android.os.UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY;
 
@@ -2814,7 +2815,8 @@
         synchronized (mUsersLock) {
             count = getAliveUsersExcludingGuestsCountLU();
         }
-        return count >= UserManager.getMaxSupportedUsers();
+        return count >= UserManager.getMaxSupportedUsers()
+                && !isCreationOverrideEnabled();
     }
 
     /**
@@ -2824,15 +2826,16 @@
      * <p>For checking whether more profiles can be added to a particular parent use
      * {@link #canAddMoreProfilesToUser}.
      */
-    private boolean canAddMoreUsersOfType(UserTypeDetails userTypeDetails) {
-        if (!userTypeDetails.isEnabled()) {
+    private boolean canAddMoreUsersOfType(@NonNull UserTypeDetails userTypeDetails) {
+        if (!isUserTypeEnabled(userTypeDetails)) {
             return false;
         }
         final int max = userTypeDetails.getMaxAllowed();
         if (max == UserTypeDetails.UNLIMITED_NUMBER_OF_USERS) {
             return true; // Indicates that there is no max.
         }
-        return getNumberOfUsersOfType(userTypeDetails.getName()) < max;
+        return getNumberOfUsersOfType(userTypeDetails.getName()) < max
+                || isCreationOverrideEnabled();
     }
 
     /**
@@ -2843,7 +2846,7 @@
     public int getRemainingCreatableUserCount(String userType) {
         checkQueryOrCreateUsersPermission("get the remaining number of users that can be added.");
         final UserTypeDetails type = mUserTypes.get(userType);
-        if (type == null || !type.isEnabled()) {
+        if (type == null || !isUserTypeEnabled(type)) {
             return 0;
         }
         synchronized (mUsersLock) {
@@ -2917,7 +2920,21 @@
     public boolean isUserTypeEnabled(String userType) {
         checkCreateUsersPermission("check if user type is enabled.");
         final UserTypeDetails userTypeDetails = mUserTypes.get(userType);
-        return userTypeDetails != null && userTypeDetails.isEnabled();
+        return userTypeDetails != null && isUserTypeEnabled(userTypeDetails);
+    }
+
+    /** Returns whether the creation of users of the given user type is enabled on this device. */
+    private boolean isUserTypeEnabled(@NonNull UserTypeDetails userTypeDetails) {
+        return userTypeDetails.isEnabled() || isCreationOverrideEnabled();
+    }
+
+    /**
+     * Returns whether to almost-always allow creating users even beyond their limit or if disabled.
+     * For Debug builds only.
+     */
+    private boolean isCreationOverrideEnabled() {
+        return Build.isDebuggable()
+                && SystemProperties.getBoolean(DEV_CREATE_OVERRIDE_PROPERTY, false);
     }
 
     @Override
@@ -2930,7 +2947,8 @@
     @Override
     public boolean canAddMoreProfilesToUser(String userType, @UserIdInt int userId,
             boolean allowedToRemoveOne) {
-        return 0 < getRemainingCreatableProfileCount(userType, userId, allowedToRemoveOne);
+        return 0 < getRemainingCreatableProfileCount(userType, userId, allowedToRemoveOne)
+                || isCreationOverrideEnabled();
     }
 
     @Override
@@ -2948,7 +2966,7 @@
         checkQueryOrCreateUsersPermission(
                 "get the remaining number of profiles that can be added to the given user.");
         final UserTypeDetails type = mUserTypes.get(userType);
-        if (type == null || !type.isEnabled()) {
+        if (type == null || !isUserTypeEnabled(type)) {
             return 0;
         }
         // Managed profiles have their own specific rules.
@@ -4407,7 +4425,7 @@
                     + ") indicated SYSTEM user, which cannot be created.");
             return null;
         }
-        if (!userTypeDetails.isEnabled()) {
+        if (!isUserTypeEnabled(userTypeDetails)) {
             throwCheckedUserOperationException(
                     "Cannot add a user of disabled type " + userType + ".",
                     UserManager.USER_OPERATION_ERROR_MAX_USERS);
@@ -4479,27 +4497,12 @@
                                     + " for user " + parentId,
                             UserManager.USER_OPERATION_ERROR_MAX_USERS);
                 }
-                // In legacy mode, restricted profile's parent can only be the owner user
-                if (isRestricted && !UserManager.isSplitSystemUser()
-                        && (parentId != UserHandle.USER_SYSTEM)) {
+                if (isRestricted && (parentId != UserHandle.USER_SYSTEM)
+                        && !isCreationOverrideEnabled()) {
                     throwCheckedUserOperationException(
-                            "Cannot add restricted profile - parent user must be owner",
+                            "Cannot add restricted profile - parent user must be system",
                             UserManager.USER_OPERATION_ERROR_UNKNOWN);
                 }
-                if (isRestricted && UserManager.isSplitSystemUser()) {
-                    if (parent == null) {
-                        throwCheckedUserOperationException(
-                                "Cannot add restricted profile - parent user must be specified",
-                                UserManager.USER_OPERATION_ERROR_UNKNOWN);
-                    }
-                    if (!parent.info.canHaveProfile()) {
-                        throwCheckedUserOperationException(
-                                "Cannot add restricted profile - profiles cannot be created for "
-                                        + "the specified parent user id "
-                                        + parentId,
-                                UserManager.USER_OPERATION_ERROR_UNKNOWN);
-                    }
-                }
 
                 userId = getNextAvailableId();
                 Slog.i(LOG_TAG, "Creating user " + userId + " of type " + userType);
@@ -6799,15 +6802,14 @@
         }
 
         @Override
-        public void assignUserToDisplay(@UserIdInt int userId, @UserIdInt int profileGroupId,
+        public int assignUserToDisplayOnStart(@UserIdInt int userId, @UserIdInt int profileGroupId,
                 boolean foreground, int displayId) {
-            mUserVisibilityMediator.startUser(userId, profileGroupId, foreground, displayId);
-            mUserVisibilityMediator.assignUserToDisplay(userId, profileGroupId, displayId);
+            return mUserVisibilityMediator.startUser(userId, profileGroupId, foreground,
+                    displayId);
         }
 
         @Override
-        public void unassignUserFromDisplay(@UserIdInt int userId) {
-            mUserVisibilityMediator.unassignUserFromDisplay(userId);
+        public void unassignUserFromDisplayOnStop(@UserIdInt int userId) {
             mUserVisibilityMediator.stopUser(userId);
         }
 
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index b8950e1..871420a9 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -397,6 +397,7 @@
                 }
 
                 setIntAttribute(parser, "enabled", builder::setEnabled);
+                setIntAttribute(parser, "max-allowed", builder::setMaxAllowed);
 
                 // Process child elements.
                 final int depth = parser.getDepth();
diff --git a/services/core/java/com/android/server/pm/UserVisibilityMediator.java b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
index bd81062..cbf7dfe 100644
--- a/services/core/java/com/android/server/pm/UserVisibilityMediator.java
+++ b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
@@ -20,12 +20,15 @@
 import static android.os.UserHandle.USER_SYSTEM;
 import static android.view.Display.DEFAULT_DISPLAY;
 
-import android.annotation.IntDef;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
+import static com.android.server.pm.UserManagerInternal.userAssignmentResultToString;
+
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.util.DebugUtils;
 import android.util.Dumpable;
 import android.util.IndentingPrintWriter;
 import android.util.SparseIntArray;
@@ -34,6 +37,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
+import com.android.server.pm.UserManagerInternal.UserAssignmentResult;
 import com.android.server.utils.Slogf;
 
 import java.io.PrintWriter;
@@ -52,23 +56,10 @@
 
     private static final String TAG = UserVisibilityMediator.class.getSimpleName();
 
-    private static final String PREFIX_START_USER_RESULT = "START_USER_";
-
     // TODO(b/242195409): might need to change this if boot logic is refactored for HSUM devices
     @VisibleForTesting
     static final int INITIAL_CURRENT_USER_ID = USER_SYSTEM;
 
-    public static final int START_USER_RESULT_SUCCESS_VISIBLE = 1;
-    public static final int START_USER_RESULT_SUCCESS_INVISIBLE = 2;
-    public static final int START_USER_RESULT_FAILURE = -1;
-
-    @IntDef(flag = false, prefix = {PREFIX_START_USER_RESULT}, value = {
-            START_USER_RESULT_SUCCESS_VISIBLE,
-            START_USER_RESULT_SUCCESS_INVISIBLE,
-            START_USER_RESULT_FAILURE
-    })
-    public @interface StartUserResult {}
-
     private final Object mLock = new Object();
 
     private final boolean mUsersOnSecondaryDisplaysEnabled;
@@ -97,10 +88,37 @@
     }
 
     /**
-     * TODO(b/244644281): merge with assignUserToDisplay() or add javadoc.
+     * See {@link UserManagerInternal#assignUserToDisplayOnStart(int, int, boolean, int)}.
      */
-    public @StartUserResult int startUser(@UserIdInt int userId, @UserIdInt int profileGroupId,
+    public @UserAssignmentResult int startUser(@UserIdInt int userId, @UserIdInt int profileGroupId,
             boolean foreground, int displayId) {
+        // TODO(b/244644281): this method need to perform 4 actions:
+        //
+        // 1. Check if the user can be started given the provided arguments
+        // 2. If it can, decide whether it's visible or not (which is the return value)
+        // 3. Update the current user / profiles state
+        // 4. Update the users on secondary display state (if applicable)
+        //
+        // Ideally, they should be done "atomically" (i.e, only changing state while holding the
+        // mLock), but the initial implementation is just calling the existing methods, as the
+        // focus is to change the UserController startUser() workflow (so it relies on this class
+        // for the logic above).
+        //
+        // The next CL will refactor it (and the unit tests) to achieve that atomicity.
+        int result = startOnly(userId, profileGroupId, foreground, displayId);
+        if (result != USER_ASSIGNMENT_RESULT_FAILURE) {
+            assignUserToDisplay(userId, profileGroupId, displayId);
+        }
+        return result;
+    }
+
+    /**
+     * @deprecated - see comment inside {@link #startUser(int, int, boolean, int)}
+     */
+    @Deprecated
+    @VisibleForTesting
+    @UserAssignmentResult int startOnly(@UserIdInt int userId,
+            @UserIdInt int profileGroupId, boolean foreground, int displayId) {
         int actualProfileGroupId = profileGroupId == NO_PROFILE_GROUP_ID
                 ? userId
                 : profileGroupId;
@@ -111,7 +129,7 @@
         if (foreground && displayId != DEFAULT_DISPLAY) {
             Slogf.w(TAG, "startUser(%d, %d, %b, %d) failed: cannot start foreground user on "
                     + "secondary display", userId, actualProfileGroupId, foreground, displayId);
-            return START_USER_RESULT_FAILURE;
+            return USER_ASSIGNMENT_RESULT_FAILURE;
         }
 
         int visibility;
@@ -121,13 +139,12 @@
                     Slogf.w(TAG, "startUser(%d, %d, %b, %d) failed: cannot start profile user on "
                             + "secondary display", userId, actualProfileGroupId, foreground,
                             displayId);
-                    return START_USER_RESULT_FAILURE;
+                    return USER_ASSIGNMENT_RESULT_FAILURE;
                 }
                 if (foreground) {
                     Slogf.w(TAG, "startUser(%d, %d, %b, %d) failed: cannot start profile user in "
-                            + "foreground", userId, actualProfileGroupId, foreground,
-                            displayId);
-                    return START_USER_RESULT_FAILURE;
+                            + "foreground", userId, actualProfileGroupId, foreground, displayId);
+                    return USER_ASSIGNMENT_RESULT_FAILURE;
                 } else {
                     boolean isParentRunning = mStartedProfileGroupIds
                             .get(actualProfileGroupId) == actualProfileGroupId;
@@ -135,18 +152,18 @@
                         Slogf.d(TAG, "profile parent running: %b", isParentRunning);
                     }
                     visibility = isParentRunning
-                            ? START_USER_RESULT_SUCCESS_VISIBLE
-                            : START_USER_RESULT_SUCCESS_INVISIBLE;
+                            ? USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE
+                            : USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
                 }
             } else if (foreground) {
                 mCurrentUserId = userId;
-                visibility = START_USER_RESULT_SUCCESS_VISIBLE;
+                visibility = USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
             } else {
-                visibility = START_USER_RESULT_SUCCESS_INVISIBLE;
+                visibility = USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
             }
             if (DBG) {
                 Slogf.d(TAG, "adding user / profile mapping (%d -> %d) and returning %s",
-                        userId, actualProfileGroupId, startUserResultToString(visibility));
+                        userId, actualProfileGroupId, userAssignmentResultToString(visibility));
             }
             mStartedProfileGroupIds.put(userId, actualProfileGroupId);
         }
@@ -154,21 +171,11 @@
     }
 
     /**
-     * TODO(b/244644281): merge with unassignUserFromDisplay() or add javadoc (and unit tests)
+     * @deprecated - see comment inside {@link #startUser(int, int, boolean, int)}
      */
-    public void stopUser(@UserIdInt int userId) {
-        if (DBG) {
-            Slogf.d(TAG, "stopUser(%d)", userId);
-        }
-        synchronized (mLock) {
-            mStartedProfileGroupIds.delete(userId);
-        }
-    }
-
-    /**
-     * See {@link UserManagerInternal#assignUserToDisplay(int, int)}.
-     */
-    public void assignUserToDisplay(int userId, int profileGroupId, int displayId) {
+    @Deprecated
+    @VisibleForTesting
+    void assignUserToDisplay(int userId, int profileGroupId, int displayId) {
         if (DBG) {
             Slogf.d(TAG, "assignUserToDisplay(%d, %d): mUsersOnSecondaryDisplaysEnabled=%b",
                     userId, displayId, mUsersOnSecondaryDisplaysEnabled);
@@ -246,23 +253,26 @@
     }
 
     /**
-     * See {@link UserManagerInternal#unassignUserFromDisplay(int)}.
+     * See {@link UserManagerInternal#unassignUserFromDisplayOnStop(int)}.
      */
-    public void unassignUserFromDisplay(int userId) {
+    public void stopUser(int userId) {
         if (DBG) {
-            Slogf.d(TAG, "unassignUserFromDisplay(%d)", userId);
+            Slogf.d(TAG, "stopUser(%d)", userId);
         }
-        if (!mUsersOnSecondaryDisplaysEnabled) {
-            // Don't need to do anything because methods (such as isUserVisible()) already know
-            // that the current user (and their profiles) is assigned to the default display.
-            if (DBG) {
-                Slogf.d(TAG, "ignoring when device doesn't support MUMD");
-            }
-            return;
-        }
-
         synchronized (mLock) {
             if (DBG) {
+                Slogf.d(TAG, "Removing %d from mStartedProfileGroupIds (%s)", userId,
+                        mStartedProfileGroupIds);
+            }
+            mStartedProfileGroupIds.delete(userId);
+
+            if (!mUsersOnSecondaryDisplaysEnabled) {
+                // Don't need to do update mUsersOnSecondaryDisplays because methods (such as
+                // isUserVisible()) already know that the current user (and their profiles) is
+                // assigned to the default display.
+                return;
+            }
+            if (DBG) {
                 Slogf.d(TAG, "Removing %d from mUsersOnSecondaryDisplays (%s)", userId,
                         mUsersOnSecondaryDisplays);
             }
@@ -395,33 +405,40 @@
             ipw.print("Current user id: ");
             ipw.println(mCurrentUserId);
 
-            ipw.print("Number of started user / profile group mappings: ");
-            ipw.println(mStartedProfileGroupIds.size());
-            if (mStartedProfileGroupIds.size() > 0) {
-                ipw.increaseIndent();
-                for (int i = 0; i < mStartedProfileGroupIds.size(); i++) {
-                    ipw.print("User #");
-                    ipw.print(mStartedProfileGroupIds.keyAt(i));
-                    ipw.print(" -> profile #");
-                    ipw.println(mStartedProfileGroupIds.valueAt(i));
-                }
-                ipw.decreaseIndent();
-            }
+            dumpIntArray(ipw, mStartedProfileGroupIds, "started user / profile group", "u", "pg");
 
-            ipw.print("Supports users on secondary displays: ");
+            ipw.print("Supports background users on secondary displays: ");
             ipw.println(mUsersOnSecondaryDisplaysEnabled);
 
             if (mUsersOnSecondaryDisplaysEnabled) {
-                ipw.print("Users on secondary displays: ");
-                synchronized (mLock) {
-                    ipw.println(mUsersOnSecondaryDisplays);
-                }
+                dumpIntArray(ipw, mUsersOnSecondaryDisplays, "background user / secondary display",
+                        "u", "d");
             }
         }
 
         ipw.decreaseIndent();
     }
 
+    private static void dumpIntArray(IndentingPrintWriter ipw, SparseIntArray array,
+            String arrayDescription, String keyName, String valueName) {
+        ipw.print("Number of ");
+        ipw.print(arrayDescription);
+        ipw.print(" mappings: ");
+        ipw.println(array.size());
+        if (array.size() <= 0) {
+            return;
+        }
+        ipw.increaseIndent();
+        for (int i = 0; i < array.size(); i++) {
+            ipw.print(keyName); ipw.print(':');
+            ipw.print(array.keyAt(i));
+            ipw.print(" -> ");
+            ipw.print(valueName); ipw.print(':');
+            ipw.println(array.valueAt(i));
+        }
+        ipw.decreaseIndent();
+    }
+
     @Override
     public void dump(PrintWriter pw, String[] args) {
         if (pw instanceof IndentingPrintWriter) {
@@ -445,14 +462,6 @@
         return map;
     }
 
-    /**
-     * Gets the user-friendly representation of the {@code result}.
-     */
-    public static String startUserResultToString(@StartUserResult int result) {
-        return DebugUtils.constantToString(UserVisibilityMediator.class, PREFIX_START_USER_RESULT,
-                result);
-    }
-
     // TODO(b/244644281): methods below are needed because some APIs use the current users (full and
     // profiles) state to decide whether a user is visible or not. If we decide to always store that
     // info into intermediate maps, we should remove them.
@@ -483,6 +492,14 @@
     }
 
     @VisibleForTesting
+    boolean isStartedUser(@UserIdInt int userId) {
+        synchronized (mLock) {
+            return mStartedProfileGroupIds.get(userId,
+                    INITIAL_CURRENT_USER_ID) != INITIAL_CURRENT_USER_ID;
+        }
+    }
+
+    @VisibleForTesting
     boolean isStartedProfile(@UserIdInt int userId) {
         int profileGroupId;
         synchronized (mLock) {
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 5f9ab95..5a638bb 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -50,6 +50,7 @@
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
 import android.content.pm.permission.SplitPermissionInfoParcelable;
+import android.healthconnect.HealthConnectManager;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.Process;
@@ -1156,7 +1157,8 @@
             if (permissionInfo == null) {
                 try {
                     permissionInfo = context.getPackageManager().getPermissionInfo(permission, 0);
-                    if (PLATFORM_PACKAGE_NAME.equals(permissionInfo.packageName)) {
+                    if (PLATFORM_PACKAGE_NAME.equals(permissionInfo.packageName)
+                            || HealthConnectManager.isHealthPermission(context, permission)) {
                         // Double addition due to concurrency is fine - the backing
                         // store is concurrent.
                         sPlatformPermissions.put(permission, permissionInfo);
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 98b5c1b..3aa333a 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3589,7 +3589,12 @@
                     @Override
                     public void onKeyguardExitResult(boolean success) {
                         if (success) {
-                            startDockOrHome(displayId, true /*fromHomeKey*/, awakenFromDreams);
+                            final long origId = Binder.clearCallingIdentity();
+                            try {
+                                startDockOrHome(displayId, true /*fromHomeKey*/, awakenFromDreams);
+                            } finally {
+                                Binder.restoreCallingIdentity(origId);
+                            }
                         }
                     }
                 });
diff --git a/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java b/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
index d9f504e..ac97038 100644
--- a/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
+++ b/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
@@ -206,16 +206,21 @@
                 Log.d(TAG, "[" + mTag + "] binding to " + mBoundServiceInfo);
             }
 
+            mRebinder = null;
+
             Intent bindIntent = new Intent(mBoundServiceInfo.getAction()).setComponent(
                     mBoundServiceInfo.getComponentName());
-            if (!mContext.bindServiceAsUser(bindIntent, this,
-                    BIND_AUTO_CREATE | BIND_NOT_FOREGROUND | BIND_NOT_VISIBLE,
-                    mHandler, UserHandle.of(mBoundServiceInfo.getUserId()))) {
-                Log.e(TAG, "[" + mTag + "] unexpected bind failure - retrying later");
-                mRebinder = this::bind;
-                mHandler.postDelayed(mRebinder, RETRY_DELAY_MS);
-            } else {
-                mRebinder = null;
+            try {
+                if (!mContext.bindServiceAsUser(bindIntent, this,
+                        BIND_AUTO_CREATE | BIND_NOT_FOREGROUND | BIND_NOT_VISIBLE,
+                        mHandler, UserHandle.of(mBoundServiceInfo.getUserId()))) {
+                    Log.e(TAG, "[" + mTag + "] unexpected bind failure - retrying later");
+                    mRebinder = this::bind;
+                    mHandler.postDelayed(mRebinder, RETRY_DELAY_MS);
+                }
+            } catch (SecurityException e) {
+                // if anything goes wrong it shouldn't crash the system server
+                Log.e(TAG, "[" + mTag + "] " + mBoundServiceInfo + " bind failed", e);
             }
         }
 
diff --git a/services/core/java/com/android/server/timedetector/EnvironmentImpl.java b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
index 8d106f7..5801920 100644
--- a/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
+++ b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
@@ -128,4 +128,5 @@
     @Override
     public void dumpDebugLog(@NonNull PrintWriter printWriter) {
         SystemClockTime.dump(printWriter);
-    }}
+    }
+}
diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
index f52f0b7..dbddb41 100644
--- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
@@ -2253,6 +2253,23 @@
         }
 
         @Override
+        public void onRequestStopRecording(String recordingId) {
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slogf.d(TAG, "onRequestStopRecording");
+                }
+                if (mSessionState.mSession == null || mSessionState.mClient == null) {
+                    return;
+                }
+                try {
+                    mSessionState.mClient.onRequestStopRecording(recordingId, mSessionState.mSeq);
+                } catch (RemoteException e) {
+                    Slogf.e(TAG, "error in onRequestStopRecording", e);
+                }
+            }
+        }
+
+        @Override
         public void onRequestSigning(String id, String algorithm, String alias, byte[] data) {
             synchronized (mLock) {
                 if (DEBUG) {
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index e6d9492..a857d90 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1989,12 +1989,6 @@
                 ? targetTask.getTopNonFinishingActivity()
                 : targetTaskTop;
 
-        // At this point we are certain we want the task moved to the front. If we need to dismiss
-        // any other always-on-top root tasks, now is the time to do it.
-        if (targetTaskTop.canTurnScreenOn() && mService.isDreaming()) {
-            targetTaskTop.mTaskSupervisor.wakeUp("recycleTask#turnScreenOnFlag");
-        }
-
         if (mMovedToFront) {
             // We moved the task to front, use starting window to hide initial drawn delay.
             targetTaskTop.showStartingWindow(true /* taskSwitch */);
@@ -2006,6 +2000,12 @@
         // And for paranoia, make sure we have correctly resumed the top activity.
         resumeTargetRootTaskIfNeeded();
 
+        // This is moving an existing task to front. But since dream activity has a higher z-order
+        // to cover normal activities, it needs the awakening event to be dismissed.
+        if (mService.isDreaming() && targetTaskTop.canTurnScreenOn()) {
+            targetTaskTop.mTaskSupervisor.wakeUp("recycleTask#turnScreenOnFlag");
+        }
+
         mLastStartActivityRecord = targetTaskTop;
         return mMovedToFront ? START_TASK_TO_FRONT : START_DELIVERED_TO_TOP;
     }
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 9398bbe..f5da4c8 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -19,6 +19,7 @@
 import static android.view.RemoteAnimationTarget.MODE_CLOSING;
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
 import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 
@@ -831,9 +832,8 @@
     }
 
     boolean isWallpaperVisible(WindowState w) {
-        if (mBackAnimationInProgress && w.isFocused()) {
-            return mShowWallpaper;
-        }
-        return false;
+        return mAnimationTargets.mComposed && mShowWallpaper
+                && w.mAttrs.type == TYPE_BASE_APPLICATION && w.mActivityRecord != null
+                && mAnimationTargets.isTarget(w.mActivityRecord, true /* open */);
     }
 }
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 872542a..dfcad48 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -291,6 +291,12 @@
     private final IBinder mFragmentToken;
 
     /**
+     * Whether to delay the call to {@link #updateOrganizedTaskFragmentSurface()} when there is a
+     * configuration change.
+     */
+    private boolean mDelayOrganizedTaskFragmentSurfaceUpdate;
+
+    /**
      * Whether to delay the last activity of TaskFragment being immediately removed while finishing.
      * This should only be set on a embedded TaskFragment, where the organizer can have the
      * opportunity to perform animations and finishing the adjacent TaskFragment.
@@ -2264,35 +2270,41 @@
 
     @Override
     public void onConfigurationChanged(Configuration newParentConfig) {
-        // Task will animate differently.
-        if (mTaskFragmentOrganizer != null) {
-            mTmpPrevBounds.set(getBounds());
-        }
-
         super.onConfigurationChanged(newParentConfig);
 
-        final boolean shouldStartChangeTransition = shouldStartChangeTransition(mTmpPrevBounds);
-        if (shouldStartChangeTransition) {
-            initializeChangeTransition(mTmpPrevBounds);
-        }
         if (mTaskFragmentOrganizer != null) {
-            if (mTransitionController.isShellTransitionsEnabled()
-                    && !mTransitionController.isCollecting(this)) {
-                // TaskFragmentOrganizer doesn't have access to the surface for security reasons, so
-                // update the surface here if it is not collected by Shell transition.
-                updateOrganizedTaskFragmentSurface();
-            } else if (!mTransitionController.isShellTransitionsEnabled()
-                    && !shouldStartChangeTransition) {
-                // Update the surface here instead of in the organizer so that we can make sure
-                // it can be synced with the surface freezer for legacy app transition.
-                updateOrganizedTaskFragmentSurface();
-            }
+            updateOrganizedTaskFragmentSurface();
         }
 
         sendTaskFragmentInfoChanged();
     }
 
+    void deferOrganizedTaskFragmentSurfaceUpdate() {
+        mDelayOrganizedTaskFragmentSurfaceUpdate = true;
+    }
+
+    void continueOrganizedTaskFragmentSurfaceUpdate() {
+        mDelayOrganizedTaskFragmentSurfaceUpdate = false;
+        updateOrganizedTaskFragmentSurface();
+    }
+
     private void updateOrganizedTaskFragmentSurface() {
+        if (mDelayOrganizedTaskFragmentSurfaceUpdate) {
+            return;
+        }
+        if (mTransitionController.isShellTransitionsEnabled()
+                && !mTransitionController.isCollecting(this)) {
+            // TaskFragmentOrganizer doesn't have access to the surface for security reasons, so
+            // update the surface here if it is not collected by Shell transition.
+            updateOrganizedTaskFragmentSurfaceUnchecked();
+        } else if (!mTransitionController.isShellTransitionsEnabled() && !isAnimating()) {
+            // Update the surface here instead of in the organizer so that we can make sure
+            // it can be synced with the surface freezer for legacy app transition.
+            updateOrganizedTaskFragmentSurfaceUnchecked();
+        }
+    }
+
+    private void updateOrganizedTaskFragmentSurfaceUnchecked() {
         final SurfaceControl.Transaction t = getSyncTransaction();
         updateSurfacePosition(t);
         updateOrganizedTaskFragmentSurfaceSize(t, false /* forceUpdate */);
@@ -2346,7 +2358,7 @@
     }
 
     /** Whether we should prepare a transition for this {@link TaskFragment} bounds change. */
-    private boolean shouldStartChangeTransition(Rect startBounds) {
+    boolean shouldStartChangeTransition(Rect startBounds) {
         if (mTaskFragmentOrganizer == null || !canStartChangeTransition()) {
             return false;
         }
@@ -2366,7 +2378,7 @@
     void setSurfaceControl(SurfaceControl sc) {
         super.setSurfaceControl(sc);
         if (mTaskFragmentOrganizer != null) {
-            updateOrganizedTaskFragmentSurface();
+            updateOrganizedTaskFragmentSurfaceUnchecked();
             // If the TaskFragmentOrganizer was set before we created the SurfaceControl, we need to
             // emit the callbacks now.
             sendTaskFragmentAppeared();
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 37bef3a..25df511 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -423,7 +423,7 @@
         Transition newTransition = null;
         if (isCollecting()) {
             if (displayChange != null) {
-                throw new IllegalArgumentException("Provided displayChange for a non-new request");
+                Slog.e(TAG, "Provided displayChange for a non-new request", new Throwable());
             }
             // Make the collecting transition wait until this request is ready.
             mCollectingTransition.setReady(readyGroupRef, false);
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 7415376..de12a4e 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -48,6 +48,7 @@
 import static com.android.server.wm.ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED;
 import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission;
 import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
+import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM;
 import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_PINNED_TASK;
 import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
 import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
@@ -144,6 +145,8 @@
     @VisibleForTesting
     final ArrayMap<IBinder, TaskFragment> mLaunchTaskFragments = new ArrayMap<>();
 
+    private final Rect mTmpBounds = new Rect();
+
     WindowOrganizerController(ActivityTaskManagerService atm) {
         mService = atm;
         mGlobalLock = atm.mGlobalLock;
@@ -702,7 +705,7 @@
     }
 
     private int applyTaskChanges(Task tr, WindowContainerTransaction.Change c) {
-        int effects = 0;
+        int effects = applyChanges(tr, c, null /* errorCallbackToken */);
         final SurfaceControl.Transaction t = c.getBoundsChangeTransaction();
 
         if ((c.getChangeMask() & WindowContainerTransaction.Change.CHANGE_HIDDEN) != 0) {
@@ -717,6 +720,10 @@
             effects = TRANSACT_EFFECTS_LIFECYCLE;
         }
 
+        if ((c.getChangeMask() & WindowContainerTransaction.Change.CHANGE_DRAG_RESIZING) != 0) {
+            tr.setDragResizing(c.getDragResizing(), DRAG_RESIZE_MODE_FREEFORM);
+        }
+
         final int childWindowingMode = c.getActivityWindowingMode();
         if (childWindowingMode > -1) {
             tr.forAllActivities(a -> { a.setWindowingMode(childWindowingMode); });
@@ -759,6 +766,7 @@
     private int applyDisplayAreaChanges(DisplayArea displayArea,
             WindowContainerTransaction.Change c) {
         final int[] effects = new int[1];
+        effects[0] = applyChanges(displayArea, c, null /* errorCallbackToken */);
 
         if ((c.getChangeMask()
                 & WindowContainerTransaction.Change.CHANGE_IGNORE_ORIENTATION_REQUEST) != 0) {
@@ -779,6 +787,27 @@
         return effects[0];
     }
 
+    private int applyTaskFragmentChanges(@NonNull TaskFragment taskFragment,
+            @NonNull WindowContainerTransaction.Change c, @Nullable IBinder errorCallbackToken) {
+        if (taskFragment.isEmbeddedTaskFragmentInPip()) {
+            // No override from organizer for embedded TaskFragment in a PIP Task.
+            return 0;
+        }
+
+        // When the TaskFragment is resized, we may want to create a change transition for it, for
+        // which we want to defer the surface update until we determine whether or not to start
+        // change transition.
+        mTmpBounds.set(taskFragment.getBounds());
+        taskFragment.deferOrganizedTaskFragmentSurfaceUpdate();
+        final int effects = applyChanges(taskFragment, c, errorCallbackToken);
+        if (taskFragment.shouldStartChangeTransition(mTmpBounds)) {
+            taskFragment.initializeChangeTransition(mTmpBounds);
+        }
+        taskFragment.continueOrganizedTaskFragmentSurfaceUpdate();
+        mTmpBounds.set(0, 0, 0, 0);
+        return effects;
+    }
+
     private int applyHierarchyOp(WindowContainerTransaction.HierarchyOp hop, int effects,
             int syncId, @Nullable Transition transition, boolean isInLockTaskMode,
             @NonNull CallerInfo caller, @Nullable IBinder errorCallbackToken,
@@ -1444,20 +1473,15 @@
     private int applyWindowContainerChange(WindowContainer wc,
             WindowContainerTransaction.Change c, @Nullable IBinder errorCallbackToken) {
         sanitizeWindowContainer(wc);
-        if (wc.asTaskFragment() != null && wc.asTaskFragment().isEmbeddedTaskFragmentInPip()) {
-            // No override from organizer for embedded TaskFragment in a PIP Task.
-            return 0;
+        if (wc.asDisplayArea() != null) {
+            return applyDisplayAreaChanges(wc.asDisplayArea(), c);
+        } else if (wc.asTask() != null) {
+            return applyTaskChanges(wc.asTask(), c);
+        } else if (wc.asTaskFragment() != null) {
+            return applyTaskFragmentChanges(wc.asTaskFragment(), c, errorCallbackToken);
+        } else {
+            return applyChanges(wc, c, errorCallbackToken);
         }
-
-        int effects = applyChanges(wc, c, errorCallbackToken);
-
-        if (wc instanceof DisplayArea) {
-            effects |= applyDisplayAreaChanges(wc.asDisplayArea(), c);
-        } else if (wc instanceof Task) {
-            effects |= applyTaskChanges(wc.asTask(), c);
-        }
-
-        return effects;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 5c5c703..86dd0b5 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1540,10 +1540,11 @@
             mWmService.makeWindowFreezingScreenIfNeededLocked(this);
 
             // If the orientation is changing, or we're starting or ending a drag resizing action,
-            // then we need to hold off on unfreezing the display until this window has been
-            // redrawn; to do that, we need to go through the process of getting informed by the
-            // application when it has finished drawing.
-            if (getOrientationChanging() || dragResizingChanged) {
+            // or we're resizing an embedded Activity, then we need to hold off on unfreezing the
+            // display until this window has been redrawn; to do that, we need to go through the
+            // process of getting informed by the application when it has finished drawing.
+            if (getOrientationChanging() || dragResizingChanged
+                    || isEmbeddedActivityResizeChanged()) {
                 if (dragResizingChanged) {
                     ProtoLog.v(WM_DEBUG_RESIZE,
                             "Resize start waiting for draw, "
@@ -4160,6 +4161,20 @@
         return mActivityRecord == null || mActivityRecord.isFullyTransparentBarAllowed(frame);
     }
 
+    /**
+     * Whether this window belongs to a resizing embedded activity.
+     */
+    private boolean isEmbeddedActivityResizeChanged() {
+        if (mActivityRecord == null || !isVisibleRequested()) {
+            // No need to update if the window is in the background.
+            return false;
+        }
+
+        final TaskFragment embeddedTaskFragment = mActivityRecord.getOrganizedTaskFragment();
+        return embeddedTaskFragment != null
+                && mDisplayContent.mChangingContainers.contains(embeddedTaskFragment);
+    }
+
     boolean isDragResizeChanged() {
         return mDragResizing != computeDragResizing();
     }
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index fe2d0be..d406e30 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -103,6 +103,7 @@
 import com.android.internal.util.EmergencyAffordanceManager;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.widget.ILockSettings;
+import com.android.internal.widget.LockSettingsInternal;
 import com.android.server.am.ActivityManagerService;
 import com.android.server.ambientcontext.AmbientContextManagerService;
 import com.android.server.appbinding.AppBindingService;
@@ -3019,6 +3020,14 @@
             t.traceEnd();
         }, t);
 
+        t.traceBegin("LockSettingsThirdPartyAppsStarted");
+        LockSettingsInternal lockSettingsInternal =
+            LocalServices.getService(LockSettingsInternal.class);
+        if (lockSettingsInternal != null) {
+            lockSettingsInternal.onThirdPartyAppsStarted();
+        }
+        t.traceEnd();
+
         t.traceBegin("StartSystemUI");
         try {
             startSystemUi(context, windowManagerF);
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java
index 923c3e3..9be370f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java
@@ -153,14 +153,8 @@
         assertUserAssignedToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
     }
 
-    @Test
-    public void testUnassignUserFromDisplay() {
-        testAssignUserToDisplay_displayAvailable();
-
-        mMediator.unassignUserFromDisplay(USER_ID);
-
-        assertNoUserAssignedToDisplay();
-    }
+    // TODO(b/244644281): when start & assign are merged, rename tests above and also call
+    // stopUserAndAssertState() at the end of them
 
     @Test
     public void testIsUserVisible_bgUserOnSecondaryDisplay() {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java
index 7af5f5d..7abdd9e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java
@@ -15,8 +15,6 @@
  */
 package com.android.server.pm;
 
-import static android.os.UserHandle.USER_SYSTEM;
-
 import static org.junit.Assert.assertThrows;
 
 import org.junit.Test;
@@ -34,6 +32,9 @@
         super(/* usersOnSecondaryDisplaysEnabled= */ false);
     }
 
+    // TODO(b/244644281): when start & assign are merged, rename tests below and also call
+    // stopUserAndAssertState() at the end of them
+
     @Test
     public void testAssignUserToDisplay_otherDisplay_currentUser() {
         mockCurrentUser(USER_ID);
@@ -59,15 +60,4 @@
         assertThrows(UnsupportedOperationException.class, () -> mMediator
                 .assignUserToDisplay(PROFILE_USER_ID, PARENT_USER_ID, SECONDARY_DISPLAY_ID));
     }
-
-    @Test
-    public void testUnassignUserFromDisplay_ignored() {
-        mockCurrentUser(USER_ID);
-
-        mMediator.unassignUserFromDisplay(USER_SYSTEM);
-        mMediator.unassignUserFromDisplay(USER_ID);
-        mMediator.unassignUserFromDisplay(OTHER_USER_ID);
-
-        assertNoUserAssignedToDisplay();
-    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
index 7b20092..e8be97d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
@@ -20,11 +20,11 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
+import static com.android.server.pm.UserManagerInternal.userAssignmentResultToString;
 import static com.android.server.pm.UserVisibilityMediator.INITIAL_CURRENT_USER_ID;
-import static com.android.server.pm.UserVisibilityMediator.START_USER_RESULT_FAILURE;
-import static com.android.server.pm.UserVisibilityMediator.START_USER_RESULT_SUCCESS_INVISIBLE;
-import static com.android.server.pm.UserVisibilityMediator.START_USER_RESULT_SUCCESS_VISIBLE;
-import static com.android.server.pm.UserVisibilityMediator.startUserResultToString;
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
@@ -100,79 +100,92 @@
 
     @Test
     public final void testStartUser_currentUser() {
-        int result = mMediator.startUser(USER_ID, USER_ID, FG, DEFAULT_DISPLAY);
-        assertStartUserResult(result, START_USER_RESULT_SUCCESS_VISIBLE);
+        int result = mMediator.startOnly(USER_ID, USER_ID, FG, DEFAULT_DISPLAY);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
 
         assertCurrentUser(USER_ID);
         assertIsCurrentUserOrRunningProfileOfCurrentUser(USER_ID);
         assertStartedProfileGroupIdOf(USER_ID, USER_ID);
+
+        stopUserAndAssertState(USER_ID);
     }
 
     @Test
     public final void testStartUser_currentUserSecondaryDisplay() {
-        int result = mMediator.startUser(USER_ID, USER_ID, FG, SECONDARY_DISPLAY_ID);
-        assertStartUserResult(result, START_USER_RESULT_FAILURE);
+        int result = mMediator.startOnly(USER_ID, USER_ID, FG, SECONDARY_DISPLAY_ID);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
 
         assertCurrentUser(INITIAL_CURRENT_USER_ID);
         assertIsNotCurrentUserOrRunningProfileOfCurrentUser(USER_ID);
         assertStartedProfileGroupIdOf(USER_ID, NO_PROFILE_GROUP_ID);
+
+        stopUserAndAssertState(USER_ID);
     }
 
     @Test
     public final void testStartUser_profileBg_parentStarted() {
         mockCurrentUser(PARENT_USER_ID);
 
-        int result = mMediator.startUser(PROFILE_USER_ID, PARENT_USER_ID, BG, DEFAULT_DISPLAY);
-        assertStartUserResult(result, START_USER_RESULT_SUCCESS_VISIBLE);
+        int result = mMediator.startOnly(PROFILE_USER_ID, PARENT_USER_ID, BG, DEFAULT_DISPLAY);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
 
         assertCurrentUser(PARENT_USER_ID);
         assertIsCurrentUserOrRunningProfileOfCurrentUser(PROFILE_USER_ID);
         assertStartedProfileGroupIdOf(PROFILE_USER_ID, PARENT_USER_ID);
-        assertIsStartedProfile(PROFILE_USER_ID);
+        assertProfileIsStarted(PROFILE_USER_ID);
+
+        stopUserAndAssertState(USER_ID);
     }
 
     @Test
     public final void testStartUser_profileBg_parentNotStarted() {
-        int result = mMediator.startUser(PROFILE_USER_ID, PARENT_USER_ID, BG, DEFAULT_DISPLAY);
-        assertStartUserResult(result, START_USER_RESULT_SUCCESS_INVISIBLE);
+        int result = mMediator.startOnly(PROFILE_USER_ID, PARENT_USER_ID, BG, DEFAULT_DISPLAY);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
 
         assertCurrentUser(INITIAL_CURRENT_USER_ID);
         assertIsNotCurrentUserOrRunningProfileOfCurrentUser(PROFILE_USER_ID);
         assertStartedProfileGroupIdOf(PROFILE_USER_ID, PARENT_USER_ID);
-        assertIsStartedProfile(PROFILE_USER_ID);
+        assertProfileIsStarted(PROFILE_USER_ID);
+
+        stopUserAndAssertState(USER_ID);
     }
 
     @Test
     public final void testStartUser_profileBg_secondaryDisplay() {
-        int result = mMediator.startUser(PROFILE_USER_ID, PARENT_USER_ID, BG, SECONDARY_DISPLAY_ID);
-        assertStartUserResult(result, START_USER_RESULT_FAILURE);
+        int result = mMediator.startOnly(PROFILE_USER_ID, PARENT_USER_ID, BG, SECONDARY_DISPLAY_ID);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
 
         assertCurrentUser(INITIAL_CURRENT_USER_ID);
         assertIsNotCurrentUserOrRunningProfileOfCurrentUser(PROFILE_USER_ID);
+
+        stopUserAndAssertState(USER_ID);
     }
 
     @Test
     public final void testStartUser_profileFg() {
-        int result = mMediator.startUser(PROFILE_USER_ID, PARENT_USER_ID, FG, DEFAULT_DISPLAY);
-        assertStartUserResult(result, START_USER_RESULT_FAILURE);
+        int result = mMediator.startOnly(PROFILE_USER_ID, PARENT_USER_ID, FG, DEFAULT_DISPLAY);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
 
         assertCurrentUser(INITIAL_CURRENT_USER_ID);
         assertIsNotCurrentUserOrRunningProfileOfCurrentUser(PROFILE_USER_ID);
-        assertStartedProfileGroupIdOf(PROFILE_USER_ID, NO_PROFILE_GROUP_ID);
+
+        stopUserAndAssertState(USER_ID);
     }
 
     @Test
     public final void testStartUser_profileFgSecondaryDisplay() {
-        int result = mMediator.startUser(PROFILE_USER_ID, PARENT_USER_ID, FG, SECONDARY_DISPLAY_ID);
+        int result = mMediator.startOnly(PROFILE_USER_ID, PARENT_USER_ID, FG, SECONDARY_DISPLAY_ID);
 
-        assertStartUserResult(result, START_USER_RESULT_FAILURE);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
         assertCurrentUser(INITIAL_CURRENT_USER_ID);
+
+        stopUserAndAssertState(USER_ID);
     }
 
     @Test
     public final void testGetStartedProfileGroupId_whenStartedWithNoProfileGroupId() {
-        int result = mMediator.startUser(USER_ID, NO_PROFILE_GROUP_ID, FG, DEFAULT_DISPLAY);
-        assertStartUserResult(result, START_USER_RESULT_SUCCESS_VISIBLE);
+        int result = mMediator.startOnly(USER_ID, NO_PROFILE_GROUP_ID, FG, DEFAULT_DISPLAY);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
 
         assertWithMessage("shit").that(mMediator.getStartedProfileGroupId(USER_ID))
                 .isEqualTo(USER_ID);
@@ -386,59 +399,78 @@
                 .isEqualTo(USER_ID);
     }
 
+    /**
+     * Stops the given user and assert the proper state is set.
+     *
+     * <p>This method should be called at the end of tests that starts a user, so it can test
+     * {@code stopUser()} as well (technically speaking, {@code stopUser()} should be tested on its
+     * own methods, but it depends on the user being started at first place, so pragmatically
+     * speaking, it's better to "reuse" such tests for both (start and stop)
+     */
+    private void stopUserAndAssertState(@UserIdInt int userId) {
+        mMediator.stopUser(userId);
+
+        assertUserIsStopped(userId);
+        assertNoUserAssignedToDisplay();
+    }
+
     // TODO(b/244644281): remove if start & assign are merged; if they aren't, add a note explaining
     // it's not meant to be used to test startUser() itself.
     protected void mockCurrentUser(@UserIdInt int userId) {
         Log.d(TAG, "mockCurrentUser(" + userId + ")");
-        int result = mMediator.startUser(userId, userId, FG, DEFAULT_DISPLAY);
-        if (result != START_USER_RESULT_SUCCESS_VISIBLE) {
+        int result = mMediator.startOnly(userId, userId, FG, DEFAULT_DISPLAY);
+        if (result != USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE) {
             throw new IllegalStateException("Failed to mock current user " + userId
-                    + ": mediator returned " + startUserResultToString(result));
+                    + ": mediator returned " + userAssignmentResultToString(result));
         }
     }
 
-    // TODO(b/244644281): remove if start & assign are merged; if they aren't, add a note explaining
+    // TODO(b/244644281): remove when start & assign are merged; or add a note explaining
     // it's not meant to be used to test startUser() itself.
     protected void startDefaultProfile() {
         mockCurrentUser(PARENT_USER_ID);
         Log.d(TAG, "starting default profile (" + PROFILE_USER_ID + ") in background after starting"
                 + " its parent (" + PARENT_USER_ID + ") on foreground");
 
-        int result = mMediator.startUser(PROFILE_USER_ID, PARENT_USER_ID, BG, DEFAULT_DISPLAY);
-        if (result != START_USER_RESULT_SUCCESS_VISIBLE) {
+        int result = mMediator.startOnly(PROFILE_USER_ID, PARENT_USER_ID, BG, DEFAULT_DISPLAY);
+        if (result != USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE) {
             throw new IllegalStateException("Failed to start profile user " + PROFILE_USER_ID
-                    + ": mediator returned " + startUserResultToString(result));
+                    + ": mediator returned " + userAssignmentResultToString(result));
         }
     }
 
-    // TODO(b/244644281): remove if start & assign are merged; if they aren't, add a note explaining
+    // TODO(b/244644281): remove when start & assign are merged; or add a note explaining
     // it's not meant to be used to test stopUser() itself.
     protected void stopDefaultProfile() {
         Log.d(TAG, "stopping default profile");
         mMediator.stopUser(PROFILE_USER_ID);
     }
 
-    // TODO(b/244644281): remove if start & assign are merged; if they aren't, add a note explaining
+    // TODO(b/244644281): remove when start & assign are merged; or add a note explaining
     // it's not meant to be used to test assignUserToDisplay() itself.
     protected final void assignUserToDisplay(@UserIdInt int userId, int displayId) {
         Log.d(TAG, "assignUserToDisplay(" + userId + ", " + displayId + ")");
-        int result = mMediator.startUser(userId, userId, BG, displayId);
-        if (result != START_USER_RESULT_SUCCESS_INVISIBLE) {
+        int result = mMediator.startOnly(userId, userId, BG, displayId);
+        if (result != USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE) {
             throw new IllegalStateException("Failed to startuser " + userId
-                    + " on background: mediator returned " + startUserResultToString(result));
+                    + " on background: mediator returned " + userAssignmentResultToString(result));
         }
         mMediator.assignUserToDisplay(userId, userId, displayId);
 
     }
 
+    // TODO(b/244644281): remove when start & assign are merged; or rename to
+    // assertNoUserAssignedToSecondaryDisplays
     protected final void assertNoUserAssignedToDisplay() {
-        assertWithMessage("uses on secondary displays")
+        assertWithMessage("users on secondary displays")
                 .that(mMediator.getUsersOnSecondaryDisplays())
                 .isEmpty();
     }
 
+    // TODO(b/244644281): remove when start & assign are merged; or rename to
+    // assertUserAssignedToSecondaryDisplay
     protected final void assertUserAssignedToDisplay(@UserIdInt int userId, int displayId) {
-        assertWithMessage("uses on secondary displays")
+        assertWithMessage("users on secondary displays")
                 .that(mMediator.getUsersOnSecondaryDisplays())
                 .containsExactly(userId, displayId);
     }
@@ -446,24 +478,44 @@
     private void assertCurrentUser(@UserIdInt int userId) {
         assertWithMessage("mediator.getCurrentUserId()").that(mMediator.getCurrentUserId())
                 .isEqualTo(userId);
+        if (userId != INITIAL_CURRENT_USER_ID) {
+            assertUserIsStarted(userId);
+        }
     }
 
-    private void assertIsStartedProfile(@UserIdInt int userId) {
+    private void assertUserIsStarted(@UserIdInt int userId) {
+        assertWithMessage("mediator.isStarted(%s)", userId).that(mMediator.isStartedUser(userId))
+                .isTrue();
+    }
+
+    private void assertUserIsStopped(@UserIdInt int userId) {
+        assertWithMessage("mediator.isStarted(%s)", userId).that(mMediator.isStartedUser(userId))
+                .isFalse();
+    }
+
+    private void assertProfileIsStarted(@UserIdInt int userId) {
         assertWithMessage("mediator.isStartedProfile(%s)", userId)
                 .that(mMediator.isStartedProfile(userId))
                 .isTrue();
+        assertUserIsStarted(userId);
     }
 
-    private void assertStartedProfileGroupIdOf(@UserIdInt int profileId, @UserIdInt int parentId) {
-        assertWithMessage("mediator.getStartedProfileGroupId(%s)", profileId)
-                .that(mMediator.getStartedProfileGroupId(profileId))
-                .isEqualTo(parentId);
+    private void assertStartedProfileGroupIdOf(@UserIdInt int userId,
+            @UserIdInt int profileGroupId) {
+        assertWithMessage("mediator.getStartedProfileGroupId(%s)", userId)
+                .that(mMediator.getStartedProfileGroupId(userId))
+                .isEqualTo(profileGroupId);
     }
 
-    private void assertIsCurrentUserOrRunningProfileOfCurrentUser(int userId) {
+    private void assertIsCurrentUserOrRunningProfileOfCurrentUser(@UserIdInt int userId) {
         assertWithMessage("mediator.isCurrentUserOrRunningProfileOfCurrentUser(%s)", userId)
                 .that(mMediator.isCurrentUserOrRunningProfileOfCurrentUser(userId))
                 .isTrue();
+        if (mMediator.getCurrentUserId() == userId) {
+            assertUserIsStarted(userId);
+        } else {
+            assertProfileIsStarted(userId);
+        }
     }
 
     private void assertIsNotCurrentUserOrRunningProfileOfCurrentUser(int userId) {
@@ -474,8 +526,8 @@
 
     private void assertStartUserResult(int actualResult, int expectedResult) {
         assertWithMessage("startUser() result (where %s=%s and %s=%s)",
-                actualResult, startUserResultToString(actualResult),
-                expectedResult, startUserResultToString(expectedResult))
+                actualResult, userAssignmentResultToString(actualResult),
+                expectedResult, userAssignmentResultToString(expectedResult))
                         .that(actualResult).isEqualTo(expectedResult);
     }
 }
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 6551bde..6349b21 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -107,6 +107,9 @@
 
     <queries>
         <package android:name="com.android.servicestests.apps.suspendtestapp" />
+        <intent>
+            <action android:name="android.media.browse.MediaBrowserService" />
+        </intent>
     </queries>
 
     <!-- Uses API introduced in O (26) -->
diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml
index 9052f58..9c7ce83 100644
--- a/services/tests/servicestests/AndroidTest.xml
+++ b/services/tests/servicestests/AndroidTest.xml
@@ -33,6 +33,7 @@
         <option name="test-file-name" value="SimpleServiceTestApp1.apk" />
         <option name="test-file-name" value="SimpleServiceTestApp2.apk" />
         <option name="test-file-name" value="SimpleServiceTestApp3.apk" />
+        <option name="test-file-name" value="FakeMediaApp.apk" />
     </target_preparer>
 
     <!-- Create place to store apks -->
diff --git a/services/tests/servicestests/res/xml/usertypes_test_full.xml b/services/tests/servicestests/res/xml/usertypes_test_full.xml
index 099ccbe..9568143 100644
--- a/services/tests/servicestests/res/xml/usertypes_test_full.xml
+++ b/services/tests/servicestests/res/xml/usertypes_test_full.xml
@@ -16,7 +16,8 @@
 <user-types>
     <full-type
         name='android.test.1'
-        max-allowed-per-parent='12' >
+        max-allowed-per-parent='12'
+        max-allowed='17' >
         <default-restrictions no_remove_user='true' no_bluetooth='true' />
         <badge-colors>
             <item res='@*android:color/profile_badge_1' />
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 80cee50..a49214f 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -39,6 +39,8 @@
 import static com.android.server.am.UserController.USER_START_MSG;
 import static com.android.server.am.UserController.USER_SWITCH_TIMEOUT_MSG;
 import static com.android.server.am.UserController.USER_VISIBILITY_CHANGED_MSG;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
 
 import static com.google.android.collect.Lists.newArrayList;
 import static com.google.android.collect.Sets.newHashSet;
@@ -100,6 +102,7 @@
 import com.android.server.SystemService;
 import com.android.server.am.UserState.KeyEvictedCallback;
 import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.UserManagerInternal.UserAssignmentResult;
 import com.android.server.pm.UserManagerService;
 import com.android.server.wm.WindowManagerService;
 
@@ -162,10 +165,15 @@
             USER_VISIBILITY_CHANGED_MSG,
             USER_CURRENT_MSG);
 
-    private static final Set<Integer> START_BACKGROUND_USER_MESSAGE_CODES = newHashSet(
+    private static final Set<Integer> START_INVISIBLE_BACKGROUND_USER_MESSAGE_CODES = newHashSet(
             USER_START_MSG,
             REPORT_LOCKED_BOOT_COMPLETE_MSG);
 
+    private static final Set<Integer> START_VISIBLE_BACKGROUND_USER_MESSAGE_CODES = newHashSet(
+            USER_START_MSG,
+            USER_VISIBILITY_CHANGED_MSG,
+            REPORT_LOCKED_BOOT_COMPLETE_MSG);
+
     @Before
     public void setUp() throws Exception {
         runWithDexmakerShareClassLoader(() -> {
@@ -184,6 +192,12 @@
             mockIsUsersOnSecondaryDisplaysEnabled(false);
             // All UserController params are set to default.
 
+            // Starts with a generic assumption that the user starts visible, but on tests where
+            // that's not the case, the test should call mockAssignUserToMainDisplay()
+            doReturn(UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE)
+                    .when(mInjector.mUserManagerInternalMock)
+                    .assignUserToDisplayOnStart(anyInt(), anyInt(), anyBoolean(), anyInt());
+
             mUserController = new UserController(mInjector);
             mUserController.setAllowUserUnlocking(true);
             setUpUser(TEST_USER_ID, NO_USERINFO_FLAGS);
@@ -211,16 +225,29 @@
 
     @Test
     public void testStartUser_background() {
+        mockAssignUserToMainDisplay(TEST_USER_ID, /* foreground= */ false,
+                USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
         boolean started = mUserController.startUser(TEST_USER_ID, /* foreground= */ false);
         assertWithMessage("startUser(%s, foreground=false)", TEST_USER_ID).that(started).isTrue();
         verify(mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt());
         verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean());
         verify(mInjector, never()).clearAllLockedTasks(anyString());
-        startBackgroundUserAssertions();
+        startBackgroundUserAssertions(/*visible= */ false);
         verifyUserAssignedToDisplay(TEST_USER_ID, Display.DEFAULT_DISPLAY);
     }
 
     @Test
+    public void testStartUser_displayAssignmentFailed() {
+        doReturn(UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE)
+                .when(mInjector.mUserManagerInternalMock)
+                .assignUserToDisplayOnStart(eq(TEST_USER_ID), anyInt(), eq(true), anyInt());
+
+        boolean started = mUserController.startUser(TEST_USER_ID, /* foreground= */ true);
+
+        assertWithMessage("startUser(%s, foreground=true)", TEST_USER_ID).that(started).isFalse();
+    }
+
+    @Test
     public void testStartUserOnSecondaryDisplay_defaultDisplay() {
         assertThrows(IllegalArgumentException.class, () -> mUserController
                 .startUserOnSecondaryDisplay(TEST_USER_ID, Display.DEFAULT_DISPLAY));
@@ -240,7 +267,7 @@
         verify(mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt());
         verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean());
         verify(mInjector, never()).clearAllLockedTasks(anyString());
-        startBackgroundUserAssertions();
+        startBackgroundUserAssertions(/*visible= */ true);
     }
 
     @Test
@@ -266,6 +293,8 @@
 
     @Test
     public void testStartPreCreatedUser_background() throws Exception {
+        mockAssignUserToMainDisplay(TEST_PRE_CREATED_USER_ID, /* foreground= */ false,
+                USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
         assertTrue(mUserController.startUser(TEST_PRE_CREATED_USER_ID, /* foreground= */ false));
         // Make sure no intents have been fired for pre-created users.
         assertTrue(mInjector.mSentIntents.isEmpty());
@@ -284,8 +313,6 @@
         // binder calls, but their side effects (in this case, that the user is stopped right away)
         assertWithMessage("wrong binder message calls").that(mInjector.mHandler.getMessageCodes())
                 .containsExactly(USER_START_MSG);
-
-        verifyUserNeverAssignedToDisplay();
     }
 
     private void startUserAssertions(
@@ -295,8 +322,10 @@
         assertEquals("Unexpected message sent", expectedMessageCodes, actualCodes);
     }
 
-    private void startBackgroundUserAssertions() {
-        startUserAssertions(START_BACKGROUND_USER_ACTIONS, START_BACKGROUND_USER_MESSAGE_CODES);
+    private void startBackgroundUserAssertions(boolean visible) {
+        startUserAssertions(START_BACKGROUND_USER_ACTIONS,
+                visible ? START_VISIBLE_BACKGROUND_USER_MESSAGE_CODES
+                        : START_INVISIBLE_BACKGROUND_USER_MESSAGE_CODES);
     }
 
     private void startForegroundUserAssertions() {
@@ -680,19 +709,24 @@
 
     @Test
     public void testStartProfile() throws Exception {
+        mockAssignUserToMainDisplay(TEST_PRE_CREATED_USER_ID, /* foreground= */ false,
+                USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
         setUpAndStartProfileInBackground(TEST_USER_ID1);
 
-        startBackgroundUserAssertions();
+        startBackgroundUserAssertions(/*visible= */ true);
         verifyUserAssignedToDisplay(TEST_USER_ID1, Display.DEFAULT_DISPLAY);
     }
 
     @Test
     public void testStartProfile_whenUsersOnSecondaryDisplaysIsEnabled() throws Exception {
+        mockAssignUserToMainDisplay(TEST_USER_ID1, /* foreground= */ false,
+                USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
+
         mockIsUsersOnSecondaryDisplaysEnabled(true);
 
         setUpAndStartProfileInBackground(TEST_USER_ID1);
 
-        startBackgroundUserAssertions();
+        startBackgroundUserAssertions(/*visible= */ true);
         verifyUserAssignedToDisplay(TEST_USER_ID1, Display.DEFAULT_DISPLAY);
     }
 
@@ -949,22 +983,29 @@
         when(mInjector.isUsersOnSecondaryDisplaysEnabled()).thenReturn(value);
     }
 
+    private void mockAssignUserToMainDisplay(@UserIdInt int userId, boolean foreground,
+            @UserAssignmentResult int result) {
+        when(mInjector.mUserManagerInternalMock.assignUserToDisplayOnStart(eq(userId),
+                /* profileGroupId= */ anyInt(), eq(foreground), eq(Display.DEFAULT_DISPLAY)))
+                        .thenReturn(result);
+    }
+
     private void verifyUserAssignedToDisplay(@UserIdInt int userId, int displayId) {
-        verify(mInjector.getUserManagerInternal()).assignUserToDisplay(eq(userId), anyInt(),
+        verify(mInjector.getUserManagerInternal()).assignUserToDisplayOnStart(eq(userId), anyInt(),
                 anyBoolean(), eq(displayId));
     }
 
     private void verifyUserNeverAssignedToDisplay() {
-        verify(mInjector.getUserManagerInternal(), never()).assignUserToDisplay(anyInt(), anyInt(),
-                anyBoolean(), anyInt());
+        verify(mInjector.getUserManagerInternal(), never()).assignUserToDisplayOnStart(anyInt(),
+                anyInt(), anyBoolean(), anyInt());
     }
 
     private void verifyUserUnassignedFromDisplay(@UserIdInt int userId) {
-        verify(mInjector.getUserManagerInternal()).unassignUserFromDisplay(userId);
+        verify(mInjector.getUserManagerInternal()).unassignUserFromDisplayOnStop(userId);
     }
 
     private void verifyUserUnassignedFromDisplayNeverCalled(@UserIdInt int userId) {
-        verify(mInjector.getUserManagerInternal(), never()).unassignUserFromDisplay(userId);
+        verify(mInjector.getUserManagerInternal(), never()).unassignUserFromDisplayOnStop(userId);
     }
 
     private void verifySystemUserVisibilityChangedNotified(boolean visible) {
diff --git a/services/tests/servicestests/src/com/android/server/media/MediaButtonReceiverHolderTest.java b/services/tests/servicestests/src/com/android/server/media/MediaButtonReceiverHolderTest.java
new file mode 100644
index 0000000..1c4ee69
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/media/MediaButtonReceiverHolderTest.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.google.common.truth.Truth;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class MediaButtonReceiverHolderTest {
+
+    @Test
+    public void createMediaButtonReceiverHolder_resolvesNullComponentName() {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+        PendingIntent pi = PendingIntent.getBroadcast(context, /* requestCode= */ 0, intent,
+                PendingIntent.FLAG_IMMUTABLE);
+        MediaButtonReceiverHolder a = MediaButtonReceiverHolder.create(/* userId= */ 0, pi,
+                context.getPackageName());
+        Truth.assertWithMessage("Component name must match PendingIntent creator package.").that(
+                a.getComponentName()).isNull();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/media/OWNERS b/services/tests/servicestests/src/com/android/server/media/OWNERS
new file mode 100644
index 0000000..55ffde2
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/media/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 137631
+include platform/frameworks/av:/media/janitors/media_solutions_OWNERS
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
index 6fe33ee..d7c1e37 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
@@ -355,6 +355,7 @@
         UserTypeDetails details = builders.get(userTypeFull).createUserTypeDetails();
         assertEquals(UNLIMITED_NUMBER_OF_USERS, details.getMaxAllowedPerParent());
         assertFalse(details.isEnabled());
+        assertEquals(17, details.getMaxAllowed());
         assertTrue(UserRestrictionsUtils.areEqual(
                 makeRestrictionsBundle("no_remove_user", "no_bluetooth"),
                 details.getDefaultRestrictions()));
diff --git a/services/tests/servicestests/test-apps/FakeMediaApp/Android.bp b/services/tests/servicestests/test-apps/FakeMediaApp/Android.bp
new file mode 100644
index 0000000..a4041b7
--- /dev/null
+++ b/services/tests/servicestests/test-apps/FakeMediaApp/Android.bp
@@ -0,0 +1,37 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test_helper_app {
+    name: "FakeMediaApp",
+
+    sdk_version: "current",
+
+    srcs: ["**/*.java"],
+
+    dex_preopt: {
+        enabled: false,
+    },
+    optimize: {
+        enabled: false,
+    },
+}
diff --git a/services/tests/servicestests/test-apps/FakeMediaApp/AndroidManifest.xml b/services/tests/servicestests/test-apps/FakeMediaApp/AndroidManifest.xml
new file mode 100644
index 0000000..c08ee7a
--- /dev/null
+++ b/services/tests/servicestests/test-apps/FakeMediaApp/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.servicestests.apps.fakemediaapp">
+
+    <application>
+        <receiver
+            android:name=".FakeMediaButtonBroadcastReceiver"
+            android:enabled="true"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MEDIA_BUTTON" />
+            </intent-filter>
+        </receiver>
+    </application>
+
+</manifest>
diff --git a/services/tests/servicestests/test-apps/FakeMediaApp/OWNERS b/services/tests/servicestests/test-apps/FakeMediaApp/OWNERS
new file mode 100644
index 0000000..55ffde2
--- /dev/null
+++ b/services/tests/servicestests/test-apps/FakeMediaApp/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 137631
+include platform/frameworks/av:/media/janitors/media_solutions_OWNERS
\ No newline at end of file
diff --git a/services/tests/servicestests/test-apps/FakeMediaApp/src/FakeMediaButtonBroadcastReceiver.java b/services/tests/servicestests/test-apps/FakeMediaApp/src/FakeMediaButtonBroadcastReceiver.java
new file mode 100644
index 0000000..41f0cf5
--- /dev/null
+++ b/services/tests/servicestests/test-apps/FakeMediaApp/src/FakeMediaButtonBroadcastReceiver.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.servicestests.apps.fakemediaapp;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+public class FakeMediaButtonBroadcastReceiver extends BroadcastReceiver {
+
+    private static final String TAG = "FakeMediaButtonBroadcastReceiver";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Log.v(TAG, "onReceive not expected");
+    }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 668345d..d54d1fe 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -7605,6 +7605,65 @@
     }
 
     @Test
+    public void testAddAutomaticZenRule_systemCallTakesPackageFromOwner() throws Exception {
+        mService.isSystemUid = true;
+        ZenModeHelper mockZenModeHelper = mock(ZenModeHelper.class);
+        when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
+                .thenReturn(true);
+        mService.setZenHelper(mockZenModeHelper);
+        ComponentName owner = new ComponentName("android", "ProviderName");
+        ZenPolicy zenPolicy = new ZenPolicy.Builder().allowAlarms(true).build();
+        boolean isEnabled = true;
+        AutomaticZenRule rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class),
+                zenPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, isEnabled);
+        mBinderService.addAutomaticZenRule(rule, "com.android.settings");
+
+        // verify that zen mode helper gets passed in a package name of "android"
+        verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule), anyString());
+    }
+
+    @Test
+    public void testAddAutomaticZenRule_systemAppIdCallTakesPackageFromOwner() throws Exception {
+        // The multi-user case: where the calling uid doesn't match the system uid, but the calling
+        // *appid* is the system.
+        mService.isSystemUid = false;
+        mService.isSystemAppId = true;
+        ZenModeHelper mockZenModeHelper = mock(ZenModeHelper.class);
+        when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
+                .thenReturn(true);
+        mService.setZenHelper(mockZenModeHelper);
+        ComponentName owner = new ComponentName("android", "ProviderName");
+        ZenPolicy zenPolicy = new ZenPolicy.Builder().allowAlarms(true).build();
+        boolean isEnabled = true;
+        AutomaticZenRule rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class),
+                zenPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, isEnabled);
+        mBinderService.addAutomaticZenRule(rule, "com.android.settings");
+
+        // verify that zen mode helper gets passed in a package name of "android"
+        verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule), anyString());
+    }
+
+    @Test
+    public void testAddAutomaticZenRule_nonSystemCallTakesPackageFromArg() throws Exception {
+        mService.isSystemUid = false;
+        mService.isSystemAppId = false;
+        ZenModeHelper mockZenModeHelper = mock(ZenModeHelper.class);
+        when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
+                .thenReturn(true);
+        mService.setZenHelper(mockZenModeHelper);
+        ComponentName owner = new ComponentName("android", "ProviderName");
+        ZenPolicy zenPolicy = new ZenPolicy.Builder().allowAlarms(true).build();
+        boolean isEnabled = true;
+        AutomaticZenRule rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class),
+                zenPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, isEnabled);
+        mBinderService.addAutomaticZenRule(rule, "another.package");
+
+        // verify that zen mode helper gets passed in the package name from the arg, not the owner
+        verify(mockZenModeHelper).addAutomaticZenRule(
+                eq("another.package"), eq(rule), anyString());
+    }
+
+    @Test
     public void testAreNotificationsEnabledForPackage() throws Exception {
         mBinderService.areNotificationsEnabledForPackage(mContext.getPackageName(),
                 mUid);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
index 8cf74fb..61a6985 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
@@ -32,6 +32,7 @@
 public class TestableNotificationManagerService extends NotificationManagerService {
     int countSystemChecks = 0;
     boolean isSystemUid = true;
+    boolean isSystemAppId = true;
     int countLogSmartSuggestionsVisible = 0;
     Set<Integer> mChannelToastsSent = new HashSet<>();
 
@@ -58,6 +59,12 @@
     }
 
     @Override
+    protected boolean isCallingAppIdSystem() {
+        countSystemChecks++;
+        return isSystemUid || isSystemAppId;
+    }
+
+    @Override
     protected boolean isCallerSystemOrPhone() {
         countSystemChecks++;
         return isSystemUid;
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 ba61980..49edde5 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -1672,6 +1672,36 @@
     }
 
     @Test
+    public void testAddAutomaticZenRule_claimedSystemOwner() {
+        // Make sure anything that claims to have a "system" owner but not actually part of the
+        // system package still gets limited on number of rules
+        for (int i = 0; i < RULE_LIMIT_PER_PACKAGE; i++) {
+            ScheduleInfo si = new ScheduleInfo();
+            si.startHour = i;
+            AutomaticZenRule zenRule = new AutomaticZenRule("name" + i,
+                    new ComponentName("android", "ScheduleConditionProvider" + i),
+                    null, // configuration activity
+                    ZenModeConfig.toScheduleConditionId(si),
+                    new ZenPolicy.Builder().build(),
+                    NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+            String id = mZenModeHelperSpy.addAutomaticZenRule("pkgname", zenRule, "test");
+            assertNotNull(id);
+        }
+        try {
+            AutomaticZenRule zenRule = new AutomaticZenRule("name",
+                    new ComponentName("android", "ScheduleConditionProviderFinal"),
+                    null, // configuration activity
+                    ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
+                    new ZenPolicy.Builder().build(),
+                    NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+            String id = mZenModeHelperSpy.addAutomaticZenRule("pkgname", zenRule, "test");
+            fail("allowed too many rules to be created");
+        } catch (IllegalArgumentException e) {
+            // yay
+        }
+    }
+
+    @Test
     public void testAddAutomaticZenRule_CA() {
         AutomaticZenRule zenRule = new AutomaticZenRule("name",
                 null,
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 07dba00..fc1989e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -1262,6 +1262,26 @@
     }
 
     @Test
+    public void testRecycleTaskWakeUpWhenDreaming() {
+        doNothing().when(mWm.mAtmService.mTaskSupervisor).wakeUp(anyString());
+        doReturn(true).when(mWm.mAtmService).isDreaming();
+        final ActivityStarter starter = prepareStarter(0 /* flags */);
+        final ActivityRecord target = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        starter.mStartActivity = target;
+        target.mVisibleRequested = false;
+        target.setTurnScreenOn(true);
+        // Assume the flag was consumed by relayout.
+        target.setCurrentLaunchCanTurnScreenOn(false);
+        startActivityInner(starter, target, null /* source */, null /* options */,
+                null /* inTask */, null /* inTaskFragment */);
+        // The flag should be set again when resuming (from recycleTask) the target as top.
+        assertTrue(target.currentLaunchCanTurnScreenOn());
+        // In real case, dream activity has a higher priority (TaskDisplayArea#getPriority) that
+        // will be put at a higher z-order. So it relies on wakeUp() to be dismissed.
+        verify(mWm.mAtmService.mTaskSupervisor).wakeUp(anyString());
+    }
+
+    @Test
     public void testTargetTaskInSplitScreen() {
         final ActivityStarter starter =
                 prepareStarter(FLAG_ACTIVITY_LAUNCH_ADJACENT, false /* mockGetRootTask */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 83f1789..3ff2c0e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -118,10 +118,13 @@
         doReturn(true).when(mTaskFragment).isVisibleRequested();
 
         clearInvocations(mTransaction);
+        mTaskFragment.deferOrganizedTaskFragmentSurfaceUpdate();
         mTaskFragment.setBounds(endBounds);
+        assertTrue(mTaskFragment.shouldStartChangeTransition(startBounds));
+        mTaskFragment.initializeChangeTransition(startBounds);
+        mTaskFragment.continueOrganizedTaskFragmentSurfaceUpdate();
 
         // Surface reset when prepare transition.
-        verify(mTaskFragment).initializeChangeTransition(startBounds);
         verify(mTransaction).setPosition(mLeash, 0, 0);
         verify(mTransaction).setWindowCrop(mLeash, 0, 0);
 
@@ -166,7 +169,7 @@
 
         mTaskFragment.setBounds(endBounds);
 
-        verify(mTaskFragment, never()).initializeChangeTransition(any());
+        assertFalse(mTaskFragment.shouldStartChangeTransition(startBounds));
     }
 
     /**
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 6bd3412..1b888f6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -95,6 +95,8 @@
 import android.view.InsetsState;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
+import android.window.ITaskFragmentOrganizer;
+import android.window.TaskFragmentOrganizer;
 
 import androidx.test.filters.SmallTest;
 
@@ -800,6 +802,39 @@
     }
 
     @Test
+    public void testEmbeddedActivityResizing_clearAllDrawn() {
+        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+        mAtm.mTaskFragmentOrganizerController.registerOrganizer(
+                ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder()));
+        final Task task = createTask(mDisplayContent);
+        final TaskFragment embeddedTf = createTaskFragmentWithEmbeddedActivity(task, organizer);
+        final ActivityRecord embeddedActivity = embeddedTf.getTopMostActivity();
+        final WindowState win = createWindow(null /* parent */, TYPE_APPLICATION, embeddedActivity,
+                "App window");
+        doReturn(true).when(embeddedActivity).isVisible();
+        embeddedActivity.mVisibleRequested = true;
+        makeWindowVisible(win);
+        win.mLayoutSeq = win.getDisplayContent().mLayoutSeq;
+        // Set the bounds twice:
+        // 1. To make sure there is no orientation change after #reportResized, which can also cause
+        // #clearAllDrawn.
+        // 2. Make #isLastConfigReportedToClient to be false after #reportResized, so it can process
+        // to check if we need redraw.
+        embeddedTf.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        embeddedTf.setBounds(0, 0, 1000, 2000);
+        win.reportResized();
+        embeddedTf.setBounds(500, 0, 1000, 2000);
+
+        // Clear all drawn when the embedded TaskFragment is in mDisplayContent.mChangingContainers.
+        win.updateResizingWindowIfNeeded();
+        verify(embeddedActivity, never()).clearAllDrawn();
+
+        mDisplayContent.mChangingContainers.add(embeddedTf);
+        win.updateResizingWindowIfNeeded();
+        verify(embeddedActivity).clearAllDrawn();
+    }
+
+    @Test
     public void testCantReceiveTouchWhenAppTokenHiddenRequested() {
         final WindowState win0 = createWindow(null, TYPE_APPLICATION, "win0");
         win0.mActivityRecord.mVisibleRequested = false;
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index 86f877f..72f6cc3 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -504,6 +504,15 @@
     }
 
     @Override
+    public boolean hasDevicePermissionWithIdentity(UsbDevice device, String packageName,
+            int pid, int uid) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+
+        final int userId = UserHandle.getUserId(uid);
+        return getPermissionsForUser(userId).hasPermission(device, packageName, pid, uid);
+    }
+
+    @Override
     public boolean hasAccessoryPermission(UsbAccessory accessory) {
         final int uid = Binder.getCallingUid();
         final int pid = Binder.getCallingPid();
@@ -518,6 +527,14 @@
     }
 
     @Override
+    public boolean hasAccessoryPermissionWithIdentity(UsbAccessory accessory, int pid, int uid) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+
+        final int userId = UserHandle.getUserId(uid);
+        return getPermissionsForUser(userId).hasPermission(accessory, pid, uid);
+    }
+
+    @Override
     public void requestDevicePermission(UsbDevice device, String packageName, PendingIntent pi) {
         final int uid = Binder.getCallingUid();
         final int pid = Binder.getCallingPid();
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamManager.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamManager.java
index f921118..d5eea1f 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamManager.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamManager.java
@@ -163,7 +163,7 @@
 
     private static class SingleAudioStreamCopyTask implements Callable<Void> {
         // TODO: Make this buffer size customizable from updateState()
-        private static final int COPY_BUFFER_LENGTH = 1_024;
+        private static final int COPY_BUFFER_LENGTH = 2_560;
 
         private final String mStreamTaskId;
         private final ParcelFileDescriptor mAudioSource;
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 1fc54d6..0721c28 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -19,7 +19,6 @@
 import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD;
 import static android.Manifest.permission.RECORD_AUDIO;
 import static android.service.attention.AttentionService.PROXIMITY_UNKNOWN;
-import static android.service.voice.HotwordDetectedResult.EXTRA_PROXIMITY_METERS;
 import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_EXTERNAL;
 import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_MICROPHONE;
 import static android.service.voice.HotwordDetectionService.ENABLE_PROXIMITY_RESULT;
@@ -206,7 +205,7 @@
     @Nullable AttentionManagerInternal mAttentionManagerInternal = null;
 
     final AttentionManagerInternal.ProximityUpdateCallbackInternal mProximityCallbackInternal =
-            this::setProximityMeters;
+            this::setProximityValue;
 
 
     volatile HotwordDetectionServiceIdentity mIdentity;
@@ -504,7 +503,7 @@
                         mSoftwareCallback.onError();
                         return;
                     }
-                    saveProximityMetersToBundle(result);
+                    saveProximityValueToBundle(result);
                     HotwordDetectedResult newResult;
                     try {
                         newResult = mHotwordAudioStreamManager.startCopyingAudioStreams(result);
@@ -639,7 +638,7 @@
                         externalCallback.onError(CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION);
                         return;
                     }
-                    saveProximityMetersToBundle(result);
+                    saveProximityValueToBundle(result);
                     HotwordDetectedResult newResult;
                     try {
                         newResult = mHotwordAudioStreamManager.startCopyingAudioStreams(result);
@@ -1242,15 +1241,15 @@
         });
     }
 
-    private void saveProximityMetersToBundle(HotwordDetectedResult result) {
+    private void saveProximityValueToBundle(HotwordDetectedResult result) {
         synchronized (mLock) {
             if (result != null && mProximityMeters != PROXIMITY_UNKNOWN) {
-                result.getExtras().putDouble(EXTRA_PROXIMITY_METERS, mProximityMeters);
+                result.setProximity(mProximityMeters);
             }
         }
     }
 
-    private void setProximityMeters(double proximityMeters) {
+    private void setProximityValue(double proximityMeters) {
         synchronized (mLock) {
             mProximityMeters = proximityMeters;
         }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 5d1901d..0a660b0 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -639,7 +639,7 @@
     private void logDetectorCreateEventIfNeeded(IHotwordRecognitionStatusCallback callback,
             int detectorType, boolean isCreated, int voiceInteractionServiceUid) {
         if (callback != null) {
-            HotwordMetricsLogger.writeDetectorCreateEvent(detectorType, true,
+            HotwordMetricsLogger.writeDetectorCreateEvent(detectorType, isCreated,
                     voiceInteractionServiceUid);
         }
 
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index d473c6a..541573c 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -14957,21 +14957,132 @@
      * @return a Pair of (major version, minor version) or (-1,-1) if unknown.
      *
      * @hide
+     *
+     * @deprecated Use {@link #getHalVersion} instead.
      */
+    @Deprecated
     @UnsupportedAppUsage
     @TestApi
     public Pair<Integer, Integer> getRadioHalVersion() {
+        return getHalVersion(HAL_SERVICE_RADIO);
+    }
+
+    /** @hide */
+    public static final int HAL_SERVICE_RADIO = 0;
+
+    /**
+     * HAL service type that supports the HAL APIs implementation of IRadioData
+     * {@link RadioDataProxy}
+     * @hide
+     */
+    @TestApi
+    public static final int HAL_SERVICE_DATA = 1;
+
+    /**
+     * HAL service type that supports the HAL APIs implementation of IRadioMessaging
+     * {@link RadioMessagingProxy}
+     * @hide
+     */
+    @TestApi
+    public static final int HAL_SERVICE_MESSAGING = 2;
+
+    /**
+     * HAL service type that supports the HAL APIs implementation of IRadioModem
+     * {@link RadioModemProxy}
+     * @hide
+     */
+    @TestApi
+    public static final int HAL_SERVICE_MODEM = 3;
+
+    /**
+     * HAL service type that supports the HAL APIs implementation of IRadioNetwork
+     * {@link RadioNetworkProxy}
+     * @hide
+     */
+    @TestApi
+    public static final int HAL_SERVICE_NETWORK = 4;
+
+    /**
+     * HAL service type that supports the HAL APIs implementation of IRadioSim
+     * {@link RadioSimProxy}
+     * @hide
+     */
+    @TestApi
+    public static final int HAL_SERVICE_SIM = 5;
+
+    /**
+     * HAL service type that supports the HAL APIs implementation of IRadioVoice
+     * {@link RadioVoiceProxy}
+     * @hide
+     */
+    @TestApi
+    public static final int HAL_SERVICE_VOICE = 6;
+
+    /**
+     * HAL service type that supports the HAL APIs implementation of IRadioIms
+     * {@link RadioImsProxy}
+     * @hide
+     */
+    @TestApi
+    public static final int HAL_SERVICE_IMS = 7;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"HAL_SERVICE_"},
+            value = {
+                    HAL_SERVICE_RADIO,
+                    HAL_SERVICE_DATA,
+                    HAL_SERVICE_MESSAGING,
+                    HAL_SERVICE_MODEM,
+                    HAL_SERVICE_NETWORK,
+                    HAL_SERVICE_SIM,
+                    HAL_SERVICE_VOICE,
+                    HAL_SERVICE_IMS,
+            })
+    public @interface HalService {}
+
+    /**
+     * The HAL Version indicating that the version is unknown or invalid.
+     * @hide
+     */
+    @TestApi
+    public static final Pair HAL_VERSION_UNKNOWN = new Pair(-1, -1);
+
+    /**
+     * The HAL Version indicating that the version is unsupported.
+     * @hide
+     */
+    @TestApi
+    public static final Pair HAL_VERSION_UNSUPPORTED = new Pair(-2, -2);
+
+    /**
+     * Retrieve the HAL Version of a specific service for this device.
+     *
+     * Get the HAL version for a specific HAL interface for test purposes.
+     *
+     * @param halService the service id to query.
+     * @return a Pair of (major version, minor version), HAL_VERSION_UNKNOWN if unknown
+     * or HAL_VERSION_UNSUPPORTED if unsupported.
+     *
+     * @hide
+     */
+    @TestApi
+    public @NonNull Pair<Integer, Integer> getHalVersion(@HalService int halService) {
         try {
             ITelephony service = getITelephony();
             if (service != null) {
-                int version = service.getRadioHalVersion();
-                if (version == -1) return new Pair<Integer, Integer>(-1, -1);
-                return new Pair<Integer, Integer>(version / 100, version % 100);
+                int version = service.getHalVersion(halService);
+                if (version != -1) {
+                    return new Pair<Integer, Integer>(version / 100, version % 100);
+                }
+            } else {
+                throw new IllegalStateException("telephony service is null.");
             }
         } catch (RemoteException e) {
-            Log.e(TAG, "getRadioHalVersion() RemoteException", e);
+            Log.e(TAG, "getHalVersion() RemoteException", e);
+            e.rethrowAsRuntimeException();
         }
-        return new Pair<Integer, Integer>(-1, -1);
+        return HAL_VERSION_UNKNOWN;
     }
 
     /**
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 648866b..abf4cde 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2159,6 +2159,12 @@
     int getRadioHalVersion();
 
     /**
+     * Get the HAL Version of a specific service
+     * encoded as 100 * MAJOR_VERSION + MINOR_VERSION or -1 if unknown
+     */
+    int getHalVersion(int service);
+
+    /**
      * Get the current calling package name.
      */
     String getCurrentPackageName();