Merge "Revert "Use OpenJDK 21 in the GCE images"" into main
diff --git a/Android.bp b/Android.bp
index 5ff75aa..64ad350 100644
--- a/Android.bp
+++ b/Android.bp
@@ -81,10 +81,10 @@
         },
     },
     cflags: [
-        "-Werror",
-        "-Wall",
-        "-D_FILE_OFFSET_BITS=64",
         "-DNODISCARD_EXPECTED=true",
+        "-D_FILE_OFFSET_BITS=64",
+        "-Wall",
+        "-Werror",
         "-Wno-error=unused-result", // TODO(b/314526051): Fix Result<> uses
     ],
     apex_available: [
@@ -99,8 +99,8 @@
     config_namespace: "cvdhost",
     bool_variables: ["enforce_mac80211_hwsim"],
     value_variables: [
-        "default_userdata_fs_type",
         "board_f2fs_blocksize",
+        "default_userdata_fs_type",
     ],
     properties: ["cflags"],
 }
diff --git a/Android.mk b/Android.mk
index 3ced0d0..f26e65a 100644
--- a/Android.mk
+++ b/Android.mk
@@ -52,8 +52,3 @@
 include $(LOCAL_PATH)/host_package.mk
 
 endif
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/apex/com.google.cf.rild/Android.bp b/apex/com.google.cf.rild/Android.bp
index 5a256bd..44bbeae 100644
--- a/apex/com.google.cf.rild/Android.bp
+++ b/apex/com.google.cf.rild/Android.bp
@@ -45,8 +45,10 @@
         "libcuttlefish-ril-2",
     ],
     prebuilts: [
+        "android.hardware.telephony.carrierlock.prebuilt.xml",
         "android.hardware.telephony.gsm.prebuilt.xml",
         "android.hardware.telephony.ims.prebuilt.xml",
+        "android.hardware.telephony.ims.singlereg.prebuilt.xml",
         "com.google.cf.rild.rc",
         "com.google.cf.rild.xml",
     ],
diff --git a/apex/com.google.cf.wifi/Android.bp b/apex/com.google.cf.wifi/Android.bp
index cd6d7a6..2d0836c 100644
--- a/apex/com.google.cf.wifi/Android.bp
+++ b/apex/com.google.cf.wifi/Android.bp
@@ -36,7 +36,6 @@
     key: "com.google.cf.apex.key",
     certificate: ":com.google.cf.apex.certificate",
     file_contexts: "file_contexts",
-    use_vndk_as_stable: true,
     updatable: false,
     // Install the apex in /vendor/apex
     soc_specific: true,
diff --git a/apex/com.google.cf.wifi/com.google.cf.wifi.rc b/apex/com.google.cf.wifi/com.google.cf.wifi.rc
index 11fbcf0..18d2f98 100644
--- a/apex/com.google.cf.wifi/com.google.cf.wifi.rc
+++ b/apex/com.google.cf.wifi/com.google.cf.wifi.rc
@@ -3,7 +3,7 @@
     oneshot
 
 # For legacy wifi without openwrt
-service setup_wifi /apex/com.google.cf.wifi/bin/setup_wifi
+service setup_wifi /apex/com.google.cf.wifi/bin/setup_wifi --interface=${ro.vendor.virtwifi.port}
     user root
     oneshot
 
diff --git a/build/Android.bp b/build/Android.bp
index 1e69ce0..d004e80 100644
--- a/build/Android.bp
+++ b/build/Android.bp
@@ -191,6 +191,7 @@
     "vulkan.pastel",
     "automotive_vsock_proxy",
     "vhost_device_vsock",
+    "vhal_proxy_server",
 ]
 
 cvd_openwrt_images = [
diff --git a/common/libs/utils/files.cpp b/common/libs/utils/files.cpp
index 23b36dd..53c1c37 100644
--- a/common/libs/utils/files.cpp
+++ b/common/libs/utils/files.cpp
@@ -82,6 +82,66 @@
   return (follow_symlinks ? stat : lstat)(path.c_str(), &st) == 0;
 }
 
+Result<dev_t> FileDeviceId(const std::string& path) {
+  struct stat out;
+  CF_EXPECTF(
+      stat(path.c_str(), &out) == 0,
+      "stat() failed trying to retrieve device ID information for \"{}\" "
+      "with error: {}",
+      path, strerror(errno));
+  return out.st_dev;
+}
+
+Result<bool> CanHardLink(const std::string& source,
+                         const std::string& destination) {
+  return CF_EXPECT(FileDeviceId(source)) ==
+         CF_EXPECT(FileDeviceId(destination));
+}
+
+Result<ino_t> FileInodeNumber(const std::string& path) {
+  struct stat out;
+  CF_EXPECTF(
+      stat(path.c_str(), &out) == 0,
+      "stat() failed trying to retrieve inode num information for \"{}\" "
+      "with error: {}",
+      path, strerror(errno));
+  return out.st_ino;
+}
+
+Result<bool> AreHardLinked(const std::string& source,
+                           const std::string& destination) {
+  return (CF_EXPECT(FileDeviceId(source)) ==
+          CF_EXPECT(FileDeviceId(destination))) &&
+         (CF_EXPECT(FileInodeNumber(source)) ==
+          CF_EXPECT(FileInodeNumber(destination)));
+}
+
+Result<std::string> CreateHardLink(const std::string& target,
+                                   const std::string& hardlink,
+                                   const bool overwrite_existing) {
+  if (FileExists(hardlink)) {
+    if (CF_EXPECT(AreHardLinked(target, hardlink))) {
+      return hardlink;
+    }
+    if (!overwrite_existing) {
+      return CF_ERRF(
+          "Cannot hardlink from \"{}\" to \"{}\", the second file already "
+          "exists and is not hardlinked to the first",
+          target, hardlink);
+    }
+    LOG(WARNING) << "Overwriting existing file \"" << hardlink << "\" with \""
+                 << target << "\" from the cache";
+    CF_EXPECTF(unlink(hardlink.c_str()) == 0,
+               "Failed to unlink \"{}\" with error: {}", hardlink,
+               strerror(errno));
+  }
+  CF_EXPECTF(link(target.c_str(), hardlink.c_str()) == 0,
+             "link() failed trying to create hardlink from \"{}\" to \"{}\" "
+             "with error: {}",
+             target, hardlink, strerror(errno));
+  return hardlink;
+}
+
 bool FileHasContent(const std::string& path) {
   return FileSize(path) > 0;
 }
@@ -92,6 +152,9 @@
   CF_EXPECTF(dir != nullptr, "Could not read from dir \"{}\"", path);
   struct dirent* ent{};
   while ((ent = readdir(dir.get()))) {
+    if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) {
+      continue;
+    }
     ret.emplace_back(ent->d_name);
   }
   return ret;
@@ -150,7 +213,7 @@
   }
 
   if (chown(path.c_str(), -1, groupId) != 0) {
-    return CF_ERRNO("Feailed to set group for path: "
+    return CF_ERRNO("Failed to set group for path: "
                     << path << ", " << group_name << ", " << strerror(errno));
   }
 
@@ -535,9 +598,6 @@
     const std::function<bool(const std::string&)>& callback) {
   const auto files = CF_EXPECT(DirectoryContents(dir));
   for (const auto& filename : files) {
-    if (filename == "." || filename == "..") {
-      continue;
-    }
     auto file_path = dir + "/";
     file_path.append(filename);
     callback(file_path);
diff --git a/common/libs/utils/files.h b/common/libs/utils/files.h
index 6e423cd..e47ad43 100644
--- a/common/libs/utils/files.h
+++ b/common/libs/utils/files.h
@@ -27,6 +27,15 @@
 
 namespace cuttlefish {
 bool FileExists(const std::string& path, bool follow_symlinks = true);
+Result<dev_t> FileDeviceId(const std::string& path);
+Result<bool> CanHardLink(const std::string& source,
+                         const std::string& destination);
+Result<ino_t> FileInodeNumber(const std::string& path);
+Result<bool> AreHardLinked(const std::string& source,
+                           const std::string& destination);
+Result<std::string> CreateHardLink(const std::string& target,
+                                   const std::string& hardlink,
+                                   const bool overwrite_existing = false);
 bool FileHasContent(const std::string& path);
 Result<std::vector<std::string>> DirectoryContents(const std::string& path);
 bool DirectoryExists(const std::string& path, bool follow_symlinks = true);
diff --git a/common/libs/utils/vsock_connection.cpp b/common/libs/utils/vsock_connection.cpp
index 310869c..cb57351 100644
--- a/common/libs/utils/vsock_connection.cpp
+++ b/common/libs/utils/vsock_connection.cpp
@@ -67,6 +67,10 @@
   disconnect_callback_ = callback;
 }
 
+// This method created due to a race condition in IsConnected().
+// TODO(b/345285391): remove this method once a fix found
+bool VsockConnection::IsConnected_Unguarded() { return fd_->IsOpen(); }
+
 bool VsockConnection::IsConnected() {
   // We need to serialize all accesses to the SharedFD.
   std::lock_guard<std::recursive_mutex> read_lock(read_mutex_);
diff --git a/common/libs/utils/vsock_connection.h b/common/libs/utils/vsock_connection.h
index 29e2c93..905253d 100644
--- a/common/libs/utils/vsock_connection.h
+++ b/common/libs/utils/vsock_connection.h
@@ -39,6 +39,7 @@
                                  std::optional<int> vhost_user_vsock_cid);
   void SetDisconnectCallback(std::function<void()> callback);
 
+  bool IsConnected_Unguarded();
   bool IsConnected();
   bool DataAvailable();
   int32_t Read();
diff --git a/guest/commands/snapshot_hook/Android.bp b/guest/commands/snapshot_hook/Android.bp
new file mode 100644
index 0000000..acc0ce1
--- /dev/null
+++ b/guest/commands/snapshot_hook/Android.bp
@@ -0,0 +1,30 @@
+//
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+sh_binary {
+    name: "snapshot_hook_pre_suspend",
+    src: "snapshot_hook_pre_suspend.sh",
+    vendor: true,
+}
+
+sh_binary {
+    name: "snapshot_hook_post_resume",
+    src: "snapshot_hook_post_resume.sh",
+    vendor: true,
+}
diff --git a/guest/Android.mk b/guest/commands/snapshot_hook/snapshot_hook_post_resume.sh
similarity index 62%
rename from guest/Android.mk
rename to guest/commands/snapshot_hook/snapshot_hook_post_resume.sh
index 428f4b5..0840c3a 100644
--- a/guest/Android.mk
+++ b/guest/commands/snapshot_hook/snapshot_hook_post_resume.sh
@@ -1,10 +1,12 @@
-# Copyright (C) 2021 The Android Open Source Project
+#!/vendor/bin/sh
+
+# Copyright 2024 Google Inc. All rights reserved.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
 # You may obtain a copy of the License at
 #
-#      http://www.apache.org/licenses/LICENSE-2.0
+#     http://www.apache.org/licenses/LICENSE-2.0
 #
 # Unless required by applicable law or agreed to in writing, software
 # distributed under the License is distributed on an "AS IS" BASIS,
@@ -12,7 +14,10 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-LOCAL_PATH:= $(call my-dir)
+# This script is run right after Cuttlefish resumes the VM as part of taking a
+# snapshot.
 
-include $(CLEAR_VARS)
-include $(call all-makefiles-under,$(LOCAL_PATH))
+set -eux
+
+/system/bin/cmd bluetooth_manager enable
+/system/bin/cmd uwb enable-uwb
diff --git a/guest/commands/snapshot_hook/snapshot_hook_pre_suspend.sh b/guest/commands/snapshot_hook/snapshot_hook_pre_suspend.sh
new file mode 100644
index 0000000..3228cb7
--- /dev/null
+++ b/guest/commands/snapshot_hook/snapshot_hook_pre_suspend.sh
@@ -0,0 +1,24 @@
+#!/vendor/bin/sh
+
+# Copyright 2024 Google Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This script is run right before Cuttlefish suspends the VM as part of taking
+# a snapshot.
+
+set -eux
+
+/system/bin/cmd bluetooth_manager disable
+/system/bin/cmd bluetooth_manager wait-for-state:STATE_OFF
+/system/bin/cmd uwb disable-uwb
diff --git a/guest/commands/v4l2_streamer/Android.bp b/guest/commands/v4l2_streamer/Android.bp
new file mode 100644
index 0000000..02cab56
--- /dev/null
+++ b/guest/commands/v4l2_streamer/Android.bp
@@ -0,0 +1,42 @@
+//
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_binary {
+    name: "cuttlefish_v4l2_streamer",
+    srcs: [
+        "main.cpp",
+        "v4l2_helpers.cpp",
+        "yuv2rgb.cpp",
+        "vsock_frame_source.cpp",
+    ],
+    shared_libs: [
+        "libbase",
+        "libbinder_ndk",
+        "liblog",
+        "libutils",
+        "libvsock_utils",
+        "libjsoncpp",
+        "libcuttlefish_fs",
+    ],
+    static_libs: [
+        "libgflags",
+        "libcuttlefish_utils",
+    ],
+    defaults: ["cuttlefish_guest_only"],
+}
diff --git a/guest/commands/v4l2_streamer/main.cpp b/guest/commands/v4l2_streamer/main.cpp
new file mode 100644
index 0000000..219ebd0
--- /dev/null
+++ b/guest/commands/v4l2_streamer/main.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+
+#include <android-base/logging.h>
+#include <gflags/gflags.h>
+
+#include "guest/commands/v4l2_streamer/vsock_frame_source.h"
+
+DEFINE_bool(service_mode, false,
+            "true to log output to Logd, false for stderr");
+
+int main(int argc, char **argv) {
+  google::ParseCommandLineFlags(&argc, &argv, true);
+
+  if (FLAGS_service_mode) {
+    ::android::base::InitLogging(
+        argv, android::base::LogdLogger(android::base::SYSTEM));
+  } else {
+    ::android::base::InitLogging(argv, android::base::StderrLogger);
+  }
+
+  android::base::SetDefaultTag("cuttlefish_v4l2_streamer");
+
+  LOG(INFO) << "streamer starting...  ";
+
+  auto vfs = cuttlefish::VsockFrameSource::Start("/dev/video0");
+
+  if (vfs.ok()) {
+    LOG(INFO) << "streamer initialized, streaming in progress...";
+
+    vfs->get()->VsockReadLoop();
+
+    LOG(INFO) << "streamer terminated.";
+  } else {
+    LOG(FATAL) << "start failed.";
+  }
+}
\ No newline at end of file
diff --git a/guest/commands/v4l2_streamer/v4l2_helpers.cpp b/guest/commands/v4l2_streamer/v4l2_helpers.cpp
new file mode 100644
index 0000000..3472cf0
--- /dev/null
+++ b/guest/commands/v4l2_streamer/v4l2_helpers.cpp
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "v4l2_helpers.h"
+
+#include <fcntl.h>
+#include <linux/videodev2.h>
+#include <log/log.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+namespace cuttlefish {
+
+Result<size_t> V4l2GetBpp(int format) {
+  CF_EXPECT(format == V4L2_PIX_FMT_BGRX32,
+            "Error: v4l2_get_bpp; only V4L2_PIX_FMT_BGRX32 supported");
+  return 4;
+}
+
+Result<size_t> V4l2GetFrameSize(int format, int width, int height) {
+  size_t bytes_per_pixel =
+      CF_EXPECT(V4l2GetBpp(format), "Error: invalid bpp format");
+
+  return width * height * bytes_per_pixel;
+}
+
+Result<size_t> V4l2GetLineWidth(int format, int width) {
+  size_t bytes_per_pixel =
+      CF_EXPECT(V4l2GetBpp(format), "Error: invalid bpp format");
+
+  return width * bytes_per_pixel;
+}
+
+void V4l2PrintFormat(struct v4l2_format* vid_format) {
+  ALOGI("	vid_format->type                =%d", vid_format->type);
+  ALOGI("	vid_format->fmt.pix.width       =%d",
+        vid_format->fmt.pix.width);
+  ALOGI("	vid_format->fmt.pix.height      =%d",
+        vid_format->fmt.pix.height);
+  ALOGI("	vid_format->fmt.pix.pixelformat =%d",
+        vid_format->fmt.pix.pixelformat);
+  ALOGI("	vid_format->fmt.pix.sizeimage   =%d",
+        vid_format->fmt.pix.sizeimage);
+  ALOGI("	vid_format->fmt.pix.field       =%d",
+        vid_format->fmt.pix.field);
+  ALOGI("	vid_format->fmt.pix.bytesperline=%d",
+        vid_format->fmt.pix.bytesperline);
+  ALOGI("	vid_format->fmt.pix.colorspace  =%d",
+        vid_format->fmt.pix.colorspace);
+}
+
+Result<std::vector<char>> V4l2ReadRawFile(const std::string& filename) {
+  std::streampos filepos = 0;
+  std::ifstream file(filename, std::ios::binary);
+
+  filepos = file.tellg();
+  file.seekg(0, std::ios::end);
+  long buffersize = file.tellg() - filepos;
+  file.seekg(0, std::ios::beg);
+
+  std::vector<char> buffer;
+  buffer.resize(buffersize);
+
+  file.read(buffer.data(), buffersize);
+
+  CF_EXPECT_NE(file.fail(), 0,
+               "Error reading Raw file buffer: " << strerror(errno));
+
+  ALOGI("Allocated and read %ld bytes", buffersize);
+
+  return buffer;
+}
+
+Result<SharedFD> V4l2InitDevice(const std::string& device_path, int format,
+                                int width, int height) {
+  int framesize = CF_EXPECT(V4l2GetFrameSize(format, width, height),
+                            "Error calculating frame size");
+  int linewidth =
+      CF_EXPECT(V4l2GetLineWidth(format, width), "Error calculating linewidth");
+
+  SharedFD fdwr = SharedFD::Open(device_path, O_RDWR);
+
+  CF_EXPECT(fdwr->IsOpen(), "Error: Could not open v4l2 device for O_RDWR: "
+                                << fdwr->StrError());
+
+  struct v4l2_capability vid_caps;
+  int ret_code = fdwr->Ioctl(VIDIOC_QUERYCAP, &vid_caps);
+
+  CF_EXPECT_NE(ret_code, -1,
+               "Error: VIDIOC_QUERYCAP failed: " << fdwr->StrError());
+
+  struct v4l2_format vid_format = v4l2_format{};
+
+  V4l2PrintFormat(&vid_format);
+
+  vid_format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
+  vid_format.fmt.pix.width = width;
+  vid_format.fmt.pix.height = height;
+  vid_format.fmt.pix.pixelformat = format;
+  vid_format.fmt.pix.sizeimage = framesize;
+  vid_format.fmt.pix.field = V4L2_FIELD_NONE;
+  vid_format.fmt.pix.bytesperline = linewidth;
+  vid_format.fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
+
+  V4l2PrintFormat(&vid_format);
+
+  ret_code = fdwr->Ioctl(VIDIOC_S_FMT, &vid_format);
+
+  CF_EXPECT_NE(ret_code, -1,
+               "Error: VIDIOC_S_FMT failed: " << fdwr->StrError());
+
+  ALOGI("frame: format=%d\tsize=%d", format, framesize);
+  V4l2PrintFormat(&vid_format);
+
+  return fdwr;
+}
+
+// This is a testing / debugging method. Only used optionally for
+// troubleshooting a v4l2 by dumping raw movie frames direct to the device. It
+// avoids using the network for simplifying the debug process.   It also shows
+// how to use the API methods provided in this file.
+Result<void> V4l2StreamFile(const std::string& device_path,
+                            const std::string& raw_movie_file) {
+  int width = 640;
+  int height = 480;
+  int format = V4L2_PIX_FMT_BGRX32;
+  int framesize = CF_EXPECT(V4l2GetFrameSize(format, width, height),
+                            "Error getting frame size");
+
+  ALOGI("Starting.... using framesize(%d)", framesize);
+
+  std::vector<char> buffer =
+      CF_EXPECT(V4l2ReadRawFile(raw_movie_file), "Error reading buffer");
+
+  ALOGI("Beginning frame push with buffersize(%ld)", buffer.size());
+
+  SharedFD fdwr = CF_EXPECT(V4l2InitDevice(device_path, format, width, height),
+                            "Error initializing device");
+
+  CF_EXPECT(fdwr->IsOpen(), "Error: initdevice == 0");
+
+  ALOGI("Device initialized(%s)", device_path.c_str());
+
+  ALOGI("Beginning stream:");
+
+  CF_EXPECT(buffer.size() > framesize, "Error: invalid buffer size");
+
+  for (long i = 0; i < buffer.size() - framesize; i += framesize) {
+    ALOGI("Beginning frame:");
+    if (fdwr->Write(((char*)buffer.data()) + i, framesize) == -1) {
+      ALOGE("Error writing buffer data: %s", fdwr->StrError().c_str());
+    }
+    sleep(1);
+    if (i % 20 == 0) {
+      ALOGI("Wrote %ld frames", ((i + framesize) / framesize));
+    }
+  }
+
+  ALOGI("ended stream:");
+
+  fdwr->Close();
+
+  ALOGI("Streaming complete.");
+
+  return {};
+}
+
+}  // End namespace cuttlefish
\ No newline at end of file
diff --git a/guest/commands/v4l2_streamer/v4l2_helpers.h b/guest/commands/v4l2_streamer/v4l2_helpers.h
new file mode 100644
index 0000000..14deca5
--- /dev/null
+++ b/guest/commands/v4l2_streamer/v4l2_helpers.h
@@ -0,0 +1,55 @@
+
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <linux/videodev2.h>
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/result.h"
+
+namespace cuttlefish {
+
+// Opens a v4l2 device, located at given [device_path]. The device is then
+// configured to receive frames of the given format, width, and height. Note
+// that only format V4L2_PIX_FMT_BGRX32 is supported at this time
+Result<SharedFD> V4l2InitDevice(const std::string& device_path, int format,
+                                int width, int height);
+
+// Returns # of bytes per pixel of given format, for
+// frame size calculations
+// Note that only format V4L2_PIX_FMT_BGRX32 is supported at this time
+Result<size_t> V4l2GetBPP(int format);
+
+// Returns size in bytes of single frame of given v4l2 format
+// Note that only format V4L2_PIX_FMT_BGRX32 is supported at this time
+Result<size_t> V4l2GetFrameSize(int format, int width, int height);
+
+// Returns size in bytes of a single line data in video fram image
+// Note that only format V4L2_PIX_FMT_BGRX32 is supported at this time
+Result<size_t> V4l2GetLineWidth(int format, int width);
+
+// Dump to logger debug info of the given v4l2_format
+void V4l2PrintFormat(struct v4l2_format* vid_format);
+
+// The following two optional methods are used for debugging / testing v4l2
+// devices, not by the runtime streamer.
+Result<void> V4l2StreamFile();
+
+// Reads a file containing raw frames in BGRA32 format.
+Result<std::vector<char>> V4l2ReadRawFile(const std::string& filename);
+
+}  // End namespace cuttlefish
\ No newline at end of file
diff --git a/guest/commands/v4l2_streamer/vsock_frame_source.cpp b/guest/commands/v4l2_streamer/vsock_frame_source.cpp
new file mode 100644
index 0000000..89c5c7a
--- /dev/null
+++ b/guest/commands/v4l2_streamer/vsock_frame_source.cpp
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <fcntl.h>
+#include <log/log.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#include "guest/commands/v4l2_streamer/v4l2_helpers.h"
+#include "guest/commands/v4l2_streamer/vsock_frame_source.h"
+#include "guest/commands/v4l2_streamer/yuv2rgb.h"
+
+namespace cuttlefish {
+
+VsockFrameSource::~VsockFrameSource() { Stop(); }
+
+bool VsockFrameSource::IsBlob(const std::vector<char>& blob) {
+  static const char kPng[] = "\x89PNG";
+  static const char kJpeg[] = "\xff\xd8";
+  bool is_png =
+      blob.size() > 4 && std::memcmp(blob.data(), kPng, sizeof(kPng)) == 0;
+  bool is_jpeg =
+      blob.size() > 2 && std::memcmp(blob.data(), kJpeg, sizeof(kJpeg)) == 0;
+  return is_png || is_jpeg;
+}
+
+bool VsockFrameSource::WriteJsonEventMessage(const std::string& message) {
+  Json::Value json_message;
+  json_message["event"] = message;
+  return connection_ && connection_->WriteMessage(json_message);
+}
+
+Result<bool> VsockFrameSource::ReadSettingsFromJson(const Json::Value& json) {
+  frame_width_ = json["width"].asInt();
+  frame_height_ = json["height"].asInt();
+  frame_rate_ = json["frame_rate"].asDouble();
+
+  if (frame_width_ > 0 && frame_height_ > 0 && frame_rate_ > 0) {
+    frame_size_ =
+        CF_EXPECT(V4l2GetFrameSize(format_, frame_width_, frame_height_),
+                  "Error getting framesize");
+    ALOGI("%s: readSettingsFromJson received: w/h/fps(%d,%d,%d)", __FUNCTION__,
+          frame_width_, frame_height_, frame_rate_);
+    return true;
+  } else {
+    ALOGE("%s: readSettingsFromJson received invalid values: w/h/fps(%d,%d,%d)",
+          __FUNCTION__, frame_width_, frame_height_, frame_rate_);
+    return false;
+  }
+}
+
+bool VsockFrameSource::Connect() {
+  connection_ = std::make_unique<
+      cuttlefish::VsockServerConnection>();  // VsockServerConnection
+  if (connection_->Connect(
+          7600, VMADDR_CID_ANY,
+          std::nullopt /* vhost_user_vsock: because it's guest */)) {
+    auto json_settings = connection_->ReadJsonMessage();
+
+    if (ReadSettingsFromJson(json_settings)) {
+      std::lock_guard<std::mutex> lock(settings_mutex_);
+      ALOGI("%s: VsockFrameSource connected", __FUNCTION__);
+      return true;
+    } else {
+      ALOGE("%s: Could not read settings", __FUNCTION__);
+    }
+  } else {
+    ALOGE("%s: VsockFrameSource connection failed", __FUNCTION__);
+  }
+  return false;
+}
+
+Result<std::unique_ptr<VsockFrameSource>> VsockFrameSource::Start(
+    const std::string& v4l2_device_path) {
+  auto frame_source = std::unique_ptr<VsockFrameSource>(new VsockFrameSource);
+
+  frame_source->v4l2_device_path_ = v4l2_device_path;
+
+  CF_EXPECT(frame_source->Connect(), "connect failed");
+
+  ALOGI("%s: VsockFrameSource connected", __FUNCTION__);
+
+  frame_source->running_ = true;
+
+  frame_source->WriteJsonEventMessage("VIRTUAL_DEVICE_START_CAMERA_SESSION");
+
+  frame_source->fd_v4l2_device_ = CF_EXPECT(
+      V4l2InitDevice(frame_source->v4l2_device_path_, frame_source->format_,
+                     frame_source->frame_width_, frame_source->frame_height_),
+      "Error opening v4l2 device");
+
+  CF_EXPECT(frame_source->fd_v4l2_device_->IsOpen(),
+            "Error: fd_v4l2_device_->IsOpen() failed");
+
+  ALOGI("%s: successful v4l2 device open.", __FUNCTION__);
+
+  return frame_source;
+}
+
+void VsockFrameSource::Stop() {
+  if (running_.exchange(false)) {
+    if (reader_thread_.joinable()) {
+      reader_thread_.join();
+    }
+    WriteJsonEventMessage("VIRTUAL_DEVICE_STOP_CAMERA_SESSION");
+    connection_ = nullptr;
+    fd_v4l2_device_->Close();
+  }
+}
+
+void VsockFrameSource::WriteFrame(const std::vector<char>& frame,
+                                  std::vector<char>& rgb_frame) {
+  if (rgb_frame.size() != frame_size_) {
+    rgb_frame.resize(frame_size_);
+  }
+  Yuv2Rgb((unsigned char*)frame.data(), (unsigned char*)rgb_frame.data(),
+          frame_width_, frame_height_);
+  fd_v4l2_device_->Write((unsigned char*)rgb_frame.data(), frame_size_);
+}
+
+bool VsockFrameSource::Running() { return running_; }
+
+bool VsockFrameSource::FramesizeMatches(const std::vector<char>& data) {
+  return data.size() == 3 * frame_width_ * frame_height_ / 2;
+}
+
+Result<void> VsockFrameSource::VsockReadLoopThreaded() {
+  CF_EXPECT(fd_v4l2_device_->IsOpen(), "Error: v4l2_initdevice == 0");
+
+  reader_thread_ = std::thread([this] { VsockReadLoop(); });
+
+  return {};
+}
+
+void VsockFrameSource::VsockReadLoop() {
+  std::vector<char> frame;
+  std::vector<char> next_frame;
+  std::vector<char> rgb_frame;
+
+  while (running_.load() && connection_->ReadMessage(next_frame)) {
+    if (FramesizeMatches(next_frame)) {
+      std::lock_guard<std::mutex> lock(frame_mutex_);
+      timestamp_ = systemTime();
+      frame.swap(next_frame);
+      yuv_frame_updated_.notify_one();
+      WriteFrame(frame, rgb_frame);
+    } else if (IsBlob(next_frame)) {
+    }  // TODO
+    else {
+      ALOGE("%s: Unexpected data of %zu bytes", __FUNCTION__,
+            next_frame.size());
+    }
+  }
+  if (!connection_->IsConnected_Unguarded()) {
+    ALOGE("%s: Connection closed - exiting", __FUNCTION__);
+    running_ = false;
+  }
+}
+
+}  // End namespace cuttlefish
\ No newline at end of file
diff --git a/guest/commands/v4l2_streamer/vsock_frame_source.h b/guest/commands/v4l2_streamer/vsock_frame_source.h
new file mode 100644
index 0000000..a983410
--- /dev/null
+++ b/guest/commands/v4l2_streamer/vsock_frame_source.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <linux/videodev2.h>
+#include <iostream>
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/result.h"
+#include "utils/Timers.h"
+#include "vsock_connection.h"
+
+namespace cuttlefish {
+
+// VsockFrameSource accepts WebRTC YUV camera stream data
+// over vsock, converts it to v4l2 format BGRX32, and then
+// writes the result to a v4l2 device.  This allows for creation
+// of v4l2 devices in guest VMs, and streaming to them
+// from Cuttlefish's WebRTC UI via any connected camera.
+class VsockFrameSource {
+ public:
+  // Starts a Frame Source streaming session targeting a
+  // specific v4l2 device
+  static Result<std::unique_ptr<VsockFrameSource>> Start(
+      const std::string& v4l2_device_path);
+
+  ~VsockFrameSource();
+
+  // Stops a thread managing the stream if running, and closes the v4l2 device.
+  void Stop();
+
+  // Returns true if there is a camera stream currently running
+  bool Running();
+
+  // This is a blocking method, that runs while connection is valid.
+  // It receives frames from a vsock socket, formats the data stream and
+  // sends it to a v4l2 output device.
+  void VsockReadLoop();
+
+  // Starts a Thread which invokes VsockReadLoop(). This allows the calling
+  // thread to perform other operations while this frame source is sending data.
+  Result<void> VsockReadLoopThreaded();
+
+ private:
+  // The v4l2 device path to receive camera frames, ie /dev/video0
+  std::string v4l2_device_path_;
+  std::unique_ptr<cuttlefish::VsockConnection> connection_;
+  std::thread reader_thread_;
+  std::atomic<bool> running_;
+  std::mutex frame_mutex_;
+  std::mutex settings_mutex_;
+  std::atomic<nsecs_t> timestamp_;
+  std::condition_variable yuv_frame_updated_;
+
+  // File handle of v4l2 device to be written to
+  SharedFD fd_v4l2_device_;
+
+  // Following frame_* values will be set after successful connection.
+  // Host process sends a message which conveys the camera dimensions
+  // to this guest instance over the vsock connection.
+  int frame_width_ = 0;
+  int frame_height_ = 0;
+  int frame_rate_ = 0;
+  int frame_size_ = 0;
+
+  // Currently this class only supports writing to v4l2 devices
+  // via this format.
+  int format_ = V4L2_PIX_FMT_BGRX32;
+
+  // Verifies that given data is a video frame. Used to
+  // distinguish control messages.
+  bool FramesizeMatches(const std::vector<char>& data);
+
+  // Determines if a vsock packet contains special data
+  // that is not camera frame.
+  bool IsBlob(const std::vector<char>& blob);
+
+  // Sends message to Host process communicating an event in the
+  // camera connection state. ie - when to start or stop streaming.
+  bool WriteJsonEventMessage(const std::string& message);
+
+  // After connect, this is called to retrieve camera dimensions
+  // and properties needed to initialize the v4l2 device and allocate
+  // buffers necessary for streaming.
+  Result<bool> ReadSettingsFromJson(const Json::Value& json);
+
+  // Established the vsock connection
+  bool Connect();
+
+  // Called once every frame to write a frame buffer to the v4l2
+  // output device.
+  void WriteFrame(const std::vector<char>& frame, std::vector<char>& rgb_frame);
+
+ protected:
+  VsockFrameSource() = default;
+};
+
+}  // End namespace cuttlefish
\ No newline at end of file
diff --git a/guest/commands/v4l2_streamer/yuv2rgb.cpp b/guest/commands/v4l2_streamer/yuv2rgb.cpp
new file mode 100644
index 0000000..1a21cd7
--- /dev/null
+++ b/guest/commands/v4l2_streamer/yuv2rgb.cpp
@@ -0,0 +1,90 @@
+
+/*
+ * Copyright 2018 Google LLC. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+// This file contains an adaptation of the algorithm at:
+// https://github.com/GoogleChromeLabs/wasm-av1/blob/master/yuv-to-rgb.c
+
+// The algorithm here creates precomputed lookup tables to speed up converting
+// YUV frames to RGB. Since it is done once every camera frame it needs to be
+// efficient.
+//
+// NOTE: This is code is being used temporarily until Cuttlefish supports
+// hardware-accelerated camera frame transfer from host to guest.  Ideally the
+// conversions will be done via DMA or GPU algorithms, not via CPU copy
+
+// Number of luminance values to precompute tables of for speed. Value is higher
+// than 255 as to allow for future color depth expansion
+#define ZOF_TAB 65536
+
+// Size of single output pixel in bytes (RGBA x 1 byte each = 4 bytes)
+#define ZOF_RGB 4
+
+namespace cuttlefish {
+
+// These tables will store precomputes values
+static int T1[ZOF_TAB], T2[ZOF_TAB], T3[ZOF_TAB], T4[ZOF_TAB];
+static int tables_initialized;
+
+// Called once to initialize tables
+static void build_yuv2rgb_tables() {
+  for (int i = 0; i < ZOF_TAB; i++) {
+    T1[i] = (int)(1.370705 * (float)(i - 128));
+    T2[i] = (int)(-0.698001 * (float)(i - 128));
+    T3[i] = (int)(-0.337633 * (float)(i - 128));
+    T4[i] = (int)(1.732446 * (float)(i - 128));
+  }
+}
+
+#define clamp(val) ((val) < 0 ? 0 : (255 < (val) ? 255 : (val)))
+
+void Yuv2Rgb(unsigned char *src, unsigned char *dst, int width, int height) {
+  if (tables_initialized == 0) {
+    tables_initialized = !0;
+    build_yuv2rgb_tables();
+  }
+  // Setup pointers to the Y, U, V planes
+  unsigned char *y = src;
+  unsigned char *u = src + (width * height);
+  unsigned char *v =
+      u + (width * height) / 4;  // Each chroma does 4 pixels in 4:2:0
+  // Loop the image, taking into account sub-sample for the chroma channels
+  for (int h = 0; h < height; h++) {
+    unsigned char *uline = u;
+    unsigned char *vline = v;
+    for (int w = 0; w < width; w++, y++) {
+      int r = *y + T1[*vline];
+      int g = *y + T2[*vline] + T3[*uline];
+      int b = *y + T4[*uline];
+      // Note: going BGRA here not RGBA
+      dst[0] = clamp(b);  // 16-bit to 8-bit, chuck precision
+      dst[1] = clamp(g);
+      dst[2] = clamp(r);
+      dst[3] = 255;
+      dst += ZOF_RGB;
+      if (w & 0x01) {
+        uline++;
+        vline++;
+      }
+    }
+    if (h & 0x01) {
+      u += width / 2;
+      v += width / 2;
+    }
+  }
+}
+
+}  // End namespace cuttlefish
\ No newline at end of file
diff --git a/guest/commands/v4l2_streamer/yuv2rgb.h b/guest/commands/v4l2_streamer/yuv2rgb.h
new file mode 100644
index 0000000..c3115df
--- /dev/null
+++ b/guest/commands/v4l2_streamer/yuv2rgb.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+namespace cuttlefish {
+
+// Read from the given [src] buffer, expected to be in WebRTC YUV format,
+// writing data the [dst] buffer in v4l2 BGRX32 format. [width] and [height]
+// must be valid to describe the frame size, so that indexing calculations are
+// accurate. Note that [src] and [dst] buffers are both required to be
+// pre-allocated, [src] will need to contain valid YUV data, and [dst] contents
+// will be overwritten.
+void Yuv2Rgb(unsigned char *src, unsigned char *dst, int width, int height);
+
+}  // namespace cuttlefish
\ No newline at end of file
diff --git a/guest/hals/health/health-aidl.cpp b/guest/hals/health/health-aidl.cpp
index 38df504..3c9ef74 100644
--- a/guest/hals/health/health-aidl.cpp
+++ b/guest/hals/health/health-aidl.cpp
@@ -103,10 +103,13 @@
 }
 
 ScopedAStatus HealthImpl::getBatteryHealthData(BatteryHealthData* out) {
-  out->batteryManufacturingDateSeconds = 0;
-  out->batteryFirstUsageSeconds = 0;
+  out->batteryManufacturingDateSeconds =
+      1689787603;  // Wednesday, 19 July 2023 17:26:43
+  out->batteryFirstUsageSeconds =
+      1691256403;  // Saturday, 5 August 2023 17:26:43
   out->batteryStateOfHealth = 99;
-  out->batterySerialNumber = std::nullopt;
+  out->batterySerialNumber =
+      "d1f92fe7591ff096ca3a29c450a5a3d1";  // MD5("battery serial")
   out->batteryPartStatus = BatteryPartStatus::UNSUPPORTED;
   return ScopedAStatus::ok();
 }
diff --git a/guest/hals/ril/reference-libril/Android.bp b/guest/hals/ril/reference-libril/Android.bp
index 09acbd4..e73d563 100644
--- a/guest/hals/ril/reference-libril/Android.bp
+++ b/guest/hals/ril/reference-libril/Android.bp
@@ -30,6 +30,7 @@
         "RefImsMedia.cpp",
         "RefImsMediaSession.cpp",
         "RefRadioNetwork.cpp",
+        "RefRadioConfig.cpp",
         "ril.cpp",
         "RilSapSocket.cpp",
         "ril_config.cpp",
diff --git a/guest/hals/ril/reference-libril/RefRadioConfig.cpp b/guest/hals/ril/reference-libril/RefRadioConfig.cpp
new file mode 100644
index 0000000..ecc62a1
--- /dev/null
+++ b/guest/hals/ril/reference-libril/RefRadioConfig.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "RefRadioConfig.h"
+#include "ril_service.h"
+
+using ::android::hardware::hidl_string;
+
+namespace cf::ril {
+
+using ::ndk::ScopedAStatus;
+using namespace ::aidl::android::hardware::radio;
+constexpr auto ok = &ScopedAStatus::ok;
+
+static RadioResponseInfo responseInfo(int32_t serial, RadioError error = RadioError::NONE) {
+    return {
+            .type = RadioResponseType::SOLICITED,
+            .serial = serial,
+            .error = error,
+    };
+}
+
+ScopedAStatus RefRadioConfig::getSimultaneousCallingSupport(int32_t serial) {
+    respond()->getSimultaneousCallingSupportResponse(responseInfo(serial), {});
+    return ok();
+}
+}  // namespace cf::ril
diff --git a/guest/hals/ril/reference-libril/RefRadioConfig.h b/guest/hals/ril/reference-libril/RefRadioConfig.h
new file mode 100644
index 0000000..fe2973d
--- /dev/null
+++ b/guest/hals/ril/reference-libril/RefRadioConfig.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <libradiocompat/RadioConfig.h>
+
+namespace cf::ril {
+
+class RefRadioConfig : public android::hardware::radio::compat::RadioConfig {
+  public:
+    using android::hardware::radio::compat::RadioConfig::RadioConfig;
+
+    ::ndk::ScopedAStatus getSimultaneousCallingSupport(int32_t serial) override;
+};
+}  // namespace cf::ril
diff --git a/guest/hals/ril/reference-libril/RefRadioModem.cpp b/guest/hals/ril/reference-libril/RefRadioModem.cpp
index 867c8f0..4dfffa4 100644
--- a/guest/hals/ril/reference-libril/RefRadioModem.cpp
+++ b/guest/hals/ril/reference-libril/RefRadioModem.cpp
@@ -33,20 +33,12 @@
         };
     }
 
-    hidl_string convertCharPtrToHidlString(const char *ptr) {
-        hidl_string ret;
-        if (ptr != NULL) {
-            ret.setToExternal(ptr, strlen(ptr));
-        }
-        return ret;
-    }
-
     ScopedAStatus RefRadioModem::getImei(int32_t serial) {
         ::aidl::android::hardware::radio::modem::ImeiInfo imeiInfo = {};
         imeiInfo.type = (::aidl::android::hardware::radio::modem::ImeiInfo::ImeiType) 1;
-        imeiInfo.imei = convertCharPtrToHidlString("867400022047199");
-        imeiInfo.svn = convertCharPtrToHidlString("01");
+        imeiInfo.imei = "867400022047199";
+        imeiInfo.svn = "01";
         respond()->getImeiResponse(responseInfo(serial), imeiInfo);
         return ok();
     }
-}
\ No newline at end of file
+}
diff --git a/guest/hals/ril/reference-libril/RefRadioNetwork.cpp b/guest/hals/ril/reference-libril/RefRadioNetwork.cpp
index 941cd24..5507ee0 100644
--- a/guest/hals/ril/reference-libril/RefRadioNetwork.cpp
+++ b/guest/hals/ril/reference-libril/RefRadioNetwork.cpp
@@ -50,6 +50,9 @@
 ScopedAStatus RefRadioNetwork::setEmergencyMode(int32_t serial,
                                                 network::EmergencyMode emergencyMode) {
     network::EmergencyRegResult regState;
+    regState.accessNetwork = AccessNetwork::EUTRAN;
+    regState.regState = network::RegState::REG_HOME;
+    regState.emcDomain = network::Domain(3);  // CS_PS
     respond()->setEmergencyModeResponse(responseInfo(serial), regState);
     return ok();
 }
diff --git a/guest/hals/ril/reference-libril/ril.h b/guest/hals/ril/reference-libril/ril.h
index 905c8d3..ae15ecb 100644
--- a/guest/hals/ril/reference-libril/ril.h
+++ b/guest/hals/ril/reference-libril/ril.h
@@ -8345,7 +8345,11 @@
 
 #define RIL_UNSOL_PHYSICAL_CHANNEL_CONFIGS 1051
 
-#define RIL_UNSOL_RESPONSE_LAST RIL_UNSOL_PHYSICAL_CHANNEL_CONFIGS
+#define RIL_UNSOL_CELLULAR_IDENTIFIER_DISCLOSED 1056
+
+#define RIL_UNSOL_SECURITY_ALGORITHM_UPDATED 1057
+
+#define RIL_UNSOL_RESPONSE_LAST RIL_UNSOL_SECURITY_ALGORITHM_UPDATED
 
 /***********************************************************************/
 
@@ -8355,7 +8359,7 @@
  *
  * "data" is the RIL_SimSlotStatus_V1_2 structure
  */
-#define RIL_UNSOL_CONFIG_ICC_SLOT_STATUS 1052
+#define RIL_UNSOL_CONFIG_ICC_SLOT_STATUS 1100
 
 #define RIL_UNSOL_RESPONSE_RADIO_CONFIG_LAST RIL_UNSOL_CONFIG_ICC_SLOT_STATUS
 
@@ -8548,6 +8552,26 @@
     RIL_KeepaliveStatusCode code;
 } RIL_KeepaliveStatus;
 
+/**
+ * A C-representation of aidl::android::hardware::radio::network::CellularIdentifierDisclosure
+ */
+typedef struct {
+    int32_t identifierType;
+    int32_t protocolMessage;
+    char* plmn;
+    bool isEmergency;
+} RIL_CellularIdentifierDisclosure;
+
+/**
+ * A C-representation of aidl::android::hardware::radio::network::SecurityAlgorithmUpdate
+ */
+typedef struct {
+    int32_t connectionEvent;
+    int32_t encryption;
+    int32_t integrity;
+    bool isUnprotectedEmergency;
+} RIL_SecurityAlgorithmUpdate;
+
 #ifdef RIL_SHLIB
 struct RIL_Env {
     /**
diff --git a/guest/hals/ril/reference-libril/ril_config.cpp b/guest/hals/ril/reference-libril/ril_config.cpp
index 706026d..eef1068 100644
--- a/guest/hals/ril/reference-libril/ril_config.cpp
+++ b/guest/hals/ril/reference-libril/ril_config.cpp
@@ -17,6 +17,8 @@
 
 #define LOG_TAG "RILC"
 
+#include "RefRadioConfig.h"
+
 #include <android-base/logging.h>
 #include <android/binder_manager.h>
 #include <android/binder_process.h>
@@ -286,7 +288,7 @@
 
     // use a compat shim to convert HIDL interface to AIDL and publish it
     // TODO(bug 220004469): replace with a full AIDL implementation
-    static auto aidlHal = ndk::SharedRefBase::make<compat::RadioConfig>(radioConfigService);
+    static auto aidlHal = ndk::SharedRefBase::make<cf::ril::RefRadioConfig>(radioConfigService);
     const auto instance = compat::RadioConfig::descriptor + "/"s + std::string(serviceNames);
     const auto status = AServiceManager_addService(aidlHal->asBinder().get(), instance.c_str());
     RLOGD("registerConfigService addService: status %d", status);
diff --git a/guest/hals/ril/reference-libril/ril_service.cpp b/guest/hals/ril/reference-libril/ril_service.cpp
index 01ddb74..5deb88c 100644
--- a/guest/hals/ril/reference-libril/ril_service.cpp
+++ b/guest/hals/ril/reference-libril/ril_service.cpp
@@ -73,6 +73,8 @@
     android::hardware::radio::V1_4::PhysicalChannelConfig;
 using RadioTechnologyV1_4 = android::hardware::radio::V1_4::RadioTechnology;
 
+namespace aidl_radio = ::aidl::android::hardware::radio;
+
 #define BOOL_TO_INT(x) (x ? 1 : 0)
 #define ATOI_NULL_HANDLED(x) (x ? atoi(x) : -1)
 #define ATOI_NULL_HANDLED_DEF(x, defaultVal) (x ? atoi(x) : defaultVal)
@@ -202,6 +204,7 @@
     sp<V1_5::IRadioIndication> mRadioIndicationV1_5;
     sp<V1_6::IRadioResponse> mRadioResponseV1_6;
     sp<V1_6::IRadioIndication> mRadioIndicationV1_6;
+    std::shared_ptr<compat::CallbackManager> mCallbackManager;
 
     Return<void> setResponseFunctions(
             const ::android::sp<IRadioResponse>& radioResponse,
@@ -10659,6 +10662,63 @@
     return 0;
 }
 
+int radio_aidl::cellularIdentifierDisclosedInd(int slotId, int indicationType, int token,
+                                               RIL_Errno e, void* response, size_t responselen) {
+    if (radioService[slotId] == NULL || radioService[slotId]->mCallbackManager == NULL) {
+        RLOGE("cellularIdentifierDisclosedInd: radioService[%d]->mCallbackManager == NULL", slotId);
+        return 0;
+    }
+    auto networkCb = radioService[slotId]->mCallbackManager->indication().networkCb();
+
+    if (!networkCb) {
+        RLOGE("networkCB is null");
+        return 0;
+    }
+
+    RIL_CellularIdentifierDisclosure* rawDisclosure =
+            static_cast<RIL_CellularIdentifierDisclosure*>(response);
+
+    aidl_radio::network::CellularIdentifierDisclosure disclosure;
+    disclosure.identifier =
+            static_cast<aidl_radio::network::CellularIdentifier>(rawDisclosure->identifierType);
+    disclosure.protocolMessage =
+            static_cast<aidl_radio::network::NasProtocolMessage>(rawDisclosure->protocolMessage);
+    disclosure.plmn = rawDisclosure->plmn;
+    disclosure.isEmergency = rawDisclosure->isEmergency;
+
+    networkCb->cellularIdentifierDisclosed(aidl_radio::RadioIndicationType(indicationType),
+                                           disclosure);
+
+    return 0;
+}
+
+int radio_aidl::securityAlgorithmUpdatedInd(int slotId, int indicationType, int token, RIL_Errno e,
+                                            void* response, size_t responselen) {
+    if (radioService[slotId] == NULL || radioService[slotId]->mCallbackManager == NULL) {
+        RLOGE("securityAlgorithmUpdatedInd: radioService[%d]->mCallbackManager == NULL", slotId);
+        return 0;
+    }
+    auto networkCb = radioService[slotId]->mCallbackManager->indication().networkCb();
+
+    if (!networkCb) {
+        RLOGE("networkCB is null");
+        return 0;
+    }
+
+    RIL_SecurityAlgorithmUpdate* rawUpdate = static_cast<RIL_SecurityAlgorithmUpdate*>(response);
+
+    aidl_radio::network::SecurityAlgorithmUpdate update;
+    update.connectionEvent =
+            static_cast<aidl_radio::network::ConnectionEvent>(rawUpdate->connectionEvent);
+    update.encryption = static_cast<aidl_radio::network::SecurityAlgorithm>(rawUpdate->encryption);
+    update.integrity = static_cast<aidl_radio::network::SecurityAlgorithm>(rawUpdate->integrity);
+    update.isUnprotectedEmergency = rawUpdate->isUnprotectedEmergency;
+
+    networkCb->securityAlgorithmsUpdated(aidl_radio::RadioIndicationType(indicationType), update);
+
+    return 0;
+}
+
 extern "C" uint8_t hexCharToInt(uint8_t c) {
     if (c >= '0' && c <= '9') return (c - '0');
     if (c >= 'A' && c <= 'F') return (c - 'A' + 10);
@@ -13439,6 +13499,7 @@
         const auto slot = serviceNames[i];
         auto context = std::make_shared<compat::DriverContext>();
         auto callbackMgr = std::make_shared<compat::CallbackManager>(context, radioHidl);
+        radioService[i]->mCallbackManager = callbackMgr;
         publishRadioHal<compat::RadioData>(context, radioHidl, callbackMgr, slot);
         publishRadioHal<compat::RadioMessaging>(context, radioHidl, callbackMgr, slot);
         publishRadioHal<compat::RadioModem>(context, radioHidl, callbackMgr, slot);
diff --git a/guest/hals/ril/reference-libril/ril_service.h b/guest/hals/ril/reference-libril/ril_service.h
index 6821f2c..db767e4 100644
--- a/guest/hals/ril/reference-libril/ril_service.h
+++ b/guest/hals/ril/reference-libril/ril_service.h
@@ -867,6 +867,19 @@
 int simSlotsStatusChanged(int slotId, int indicationType, int token,
                           RIL_Errno e, void *response, size_t responseLen);
 
-}   // namespace radio
+}  // namespace radio_1_6
+
+/******************************************************************************/
+/* AIDL-only Radio Indication unsolicited interfaces' handling functions.     */
+/******************************************************************************/
+namespace radio_aidl {
+/* These are not defined in HIDL HALs through version 1.6 but we use the same RIL infrastructure to
+ * coordinate holding a wake lock and invoking these methods. */
+int cellularIdentifierDisclosedInd(int slotId, int indicationType, int token, RIL_Errno e,
+                                   void* response, size_t responselen);
+
+int securityAlgorithmUpdatedInd(int slotId, int indicationType, int token, RIL_Errno e,
+                                void* response, size_t responselen);
+}  // namespace radio_aidl
 
 #endif  // RIL_SERVICE_H
diff --git a/guest/hals/ril/reference-libril/ril_unsol_commands.h b/guest/hals/ril/reference-libril/ril_unsol_commands.h
index 62dd40c..67e7169 100644
--- a/guest/hals/ril/reference-libril/ril_unsol_commands.h
+++ b/guest/hals/ril/reference-libril/ril_unsol_commands.h
@@ -14,55 +14,75 @@
 ** See the License for the specific language governing permissions and
 ** limitations under the License.
 */
-    {RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED, radio_1_6::radioStateChangedInd, WAKE_PARTIAL},
-    {RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED, radio_1_6::callStateChangedInd, WAKE_PARTIAL},
-    {RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED, radio_1_6::networkStateChangedInd, WAKE_PARTIAL},
-    {RIL_UNSOL_RESPONSE_NEW_SMS, radio_1_6::newSmsInd, WAKE_PARTIAL},
-    {RIL_UNSOL_RESPONSE_NEW_SMS_STATUS_REPORT, radio_1_6::newSmsStatusReportInd, WAKE_PARTIAL},
-    {RIL_UNSOL_RESPONSE_NEW_SMS_ON_SIM, radio_1_6::newSmsOnSimInd, WAKE_PARTIAL},
-    {RIL_UNSOL_ON_USSD, radio_1_6::onUssdInd, WAKE_PARTIAL},
-    {RIL_UNSOL_ON_USSD_REQUEST, radio_1_6::onUssdInd, DONT_WAKE},
-    {RIL_UNSOL_NITZ_TIME_RECEIVED, radio_1_6::nitzTimeReceivedInd, WAKE_PARTIAL},
-    {RIL_UNSOL_SIGNAL_STRENGTH, radio_1_6::currentSignalStrengthInd, DONT_WAKE},
-    {RIL_UNSOL_DATA_CALL_LIST_CHANGED, radio_1_6::dataCallListChangedInd, WAKE_PARTIAL},
-    {RIL_UNSOL_SUPP_SVC_NOTIFICATION, radio_1_6::suppSvcNotifyInd, WAKE_PARTIAL},
-    {RIL_UNSOL_STK_SESSION_END, radio_1_6::stkSessionEndInd, WAKE_PARTIAL},
-    {RIL_UNSOL_STK_PROACTIVE_COMMAND, radio_1_6::stkProactiveCommandInd, WAKE_PARTIAL},
-    {RIL_UNSOL_STK_EVENT_NOTIFY, radio_1_6::stkEventNotifyInd, WAKE_PARTIAL},
-    {RIL_UNSOL_STK_CALL_SETUP, radio_1_6::stkCallSetupInd, WAKE_PARTIAL},
-    {RIL_UNSOL_SIM_SMS_STORAGE_FULL, radio_1_6::simSmsStorageFullInd, WAKE_PARTIAL},
-    {RIL_UNSOL_SIM_REFRESH, radio_1_6::simRefreshInd, WAKE_PARTIAL},
-    {RIL_UNSOL_CALL_RING, radio_1_6::callRingInd, WAKE_PARTIAL},
-    {RIL_UNSOL_RESPONSE_SIM_STATUS_CHANGED, radio_1_6::simStatusChangedInd, WAKE_PARTIAL},
-    {RIL_UNSOL_RESPONSE_CDMA_NEW_SMS, radio_1_6::cdmaNewSmsInd, WAKE_PARTIAL},
-    {RIL_UNSOL_RESPONSE_NEW_BROADCAST_SMS, radio_1_6::newBroadcastSmsInd, WAKE_PARTIAL},
-    {RIL_UNSOL_CDMA_RUIM_SMS_STORAGE_FULL, radio_1_6::cdmaRuimSmsStorageFullInd, WAKE_PARTIAL},
-    {RIL_UNSOL_RESTRICTED_STATE_CHANGED, radio_1_6::restrictedStateChangedInd, WAKE_PARTIAL},
-    {RIL_UNSOL_ENTER_EMERGENCY_CALLBACK_MODE, radio_1_6::enterEmergencyCallbackModeInd, WAKE_PARTIAL},
-    {RIL_UNSOL_CDMA_CALL_WAITING, radio_1_6::cdmaCallWaitingInd, WAKE_PARTIAL},
-    {RIL_UNSOL_CDMA_OTA_PROVISION_STATUS, radio_1_6::cdmaOtaProvisionStatusInd, WAKE_PARTIAL},
-    {RIL_UNSOL_CDMA_INFO_REC, radio_1_6::cdmaInfoRecInd, WAKE_PARTIAL},
-    {RIL_UNSOL_OEM_HOOK_RAW, radio_1_6::oemHookRawInd, WAKE_PARTIAL},
-    {RIL_UNSOL_RINGBACK_TONE, radio_1_6::indicateRingbackToneInd, WAKE_PARTIAL},
-    {RIL_UNSOL_RESEND_INCALL_MUTE, radio_1_6::resendIncallMuteInd, WAKE_PARTIAL},
-    {RIL_UNSOL_CDMA_SUBSCRIPTION_SOURCE_CHANGED, radio_1_6::cdmaSubscriptionSourceChangedInd, WAKE_PARTIAL},
-    {RIL_UNSOL_CDMA_PRL_CHANGED, radio_1_6::cdmaPrlChangedInd, WAKE_PARTIAL},
-    {RIL_UNSOL_EXIT_EMERGENCY_CALLBACK_MODE, radio_1_6::exitEmergencyCallbackModeInd, WAKE_PARTIAL},
-    {RIL_UNSOL_RIL_CONNECTED, radio_1_6::rilConnectedInd, WAKE_PARTIAL},
-    {RIL_UNSOL_VOICE_RADIO_TECH_CHANGED, radio_1_6::voiceRadioTechChangedInd, WAKE_PARTIAL},
-    {RIL_UNSOL_CELL_INFO_LIST, radio_1_6::cellInfoListInd, WAKE_PARTIAL},
-    {RIL_UNSOL_RESPONSE_IMS_NETWORK_STATE_CHANGED, radio_1_6::imsNetworkStateChangedInd, WAKE_PARTIAL},
-    {RIL_UNSOL_UICC_SUBSCRIPTION_STATUS_CHANGED, radio_1_6::subscriptionStatusChangedInd, WAKE_PARTIAL},
-    {RIL_UNSOL_SRVCC_STATE_NOTIFY, radio_1_6::srvccStateNotifyInd, WAKE_PARTIAL},
-    {RIL_UNSOL_HARDWARE_CONFIG_CHANGED, radio_1_6::hardwareConfigChangedInd, WAKE_PARTIAL},
-    {RIL_UNSOL_DC_RT_INFO_CHANGED, NULL, WAKE_PARTIAL},
-    {RIL_UNSOL_RADIO_CAPABILITY, radio_1_6::radioCapabilityIndicationInd, WAKE_PARTIAL},
-    {RIL_UNSOL_ON_SS, radio_1_6::onSupplementaryServiceIndicationInd, WAKE_PARTIAL},
-    {RIL_UNSOL_STK_CC_ALPHA_NOTIFY, radio_1_6::stkCallControlAlphaNotifyInd, WAKE_PARTIAL},
-    {RIL_UNSOL_LCEDATA_RECV, radio_1_6::lceDataInd, WAKE_PARTIAL},
-    {RIL_UNSOL_PCO_DATA, radio_1_6::pcoDataInd, WAKE_PARTIAL},
-    {RIL_UNSOL_MODEM_RESTART, radio_1_6::modemResetInd, WAKE_PARTIAL},
-    {RIL_UNSOL_CARRIER_INFO_IMSI_ENCRYPTION, radio_1_6::carrierInfoForImsiEncryption, WAKE_PARTIAL},
-    {RIL_UNSOL_NETWORK_SCAN_RESULT, radio_1_6::networkScanResultInd, WAKE_PARTIAL},
-    {RIL_UNSOL_KEEPALIVE_STATUS, radio_1_6::keepaliveStatusInd, WAKE_PARTIAL},
-    {RIL_UNSOL_PHYSICAL_CHANNEL_CONFIGS, radio_1_6::reportPhysicalChannelConfigs, WAKE_PARTIAL},
+{RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED, radio_1_6::radioStateChangedInd, WAKE_PARTIAL},
+        {RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED, radio_1_6::callStateChangedInd, WAKE_PARTIAL},
+        {RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED, radio_1_6::networkStateChangedInd,
+         WAKE_PARTIAL},
+        {RIL_UNSOL_RESPONSE_NEW_SMS, radio_1_6::newSmsInd, WAKE_PARTIAL},
+        {RIL_UNSOL_RESPONSE_NEW_SMS_STATUS_REPORT, radio_1_6::newSmsStatusReportInd, WAKE_PARTIAL},
+        {RIL_UNSOL_RESPONSE_NEW_SMS_ON_SIM, radio_1_6::newSmsOnSimInd, WAKE_PARTIAL},
+        {RIL_UNSOL_ON_USSD, radio_1_6::onUssdInd, WAKE_PARTIAL},
+        {RIL_UNSOL_ON_USSD_REQUEST, radio_1_6::onUssdInd, DONT_WAKE},
+        {RIL_UNSOL_NITZ_TIME_RECEIVED, radio_1_6::nitzTimeReceivedInd, WAKE_PARTIAL},
+        {RIL_UNSOL_SIGNAL_STRENGTH, radio_1_6::currentSignalStrengthInd, DONT_WAKE},
+        {RIL_UNSOL_DATA_CALL_LIST_CHANGED, radio_1_6::dataCallListChangedInd, WAKE_PARTIAL},
+        {RIL_UNSOL_SUPP_SVC_NOTIFICATION, radio_1_6::suppSvcNotifyInd, WAKE_PARTIAL},
+        {RIL_UNSOL_STK_SESSION_END, radio_1_6::stkSessionEndInd, WAKE_PARTIAL},
+        {RIL_UNSOL_STK_PROACTIVE_COMMAND, radio_1_6::stkProactiveCommandInd, WAKE_PARTIAL},
+        {RIL_UNSOL_STK_EVENT_NOTIFY, radio_1_6::stkEventNotifyInd, WAKE_PARTIAL},
+        {RIL_UNSOL_STK_CALL_SETUP, radio_1_6::stkCallSetupInd, WAKE_PARTIAL},
+        {RIL_UNSOL_SIM_SMS_STORAGE_FULL, radio_1_6::simSmsStorageFullInd, WAKE_PARTIAL},
+        {RIL_UNSOL_SIM_REFRESH, radio_1_6::simRefreshInd, WAKE_PARTIAL},
+        {RIL_UNSOL_CALL_RING, radio_1_6::callRingInd, WAKE_PARTIAL},
+        {RIL_UNSOL_RESPONSE_SIM_STATUS_CHANGED, radio_1_6::simStatusChangedInd, WAKE_PARTIAL},
+        {RIL_UNSOL_RESPONSE_CDMA_NEW_SMS, radio_1_6::cdmaNewSmsInd, WAKE_PARTIAL},
+        {RIL_UNSOL_RESPONSE_NEW_BROADCAST_SMS, radio_1_6::newBroadcastSmsInd, WAKE_PARTIAL},
+        {RIL_UNSOL_CDMA_RUIM_SMS_STORAGE_FULL, radio_1_6::cdmaRuimSmsStorageFullInd, WAKE_PARTIAL},
+        {RIL_UNSOL_RESTRICTED_STATE_CHANGED, radio_1_6::restrictedStateChangedInd, WAKE_PARTIAL},
+        {RIL_UNSOL_ENTER_EMERGENCY_CALLBACK_MODE, radio_1_6::enterEmergencyCallbackModeInd,
+         WAKE_PARTIAL},
+        {RIL_UNSOL_CDMA_CALL_WAITING, radio_1_6::cdmaCallWaitingInd, WAKE_PARTIAL},
+        {RIL_UNSOL_CDMA_OTA_PROVISION_STATUS, radio_1_6::cdmaOtaProvisionStatusInd, WAKE_PARTIAL},
+        {RIL_UNSOL_CDMA_INFO_REC, radio_1_6::cdmaInfoRecInd, WAKE_PARTIAL},
+        {RIL_UNSOL_OEM_HOOK_RAW, radio_1_6::oemHookRawInd, WAKE_PARTIAL},
+        {RIL_UNSOL_RINGBACK_TONE, radio_1_6::indicateRingbackToneInd, WAKE_PARTIAL},
+        {RIL_UNSOL_RESEND_INCALL_MUTE, radio_1_6::resendIncallMuteInd, WAKE_PARTIAL},
+        {RIL_UNSOL_CDMA_SUBSCRIPTION_SOURCE_CHANGED, radio_1_6::cdmaSubscriptionSourceChangedInd,
+         WAKE_PARTIAL},
+        {RIL_UNSOL_CDMA_PRL_CHANGED, radio_1_6::cdmaPrlChangedInd, WAKE_PARTIAL},
+        {RIL_UNSOL_EXIT_EMERGENCY_CALLBACK_MODE, radio_1_6::exitEmergencyCallbackModeInd,
+         WAKE_PARTIAL},
+        {RIL_UNSOL_RIL_CONNECTED, radio_1_6::rilConnectedInd, WAKE_PARTIAL},
+        {RIL_UNSOL_VOICE_RADIO_TECH_CHANGED, radio_1_6::voiceRadioTechChangedInd, WAKE_PARTIAL},
+        {RIL_UNSOL_CELL_INFO_LIST, radio_1_6::cellInfoListInd, WAKE_PARTIAL},
+        {RIL_UNSOL_RESPONSE_IMS_NETWORK_STATE_CHANGED, radio_1_6::imsNetworkStateChangedInd,
+         WAKE_PARTIAL},
+        {RIL_UNSOL_UICC_SUBSCRIPTION_STATUS_CHANGED, radio_1_6::subscriptionStatusChangedInd,
+         WAKE_PARTIAL},
+        {RIL_UNSOL_SRVCC_STATE_NOTIFY, radio_1_6::srvccStateNotifyInd, WAKE_PARTIAL},
+        {RIL_UNSOL_HARDWARE_CONFIG_CHANGED, radio_1_6::hardwareConfigChangedInd, WAKE_PARTIAL},
+        {RIL_UNSOL_DC_RT_INFO_CHANGED, NULL, WAKE_PARTIAL},
+        {RIL_UNSOL_RADIO_CAPABILITY, radio_1_6::radioCapabilityIndicationInd, WAKE_PARTIAL},
+        {RIL_UNSOL_ON_SS, radio_1_6::onSupplementaryServiceIndicationInd, WAKE_PARTIAL},
+        {RIL_UNSOL_STK_CC_ALPHA_NOTIFY, radio_1_6::stkCallControlAlphaNotifyInd, WAKE_PARTIAL},
+        {RIL_UNSOL_LCEDATA_RECV, radio_1_6::lceDataInd, WAKE_PARTIAL},
+        {RIL_UNSOL_PCO_DATA, radio_1_6::pcoDataInd, WAKE_PARTIAL},
+        {RIL_UNSOL_MODEM_RESTART, radio_1_6::modemResetInd, WAKE_PARTIAL},
+        {RIL_UNSOL_CARRIER_INFO_IMSI_ENCRYPTION, radio_1_6::carrierInfoForImsiEncryption,
+         WAKE_PARTIAL},
+        {RIL_UNSOL_NETWORK_SCAN_RESULT, radio_1_6::networkScanResultInd, WAKE_PARTIAL},
+        {RIL_UNSOL_KEEPALIVE_STATUS, radio_1_6::keepaliveStatusInd, WAKE_PARTIAL},
+        {RIL_UNSOL_PHYSICAL_CHANNEL_CONFIGS, radio_1_6::reportPhysicalChannelConfigs, WAKE_PARTIAL},
+        /**
+         * These are needed as placeholders because this array is intended to be indexable by the
+         * response number - 1000.
+         *
+         * Over time, RIL and framework values have diverged, but we try to keep them in sync when
+         * possible.
+         */
+        {1052, NULL, DONT_WAKE}, {1053, NULL, DONT_WAKE}, {1054, NULL, DONT_WAKE},
+        {1055, NULL, DONT_WAKE},
+        {RIL_UNSOL_CELLULAR_IDENTIFIER_DISCLOSED, radio_aidl::cellularIdentifierDisclosedInd,
+         WAKE_PARTIAL},
+        {RIL_UNSOL_SECURITY_ALGORITHM_UPDATED, radio_aidl::securityAlgorithmUpdatedInd,
+         WAKE_PARTIAL},
diff --git a/guest/hals/ril/reference-ril/reference-ril.c b/guest/hals/ril/reference-ril/reference-ril.c
index fa158b1..bcb8ba3 100644
--- a/guest/hals/ril/reference-ril/reference-ril.c
+++ b/guest/hals/ril/reference-ril/reference-ril.c
@@ -6225,6 +6225,77 @@
         }
 
         free(line);
+    } else if (strStartsWith(s, "+REMOTEIDDISCLOSURE")) {
+        RLOGD("starting REMOTEIDDISCLOSURE %s", s);
+        line = p = strdup(s);
+        if (!line) {
+            RLOGE("+REMOTEIDDISCLOSURE unable to allocate memory");
+            return;
+        }
+        if (at_tok_start(&p) < 0) {
+            RLOGE("invalid +REMOTEIDDISCLOSURE command: %s", s);
+            free(line);
+            return;
+        }
+
+        RIL_CellularIdentifierDisclosure disclosure;
+
+        if (at_tok_nextstr(&p, &disclosure.plmn) < 0) {
+            RLOGE("+REMOTEIDDISCLOSURE unable to parse plmn %s", s);
+            return;
+        }
+        if (at_tok_nextint(&p, &disclosure.identifierType) < 0) {
+            RLOGE("+REMOTEIDDISCLOSURE unable to parse identifier %s", s);
+            return;
+        }
+        if (at_tok_nextint(&p, &disclosure.protocolMessage) < 0) {
+            RLOGE("+REMOTEIDDISCLOSURE unable to parse protocol message %s", s);
+            return;
+        }
+        if (at_tok_nextbool(&p, (char*)&disclosure.isEmergency) < 0) {
+            RLOGE("+REMOTEIDDISCLOSURE unable to parse isEmergency %s", s);
+            return;
+        }
+
+        RIL_onUnsolicitedResponse(RIL_UNSOL_CELLULAR_IDENTIFIER_DISCLOSED, (void*)&disclosure,
+                                  sizeof(disclosure));
+        free(line);
+    } else if (strStartsWith(s, "+UPDATESECURITYALGORITHM")) {
+        RLOGD("starting UPDATESECURITYALGORITHM %s", s);
+        line = p = strdup(s);
+        if (!line) {
+            RLOGE("+UPDATESECURITYALGORITHM unable to allocate memory");
+            return;
+        }
+        if (at_tok_start(&p) < 0) {
+            RLOGE("invalid +UPDATESECURITYALGORITHM command: %s", s);
+            free(line);
+            return;
+        }
+
+        RIL_SecurityAlgorithmUpdate update;
+
+        if (at_tok_nextint(&p, &update.connectionEvent) < 0) {
+            RLOGE("+UPDATESECURITYALGORITHM unable to parse connection event %s", s);
+            return;
+        }
+        if (at_tok_nextint(&p, &update.encryption) < 0) {
+            RLOGE("+UPDATESECURITYALGORITHM unable to parse encryption %s", s);
+            return;
+        }
+        if (at_tok_nextint(&p, &update.integrity) < 0) {
+            RLOGE("+UPDATESECURITYALGORITHM unable to parse integrity %s", s);
+            return;
+        }
+        if (at_tok_nextbool(&p, (char*)&update.isUnprotectedEmergency) < 0) {
+            RLOGE("+UPDATESECURITYALGORITHM unable to parse isUnprotectedEmergency %s", s);
+            return;
+        }
+
+        RIL_onUnsolicitedResponse(RIL_UNSOL_SECURITY_ALGORITHM_UPDATED, &update, sizeof(update));
+        free(line);
+    } else {
+        RLOGE("Unexpected unsolicited request: %s", s);
     }
 }
 
diff --git a/guest/hals/vehicle/Android.bp b/guest/hals/vehicle/Android.bp
new file mode 100644
index 0000000..e5ec96c
--- /dev/null
+++ b/guest/hals/vehicle/Android.bp
@@ -0,0 +1,53 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//       http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+filegroup {
+    name: "android.hardware.automotive.vehicle@V3-cf-service.xml",
+    srcs: ["android.hardware.automotive.vehicle@V3-cf-service.xml"],
+}
+
+cc_binary {
+    name: "android.hardware.automotive.vehicle@V3-cf-service",
+    defaults: ["VehicleHalDefaults"],
+    init_rc: ["android.hardware.automotive.vehicle@V3-cf-service.rc"],
+    vendor: true,
+    relative_install_path: "hw",
+    srcs: [
+        "VehicleService.cpp",
+    ],
+    header_libs: [
+        "IVehicleHardware",
+        "vhal_vsockinfo",
+    ],
+    static_libs: [
+        "android.hardware.automotive.vehicle@default-grpc-hardware-lib",
+        "DefaultVehicleHal",
+        "VehicleHalUtils",
+    ],
+    shared_libs: [
+        "libbase",
+        "libcutils",
+        "libgrpc++",
+        "liblog",
+        "libprotobuf-cpp-full",
+    ],
+    cflags: [
+        "-Wno-unused-parameter",
+    ],
+    vintf_fragments: ["android.hardware.automotive.vehicle@V3-cf-service.xml"],
+}
diff --git a/guest/hals/vehicle/VehicleService.cpp b/guest/hals/vehicle/VehicleService.cpp
new file mode 100644
index 0000000..173eea0
--- /dev/null
+++ b/guest/hals/vehicle/VehicleService.cpp
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "DefaultVehicleHal.h"
+#include "GRPCVehicleHardware.h"
+#include "vsockinfo.h"
+
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+#include <cutils/properties.h>
+#include <linux/vm_sockets.h>
+#include <sys/socket.h>
+
+#include <chrono>
+#include <memory>
+#include <utility>
+
+using ::android::hardware::automotive::utils::VsockConnectionInfo;
+using ::android::hardware::automotive::vehicle::DefaultVehicleHal;
+using ::android::hardware::automotive::vehicle::virtualization::
+    GRPCVehicleHardware;
+
+const char* SERVICE_NAME =
+    "android.hardware.automotive.vehicle.IVehicle/default";
+const char* BOOTCONFIG_PORT = "ro.boot.vhal_proxy_server_port";
+
+int main(int /* argc */, char* /* argv */[]) {
+  LOG(INFO) << "Starting thread pool...";
+  if (!ABinderProcess_setThreadPoolMaxThreadCount(4)) {
+    LOG(ERROR) << "Failed to set thread pool max thread count.";
+    return 1;
+  }
+  ABinderProcess_startThreadPool();
+
+  VsockConnectionInfo vsock = {
+      .cid = VMADDR_CID_HOST,
+      .port =
+          static_cast<unsigned int>(property_get_int32(BOOTCONFIG_PORT, -1)),
+  };
+  CHECK(vsock.port >= 0) << "Failed to read port number from: "
+                         << BOOTCONFIG_PORT;
+  std::string vsockStr = vsock.str();
+
+  LOG(INFO) << "Connecting to vsock server at " << vsockStr;
+
+  constexpr auto maxConnectWaitTime = std::chrono::seconds(5);
+  auto hardware = std::make_unique<GRPCVehicleHardware>(vsockStr);
+  if (const auto connected = hardware->waitForConnected(maxConnectWaitTime)) {
+    LOG(INFO) << "Connected to vsock server at " << vsockStr;
+  } else {
+    LOG(INFO)
+        << "Failed to connect to vsock server at " << vsockStr
+        << ", check if it is working, or maybe the server is coming up late.";
+    return 1;
+  }
+
+  auto vhal =
+      ::ndk::SharedRefBase::make<DefaultVehicleHal>(std::move(hardware));
+  LOG(INFO) << "Registering as service...";
+  binder_exception_t err =
+      AServiceManager_addService(vhal->asBinder().get(), SERVICE_NAME);
+  CHECK(err == EX_NONE) << "Failed to register " << SERVICE_NAME
+                        << " service, exception: " << err << ".";
+
+  LOG(INFO) << "Vehicle Service Ready.";
+
+  ABinderProcess_joinThreadPool();
+
+  LOG(INFO) << "Vehicle Service Exiting.";
+
+  return 0;
+}
diff --git a/guest/hals/vehicle/android.hardware.automotive.vehicle@V3-cf-service.rc b/guest/hals/vehicle/android.hardware.automotive.vehicle@V3-cf-service.rc
new file mode 100644
index 0000000..5196c8d
--- /dev/null
+++ b/guest/hals/vehicle/android.hardware.automotive.vehicle@V3-cf-service.rc
@@ -0,0 +1,4 @@
+service vendor.vehicle-hal-trout /vendor/bin/hw/android.hardware.automotive.vehicle@V3-cf-service
+    class early_hal
+    user vehicle_network
+    group system inet
\ No newline at end of file
diff --git a/guest/hals/vehicle/android.hardware.automotive.vehicle@V3-cf-service.xml b/guest/hals/vehicle/android.hardware.automotive.vehicle@V3-cf-service.xml
new file mode 100644
index 0000000..7cf334d
--- /dev/null
+++ b/guest/hals/vehicle/android.hardware.automotive.vehicle@V3-cf-service.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2023, The Android Open Source Project.
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** limitations under the License.
+*/
+-->
+<manifest version="1.0" type="device">
+    <hal format="aidl">
+        <name>android.hardware.automotive.vehicle</name>
+        <version>3</version>
+        <fqname>IVehicle/default</fqname>
+    </hal>
+</manifest>
diff --git a/guest/hals/vehicle/apex/Android.bp b/guest/hals/vehicle/apex/Android.bp
new file mode 100644
index 0000000..f2f01d4
--- /dev/null
+++ b/guest/hals/vehicle/apex/Android.bp
@@ -0,0 +1,45 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//       http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+apex_key {
+    name: "com.android.hardware.automotive.vehicle.test.key",
+    public_key: "com.android.hardware.automotive.vehicle.test.avbpubkey",
+    private_key: "com.android.hardware.automotive.vehicle.test.pem",
+}
+
+prebuilt_etc {
+    name: "com.android.hardware.automotive.vehicle.cf.rc",
+    src: "com.android.hardware.automotive.vehicle.cf.rc",
+    installable: false,
+}
+
+apex {
+    name: "com.android.hardware.automotive.vehicle.cf",
+    manifest: "apex_manifest.json",
+    key: "com.android.hardware.automotive.vehicle.test.key",
+    file_contexts: "file_contexts",
+    updatable: false,
+    soc_specific: true,
+    binaries: [
+        "android.hardware.automotive.vehicle@V3-cf-service",
+    ],
+    prebuilts: [
+        "com.android.hardware.automotive.vehicle.cf.rc",
+    ],
+    vintf_fragments: [":android.hardware.automotive.vehicle@V3-cf-service.xml"],
+}
diff --git a/guest/hals/vehicle/apex/apex_manifest.json b/guest/hals/vehicle/apex/apex_manifest.json
new file mode 100644
index 0000000..69ad500
--- /dev/null
+++ b/guest/hals/vehicle/apex/apex_manifest.json
@@ -0,0 +1,5 @@
+{
+  "name": "com.android.hardware.automotive.vehicle",
+  "vendorBootstrap": true,
+  "version": 1
+}
diff --git a/guest/hals/vehicle/apex/com.android.hardware.automotive.vehicle.cf.rc b/guest/hals/vehicle/apex/com.android.hardware.automotive.vehicle.cf.rc
new file mode 100644
index 0000000..ad2c193
--- /dev/null
+++ b/guest/hals/vehicle/apex/com.android.hardware.automotive.vehicle.cf.rc
@@ -0,0 +1,4 @@
+service vendor.vehicle-cf /apex/com.android.hardware.automotive.vehicle/bin/hw/android.hardware.automotive.vehicle@V3-cf-service
+    class early_hal
+    user vehicle_network
+    group system inet
\ No newline at end of file
diff --git a/guest/hals/vehicle/apex/com.android.hardware.automotive.vehicle.test.avbpubkey b/guest/hals/vehicle/apex/com.android.hardware.automotive.vehicle.test.avbpubkey
new file mode 100644
index 0000000..1cb5351
--- /dev/null
+++ b/guest/hals/vehicle/apex/com.android.hardware.automotive.vehicle.test.avbpubkey
Binary files differ
diff --git a/guest/hals/vehicle/apex/com.android.hardware.automotive.vehicle.test.pem b/guest/hals/vehicle/apex/com.android.hardware.automotive.vehicle.test.pem
new file mode 100644
index 0000000..ad1df2a
--- /dev/null
+++ b/guest/hals/vehicle/apex/com.android.hardware.automotive.vehicle.test.pem
@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+MIIJQQIBADANBgkqhkiG9w0BAQEFAASCCSswggknAgEAAoICAQCrTqi61XNkAJtV
+D2THtSK2LdFv/pxss456mCPjxLJ+vpHnuKce1VPOnR4QG873oPKOWWpU3q4hXtdX
+UCM2tGX8/oC3dKhPjsw0B0IQ3M2QEn/Kn0Afn8jy9rwAgxsRoss/RJTP4j8vucpp
+GGB/Ompox/Lt74LqPMbfQjl697sHnWM9xLRbxat/S0cVMFg29URfkCOy4vWI4bP0
+TsDHYKhKaYzOEGq2eFeTQRKv+mmNpfGNUHKc7meNHVSbyF8sLKFENeRacWUijSW+
+yDTVcuGaNjbL+vrWt0otTI2J2RnQ9CL/g4QwbmG1Kr2i0KaTUrYskbgaWm2NR6mY
+fx4TfDT59CJN1HtxN7kjgW7A3lQr2Zo7jkjIRvv9bWoUODcQxDi7HqWieWgKuIZQ
+n+jsorrm9eES2+hA/V4G/oKVTWc9A+neXuxL5duHtoTnBc1ObddnHGmtBNbm7Ieu
+9Z+OOOgvD93j0B0DvI6ICCleO8rS4/NAdmBl508hHUOzIJOymmS+rzRud6Voeue/
+8VQMTVMEN9W+NZJXd2wyr4Gkq65Q+a16eq7jexOyu2PpkC5gCcJaevwK/QX4eF/M
+oZdwabMF+97F0aI3iP1qDL17EK59aod6bV+fPxwW3HwwFx1dgO72De+Yvn7ZC0K3
+hnX4gLrnPzArmPVWZqQY1nwpjk+evQIDAQABAoICAAVxhe4N61FBra3pw8+Ow3Ab
+i5yt8AWKPRxLKlTKAswBPTyqQvE5YSTfHD3xzEVVIXABil3xB4J684fMPZo26Ei+
+Q5PQyqpghL+gfWpwwb1ywQnMvqpilu2VhorTn7ATRrcXdE0YyN443qQxSJuyXQBA
+XklOQ5Ay5zTf81ijd5d52lXzorGJ2fW9DCCxvRPd/lK1D1VXXesAFQpN2FVdjmty
+6C0CG4n5z/qXZkMeR21fWQofBM1UzAiRZodeuht6vZMfzUZMdSTAbHMgBIHF2UJf
+t6UKsB7TizFeTd8T/XyEzXnmEXTChStPFOCQFcL3rV9EMB/yQKntHYgBfhCrcTl0
+woxLUBoBpaMZ0RXHkaDCapMKbVMC/gvUEz4h+42q523SYByHvwU/jOw+ZzdJYVZ9
+hcWA0+XwaQkKKBe1xYfIwSChX5G33HjaT1Vl5DLYei8OEUvc7q0Mdnjtgj5FQ+Qv
+t/WMSqbw4jUW9Jvgh5Gj/i56uia+d56R7AoVw27DJUKZbXX1wQQq82dwSiPy0zDf
+8kZuXHUR7nBPw0HaVbVsLW+BDe8CDWQCdgTzJws+R8CF/wqLYnGL6ueveJI0POpF
+J6G+7Tn+7ol4983/0/SA/fDpLJOyjLgNpQcZI4Jvjt7r0NJ56wHAHIhxBWw/vXWp
+lcfcdBohRVMGLG4IOCmRAoIBAQDdhhYIJOBauPAKOZYfNwKWeP4tR1E5cs2nEQKJ
+LRp+yENtvgBsVeOs8laUuYBvwwvw/PUDLk37HUPi4DP77RaMEzIJwmrBks12en9m
+Mv+isw/0ztO+qXkxnCyN2TfZZYub43DlBqAXM0Y85MT+Ri0rt/hQbRgKDPq50CRv
+cFexvsrCZfnHXeyBrbGz5B7hRs7tQVNRFu9oEcL4oZ+f1rTqktf8aTI2yV9C6yBK
+qaHGJUneXZYluvYrA666EocmwRchetSEiGmIU1NihyCjiUUW1q2VGuXgqoVHZ6JH
+lyZvJM2gHFjiCZuo8Wn4y+/j/uVjEJFTABINRADbLnDU0iQVAoIBAQDF99rhfqm5
+000p3MThe+Yy7HJsNXi8Jk/n5hrQc+iR46+WHU2dwJb2qLcxDWjOhQ48e/4OpJaW
+8F9jiBb2euGIA72qY/PGKI4lOBvfFXQkckYJYfDo7k6M8R26i+llNORI363U/z/9
++F0gMAdObXRLZk95R8KSKlvLzghLCxLNG/8G6+lCsNgc7cqqpmrQZveApnEVa+0F
+hJI7kZOh/h3DZakh9cfjMFGlF2/xQFiP1IezG2iuHPsBRcrhhoyYrGpjaDIjnZgj
+4SwtSD5SG0iPYSdt76xC6vLWCdRKQX0RwIKykvuq26437wO6Td1xrv0fIHhBKxmG
+IJCDk4UjL3IJAoIBAH727QW57QTSXnHd05cMdmU776KP75PXotsQOr3YpgjoSw1J
+CwEMsTNcqhcPwvdLeTqkIOGz1moIufH8wo6vsm6SSemiDoHn2+7+AqrWrAHaU1ji
+eiYbCCVQ95BNYV8ufjfYwcniZDsn3ifkjquWGSmnhaDDBjYsfj+ldlyQrRJo4dlN
+jT2hLX6dyO3r328KoGsHN/OQC6NAGtlZ+R0peAPCKhy3Rlp0TC8UiCuC0f+5O39B
+cF8rqw+4SprHJ8MkhFdiCQ/1B1dlOrOL8z8H3Btc65w8EGkWEtF8KlyR2yt0ko4j
+8SWMkDTiOPSqHlI0s9B1nHVP4wjLYxo8odq4nokCggEAFnx0rDBK7wSFO0AMTOBZ
+4WbiCFB6ikR/xwNoAE1qGUgXZaGmc0iw3QuE41l2kh6i41PiI5+mSza6Xv/SO+Tx
+QRXLsVLxGYz5uFiLMeep7YndUquBRbPr0C6DBfFmbUx4sZ5WjF8B0cMoMMPD0LC2
+COpyFICGJTwSeS/J2VxEom+PpCWftSeJJKAN6RxF/a30ZvSA28IKhhns0j9S2Y+X
+qd4eO/FC5xdR7sKewHRimtO1Ji7y0PQRvq9CwCMcTSsoWmo3Z7w6z7h/4BTYn1Li
+BwcuB1q14lP1iUJMjrKplh6budTgKSQSwd+wOLUndgY+Ug7KGfzfRVDlmUxkTv7Q
+oQKCAQAoATx473cwOmHJtPeFPnPf085OxV9mf9M5qBzEnx/kUmgMo3vJhTonQFQ+
+10H7a0I0/QgG1SCQCNfkrlyfv9ULwnXauAZD0pLxf774oIkp+bgwBwNImf0izsVB
+FRrGGqNgD5VaYDXUMqCW6SrhcrtCZsAkq1Ve4SFdTm8n9R+ERtQJdi+gXGSgYWWX
+MDvZIMpozXpiBfQzjLMHThlPQGRIZTvaYs0NQrNanqhT9W8iBwnzAkXWaCohKA3e
+cYZKPY0KnBWw+jMSWih/GiWfAtUKMI52xR7ZTv5nG8mtV13zVp01FBCFAG0gxNVw
+eez9BiOhFea4pIiQAieaXTGc4E7o
+-----END PRIVATE KEY-----
diff --git a/guest/hals/vehicle/apex/file_contexts b/guest/hals/vehicle/apex/file_contexts
new file mode 100644
index 0000000..46fb9f6
--- /dev/null
+++ b/guest/hals/vehicle/apex/file_contexts
@@ -0,0 +1,2 @@
+(/.*)?	u:object_r:vendor_file:s0
+/bin/hw/android.hardware.automotive.vehicle@V3-cf-service	u:object_r:hal_vehicle_default_exec:s0
diff --git a/guest/hals/vulkan/Android.bp b/guest/hals/vulkan/Android.bp
index 9e7b110..8d974da 100644
--- a/guest/hals/vulkan/Android.bp
+++ b/guest/hals/vulkan/Android.bp
@@ -34,7 +34,7 @@
                 "vulkan.pastel",
             ],
         },
-    }
+    },
 }
 
 apex {
@@ -58,6 +58,7 @@
         "vulkan.ranchu",
     ],
     prebuilts: [
+        "com.google.cf.vulkan.rc",
         "com.google.cf.vulkan-linker-config",
         "android.hardware.vulkan.level-1.prebuilt.xml",
         "android.hardware.vulkan.compute-0.prebuilt.xml",
@@ -67,6 +68,12 @@
     ],
 }
 
+prebuilt_etc {
+    name: "com.google.cf.vulkan.rc",
+    src: "com.google.cf.vulkan.rc",
+    installable: false,
+}
+
 linker_config {
     name: "com.google.cf.vulkan-linker-config",
     src: "apex_linkerconfig.json",
diff --git a/guest/hals/vulkan/com.google.cf.vulkan.rc b/guest/hals/vulkan/com.google.cf.vulkan.rc
new file mode 100644
index 0000000..bd638cc
--- /dev/null
+++ b/guest/hals/vulkan/com.google.cf.vulkan.rc
@@ -0,0 +1,5 @@
+# `on init` is normally not available for .rc files in vendor apexes
+# But the vulkan apex is okay because it's bootstrap APEX.
+# For bootstrap APEXes, `on init` is the earliest available trigger.
+on init
+  setprop ro.vulkan.apex com.google.cf.vulkan
diff --git a/guest/libs/Android.mk b/guest/libs/Android.mk
deleted file mode 100644
index 428f4b5..0000000
--- a/guest/libs/Android.mk
+++ /dev/null
@@ -1,18 +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.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/guest/libs/wpa_supplicant_8_lib/Android.mk b/guest/libs/wpa_supplicant_8_lib/Android.mk
deleted file mode 100644
index 31179f5..0000000
--- a/guest/libs/wpa_supplicant_8_lib/Android.mk
+++ /dev/null
@@ -1,82 +0,0 @@
-# Copyright (C) 2016 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-
-ifeq ($(WPA_SUPPLICANT_VERSION),VER_0_8_X)
-
-ifneq ($(BOARD_WPA_SUPPLICANT_DRIVER),)
-  CONFIG_DRIVER_$(BOARD_WPA_SUPPLICANT_DRIVER) := y
-endif
-
-# Use a custom libnl on releases before N
-ifeq (0, $(shell test $(PLATFORM_SDK_VERSION) -lt 24; echo $$?))
-EXTERNAL_VSOC_LIBNL_INCLUDE := external/gce/libnl/include
-else
-EXTERNAL_VSOC_LIBNL_INCLUDE :=
-endif
-
-
-WPA_SUPPL_DIR = external/wpa_supplicant_8
-WPA_SRC_FILE :=
-
-include $(WPA_SUPPL_DIR)/wpa_supplicant/android.config
-
-WPA_SUPPL_DIR_INCLUDE = $(WPA_SUPPL_DIR)/src \
-	$(WPA_SUPPL_DIR)/src/common \
-	$(WPA_SUPPL_DIR)/src/drivers \
-	$(WPA_SUPPL_DIR)/src/l2_packet \
-	$(WPA_SUPPL_DIR)/src/utils \
-	$(WPA_SUPPL_DIR)/src/wps \
-	$(WPA_SUPPL_DIR)/wpa_supplicant \
-	$(EXTERNAL_VSOC_LIBNL_INCLUDE)
-
-WPA_SUPPL_DIR_INCLUDE += external/libnl/include
-
-ifdef CONFIG_DRIVER_NL80211
-WPA_SRC_FILE += driver_cmd_nl80211.c
-endif
-
-ifeq ($(TARGET_ARCH),arm)
-# To force sizeof(enum) = 4
-L_CFLAGS += -mabi=aapcs-linux
-endif
-
-ifdef CONFIG_ANDROID_LOG
-L_CFLAGS += -DCONFIG_ANDROID_LOG
-endif
-
-########################
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := lib_driver_cmd_simulated_cf
-LOCAL_VENDOR_MODULE := true
-LOCAL_SHARED_LIBRARIES := libc libcutils
-
-LOCAL_CFLAGS := $(L_CFLAGS) \
-    $(VSOC_VERSION_CFLAGS)
-
-LOCAL_SRC_FILES := $(WPA_SRC_FILE)
-
-LOCAL_C_INCLUDES := \
-  device/google/cuttlefish_common \
-  $(WPA_SUPPL_DIR_INCLUDE)\
-
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-include $(BUILD_STATIC_LIBRARY)
-
-########################
-
-endif
diff --git a/host/commands/assemble_cvd/assemble_cvd.cc b/host/commands/assemble_cvd/assemble_cvd.cc
index 0f73d4d..2f0fc18 100644
--- a/host/commands/assemble_cvd/assemble_cvd.cc
+++ b/host/commands/assemble_cvd/assemble_cvd.cc
@@ -242,6 +242,7 @@
   preserving.insert("persistent_composite_gpt_footer.img");
   preserving.insert("persistent_composite.img");
   preserving.insert("persistent_composite_overlay.img");
+  preserving.insert("pflash.img");
   preserving.insert("uboot_env.img");
   preserving.insert("factory_reset_protected.img");
   preserving.insert("misc.img");
@@ -318,9 +319,6 @@
         const auto log_files =
             CF_EXPECT(DirectoryContents(instance.PerInstanceLogPath("")));
         for (const auto& filename : log_files) {
-          if (filename == "." || filename == "..") {
-            continue;
-          }
           const std::string path = instance.PerInstanceLogPath(filename);
           auto fd = SharedFD::Open(path, O_WRONLY | O_APPEND);
           CF_EXPECT(fd->IsOpen(),
diff --git a/host/commands/assemble_cvd/bootconfig_args.cpp b/host/commands/assemble_cvd/bootconfig_args.cpp
index f7a6d96..0d9739a 100644
--- a/host/commands/assemble_cvd/bootconfig_args.cpp
+++ b/host/commands/assemble_cvd/bootconfig_args.cpp
@@ -201,6 +201,11 @@
           ? "com.android.hardware.gatekeeper.nonsecure"
           : "com.android.hardware.gatekeeper.cf_remote";
 
+  if (config.vhal_proxy_server_port()) {
+    bootconfig_args["androidboot.vhal_proxy_server_port"] =
+        std::to_string(config.vhal_proxy_server_port());
+  }
+
   std::vector<std::string> args = instance.extra_bootconfig_args();
 
   LOG(DEBUG) << "Parsing extra_bootconfig_args of size:" << args.size()
diff --git a/host/commands/assemble_cvd/disk_builder.cpp b/host/commands/assemble_cvd/disk_builder.cpp
index d6ee26b..964b0b4 100644
--- a/host/commands/assemble_cvd/disk_builder.cpp
+++ b/host/commands/assemble_cvd/disk_builder.cpp
@@ -196,6 +196,7 @@
   }
 
   CF_EXPECT(vm_manager_ != VmmMode::kUnknown);
+  // TODO: b/346855591 - run with QEMU when crosvm block device is integrated
   if (vm_manager_ == VmmMode::kCrosvm) {
     CF_EXPECT(!header_path_.empty(), "No header path");
     CF_EXPECT(!footer_path_.empty(), "No footer path");
diff --git a/host/commands/assemble_cvd/disk_flags.cc b/host/commands/assemble_cvd/disk_flags.cc
index d7fac96..f7144f9 100644
--- a/host/commands/assemble_cvd/disk_flags.cc
+++ b/host/commands/assemble_cvd/disk_flags.cc
@@ -613,6 +613,20 @@
   return {};
 }
 
+Result<void> InitializePflash(
+    const CuttlefishConfig::InstanceSpecific& instance) {
+  if (FileExists(instance.pflash_path())) {
+    return {};
+  }
+
+  auto boot_size_mb = FileSize(instance.bootloader()) / (1 << 20);
+
+  // Pad out bootloader space to 4MB
+  CF_EXPECTF(CreateBlankImage(instance.pflash_path(), 4 - boot_size_mb, "none"),
+             "Failed to create '{}'", instance.pflash_path());
+  return {};
+}
+
 Result<void> InitializeSdCard(
     const CuttlefishConfig& config,
     const CuttlefishConfig::InstanceSpecific& instance) {
@@ -693,7 +707,8 @@
       .install(AutoSetup<GeneratePersistentBootconfig>::Component)
       .install(AutoSetup<GeneratePersistentVbmeta>::Component)
       .install(AutoSetup<InitializeInstanceCompositeDisk>::Component)
-      .install(AutoSetup<InitializeDataImage>::Component);
+      .install(AutoSetup<InitializeDataImage>::Component)
+      .install(AutoSetup<InitializePflash>::Component);
 }
 
 Result<void> DiskImageFlagsVectorization(CuttlefishConfig& config, const FetcherConfig& fetcher_config) {
diff --git a/host/commands/assemble_cvd/flags.cc b/host/commands/assemble_cvd/flags.cc
index f872dc1..078a8b6 100644
--- a/host/commands/assemble_cvd/flags.cc
+++ b/host/commands/assemble_cvd/flags.cc
@@ -221,6 +221,16 @@
 DEFINE_bool(enable_automotive_proxy, CF_DEFAULTS_ENABLE_AUTOMOTIVE_PROXY,
             "Enable the automotive proxy service on the host.");
 
+DEFINE_bool(enable_vhal_proxy_server, CF_DEFAULTS_ENABLE_VHAL_PROXY_SERVER,
+            "Enable the vhal proxy service on the host.");
+DEFINE_int32(vhal_proxy_server_instance_num,
+             CF_DEFAULTS_VHAL_PROXY_SERVER_INSTANCE_NUM,
+             "If it is greater than 0, use an existing vhal proxy server "
+             "instance which is "
+             "launched from cuttlefish instance "
+             "with vhal_proxy_server_instance_num. Else, launch a new vhal "
+             "proxy server instance");
+
 /**
  * crosvm sandbox feature requires /var/empty and seccomp directory
  *
@@ -508,6 +518,9 @@
     fail_fast, CF_DEFAULTS_FAIL_FAST ? "true" : "false",
     "Whether to exit when a heuristic predicts the boot will not complete");
 
+DEFINE_vec(vhost_user_block, CF_DEFAULTS_VHOST_USER_BLOCK ? "true" : "false",
+           "(experimental) use crosvm vhost-user block device implementation ");
+
 DECLARE_string(assembly_dir);
 DECLARE_string(boot_image);
 DECLARE_string(system_image_dir);
@@ -669,6 +682,11 @@
     guest_config.gfxstream_supported =
         res.ok() && res.value() == "supported";
 
+    auto res_bgra_support = GetAndroidInfoConfig(instance_android_info_txt,
+                                                 "supports_bgra_framebuffers");
+    guest_config.supports_bgra_framebuffers =
+        res_bgra_support.value_or("") == "true";
+
     auto res_vhost_user_vsock =
         GetAndroidInfoConfig(instance_android_info_txt, "vhost_user_vsock");
     guest_config.vhost_user_vsock = res_vhost_user_vsock.value_or("") == "true";
@@ -1214,6 +1232,9 @@
 
   std::vector<bool> fail_fast_vec = CF_EXPECT(GET_FLAG_BOOL_VALUE(fail_fast));
 
+  std::vector<bool> vhost_user_block_vec =
+      CF_EXPECT(GET_FLAG_BOOL_VALUE(vhost_user_block));
+
   std::vector<std::string> mcu_config_vec = CF_EXPECT(GET_FLAG_STR_VALUE(mcu_config_path));
 
   std::string default_enable_sandbox = "";
@@ -1282,6 +1303,16 @@
 
   tmp_config_obj.set_host_sandbox(FLAGS_enable_host_sandbox);
 
+  auto vhal_proxy_server_instance_num = *instance_nums.begin() - 1;
+  if (FLAGS_vhal_proxy_server_instance_num > 0) {
+    vhal_proxy_server_instance_num = FLAGS_vhal_proxy_server_instance_num - 1;
+  }
+  tmp_config_obj.set_vhal_proxy_server_port(9300 +
+                                            vhal_proxy_server_instance_num);
+  LOG(DEBUG) << "launch vhal proxy server: "
+             << (FLAGS_enable_vhal_proxy_server &&
+                 vhal_proxy_server_instance_num <= 0);
+
   // Environment specific configs
   // Currently just setting for the default environment
   auto environment_name =
@@ -1319,6 +1350,9 @@
     mutable_env_config.set_start_wmediumd(false);
   }
 
+  const auto graphics_availability =
+      GetGraphicsAvailabilityWithSubprocessCheck();
+
   // Instance specific configs
   bool is_first_instance = true;
   int instance_index = 0;
@@ -1449,6 +1483,11 @@
     instance.set_blank_data_image_mb(blank_data_image_mb_vec[instance_index]);
     instance.set_gdb_port(gdb_port_vec[instance_index]);
     instance.set_fail_fast(fail_fast_vec[instance_index]);
+    if (vhost_user_block_vec[instance_index]) {
+      CF_EXPECT_EQ(tmp_config_obj.vm_manager(), VmmMode::kCrosvm,
+                   "vhost-user block only supported on crosvm");
+    }
+    instance.set_vhost_user_block(vhost_user_block_vec[instance_index]);
 
     std::optional<std::vector<CuttlefishConfig::DisplayConfig>>
         binding_displays_configs;
@@ -1563,7 +1602,8 @@
 
     // gpu related settings
     const std::string gpu_mode = CF_EXPECT(ConfigureGpuSettings(
-        gpu_mode_vec[instance_index], gpu_vhost_user_mode_vec[instance_index],
+        graphics_availability, gpu_mode_vec[instance_index],
+        gpu_vhost_user_mode_vec[instance_index],
         gpu_renderer_features_vec[instance_index],
         gpu_context_types_vec[instance_index], vmm_mode,
         guest_configs[instance_index], instance));
@@ -1605,6 +1645,9 @@
     instance.set_gpu_context_types(gpu_context_types_vec[instance_index]);
     instance.set_guest_vulkan_driver(guest_vulkan_driver_vec[instance_index]);
 
+    instance.set_guest_uses_bgra_framebuffers(
+        guest_configs[instance_index].supports_bgra_framebuffers);
+
     if (!frames_socket_path_vec[instance_index].empty()) {
       instance.set_frames_socket_path(frames_socket_path_vec[instance_index]);
     } else {
@@ -1739,6 +1782,9 @@
 
     instance.set_start_pica(is_first_instance && !is_uwb_netsim &&
                             FLAGS_pica_instance_num <= 0);
+    instance.set_start_vhal_proxy_server(
+        is_first_instance && FLAGS_enable_vhal_proxy_server &&
+        FLAGS_vhal_proxy_server_instance_num <= 0);
 
     // TODO(b/288987294) Remove this when separating environment is done
     bool instance_start_wmediumd = is_first_instance && start_wmediumd;
diff --git a/host/commands/assemble_cvd/flags.h b/host/commands/assemble_cvd/flags.h
index 8cd519d..8ab0fa6 100644
--- a/host/commands/assemble_cvd/flags.h
+++ b/host/commands/assemble_cvd/flags.h
@@ -35,6 +35,7 @@
   std::string android_version_number;
   bool gfxstream_supported = false;
   bool vhost_user_vsock = false;
+  bool supports_bgra_framebuffers = false;
 };
 
 Result<std::vector<GuestConfig>> GetGuestConfigAndSetDefaults();
diff --git a/host/commands/assemble_cvd/flags_defaults.h b/host/commands/assemble_cvd/flags_defaults.h
index 93618a7..f8a4c99 100644
--- a/host/commands/assemble_cvd/flags_defaults.h
+++ b/host/commands/assemble_cvd/flags_defaults.h
@@ -182,6 +182,10 @@
 // Automotive Proxy default parameter
 #define CF_DEFAULTS_ENABLE_AUTOMOTIVE_PROXY false
 
+// Vhal Proxy Server default parameter
+#define CF_DEFAULTS_ENABLE_VHAL_PROXY_SERVER false
+#define CF_DEFAULTS_VHAL_PROXY_SERVER_INSTANCE_NUM 0
+
 // Bluetooth default parameters
 #define CF_DEFAULTS_ENABLE_HOST_BLUETOOTH true
 #define CF_DEFAULTS_ROOTCANAL_INSTANCE_NUM 0
@@ -245,3 +249,7 @@
 
 // Whether to exit when heuristics predict the boot will not complete
 #define CF_DEFAULTS_FAIL_FAST true
+
+// Whether to use the crosvm vhost-user block device implementation with QEMU
+// TODO: b/346855591 - default to `true`
+#define CF_DEFAULTS_VHOST_USER_BLOCK false
diff --git a/host/commands/assemble_cvd/graphics_flags.cc b/host/commands/assemble_cvd/graphics_flags.cc
index d843f51..c4275aa 100644
--- a/host/commands/assemble_cvd/graphics_flags.cc
+++ b/host/commands/assemble_cvd/graphics_flags.cc
@@ -352,25 +352,6 @@
   return CF_ERR("Graphics detector unavailable for host arch.");
 }
 
-CF_UNUSED_ON_MACOS
-Result<const gfxstream::proto::GraphicsAvailability>
-GetGraphicsAvailabilityWithSubprocessCheck() {
-  Command graphics_detector_cmd(CF_EXPECT(GraphicsDetectorBinaryPath()));
-  std::string graphics_detector_stdout;
-  auto ret = RunWithManagedStdio(std::move(graphics_detector_cmd), nullptr,
-                                 &graphics_detector_stdout, nullptr);
-  CF_EXPECT_EQ(ret, 0, "Failed to run graphics detector, bad return value");
-
-  gfxstream::proto::GraphicsAvailability availability;
-  google::protobuf::TextFormat::Parser parser;
-  if (!parser.ParseFromString(graphics_detector_stdout, &availability)) {
-    return CF_ERR("Failed to parse graphics detector stdout: "
-                  << graphics_detector_stdout);
-  }
-
-  return availability;
-}
-
 bool IsAmdGpu(const gfxstream::proto::GraphicsAvailability& availability) {
   return (availability.has_egl() &&
           ((availability.egl().has_gles2_availability() &&
@@ -481,18 +462,57 @@
   return {};
 }
 
-}  // namespace
-
 static std::unordered_set<std::string> kSupportedGpuContexts{
     "gfxstream-vulkan", "gfxstream-composer", "cross-domain", "magma"};
 
+}  // namespace
+
+gfxstream::proto::GraphicsAvailability
+GetGraphicsAvailabilityWithSubprocessCheck() {
+#ifdef __APPLE__
+  return {};
+#else
+  auto graphics_detector_binary_result = GraphicsDetectorBinaryPath();
+  if (!graphics_detector_binary_result.ok()) {
+    LOG(ERROR) << "Failed to run graphics detector, graphics detector path "
+               << " not available: "
+               << graphics_detector_binary_result.error().FormatForEnv()
+               << ". Assuming no availability.";
+    return {};
+  }
+
+  Command graphics_detector_cmd(graphics_detector_binary_result.value());
+  std::string graphics_detector_stdout;
+  auto ret = RunWithManagedStdio(std::move(graphics_detector_cmd), nullptr,
+                                 &graphics_detector_stdout, nullptr);
+  if (ret != 0) {
+    LOG(ERROR) << "Failed to run graphics detector, bad return value: " << ret
+               << ". Assuming no availability.";
+    return {};
+  }
+
+  gfxstream::proto::GraphicsAvailability availability;
+  google::protobuf::TextFormat::Parser parser;
+  if (!parser.ParseFromString(graphics_detector_stdout, &availability)) {
+    LOG(ERROR) << "Failed to parse graphics detector stdout: "
+               << graphics_detector_stdout << ". Assuming no availability.";
+    return {};
+  }
+
+  LOG(DEBUG) << "Host Graphics Availability:" << availability.DebugString();
+  return availability;
+#endif
+}
+
 Result<std::string> ConfigureGpuSettings(
+    const gfxstream::proto::GraphicsAvailability& graphics_availability,
     const std::string& gpu_mode_arg, const std::string& gpu_vhost_user_mode_arg,
     const std::string& gpu_renderer_features_arg,
     std::string& gpu_context_types_arg, VmmMode vmm,
     const GuestConfig& guest_config,
     CuttlefishConfig::MutableInstanceSpecific& instance) {
 #ifdef __APPLE__
+  (void)graphics_availability;
   (void)gpu_vhost_user_mode_arg;
   (void)vmm;
   (void)guest_config;
@@ -506,20 +526,6 @@
   instance.set_gpu_mode(gpu_mode);
   instance.set_enable_gpu_vhost_user(false);
 #else
-  gfxstream::proto::GraphicsAvailability graphics_availability;
-
-  auto graphics_availability_result =
-      GetGraphicsAvailabilityWithSubprocessCheck();
-  if (!graphics_availability_result.ok()) {
-    LOG(ERROR) << "Failed to get graphics availability: "
-               << graphics_availability_result.error().Message()
-               << ". Assuming none.";
-  } else {
-    graphics_availability = graphics_availability_result.value();
-    LOG(DEBUG) << "Host Graphics Availability:"
-               << graphics_availability.DebugString();
-  }
-
   const std::string gpu_mode = CF_EXPECT(
       SelectGpuMode(gpu_mode_arg, vmm, guest_config, graphics_availability));
   const bool enable_gpu_vhost_user =
diff --git a/host/commands/assemble_cvd/graphics_flags.h b/host/commands/assemble_cvd/graphics_flags.h
index 8329724..f5d1ce8 100644
--- a/host/commands/assemble_cvd/graphics_flags.h
+++ b/host/commands/assemble_cvd/graphics_flags.h
@@ -17,6 +17,8 @@
 
 #include <string>
 
+#include <GraphicsDetector.pb.h>
+
 #include "common/libs/utils/result.h"
 #include "host/commands/assemble_cvd/flags.h"
 #include "host/libs/config/config_utils.h"
@@ -24,7 +26,11 @@
 
 namespace cuttlefish {
 
+gfxstream::proto::GraphicsAvailability
+GetGraphicsAvailabilityWithSubprocessCheck();
+
 Result<std::string> ConfigureGpuSettings(
+    const gfxstream::proto::GraphicsAvailability& graphics_availability,
     const std::string& gpu_mode_arg, const std::string& gpu_vhost_user_mode_arg,
     const std::string& gpu_renderer_features_arg,
     std::string& gpu_context_types_arg, VmmMode vmm,
diff --git a/host/commands/assemble_cvd/misc_info.cc b/host/commands/assemble_cvd/misc_info.cc
index 292258b..2548a5e 100644
--- a/host/commands/assemble_cvd/misc_info.cc
+++ b/host/commands/assemble_cvd/misc_info.cc
@@ -16,22 +16,136 @@
 #include "misc_info.h"
 
 #include <algorithm>
+#include <array>
+#include <memory>
+#include <set>
 #include <string>
+#include <string_view>
+#include <unordered_set>
 #include <vector>
 
 #include <android-base/logging.h>
-#include <android-base/stringprintf.h>
+#include <android-base/parseint.h>
 #include <android-base/strings.h>
+#include <fmt/format.h>
 
+#include "common/libs/fs/shared_buf.h"
+#include "common/libs/fs/shared_fd.h"
 #include "common/libs/utils/contains.h"
 #include "common/libs/utils/result.h"
+#include "host/libs/avb/avb.h"
+#include "host/libs/config/known_paths.h"
 
 namespace cuttlefish {
 namespace {
 
+constexpr char kAvbVbmetaAlgorithm[] = "avb_vbmeta_algorithm";
+constexpr char kAvbVbmetaArgs[] = "avb_vbmeta_args";
+constexpr char kAvbVbmetaKeyPath[] = "avb_vbmeta_key_path";
 constexpr char kDynamicPartitions[] = "dynamic_partition_list";
 constexpr char kGoogleDynamicPartitions[] = "google_dynamic_partitions";
+constexpr char kRollbackIndexSuffix[] = "_rollback_index_location";
+constexpr char kSuperBlockDevices[] = "super_block_devices";
 constexpr char kSuperPartitionGroups[] = "super_partition_groups";
+constexpr char kUseDynamicPartitions[] = "use_dynamic_partitions";
+constexpr char kRsa2048Algorithm[] = "SHA256_RSA2048";
+constexpr char kRsa4096Algorithm[] = "SHA256_RSA4096";
+constexpr std::array kNonPartitionKeysToMerge = {
+    "ab_update", "default_system_dev_certificate"};
+// based on build/make/tools/releasetools/common.py:AVB_PARTITIONS
+constexpr std::array kVbmetaPartitions = {"boot",
+                                          "init_boot",
+                                          "odm",
+                                          "odm_dlkm",
+                                          "vbmeta_system",
+                                          "vbmeta_system_dlkm",
+                                          "vbmeta_vendor_dlkm",
+                                          "vendor",
+                                          "vendor_boot"};
+
+Result<std::string> GetExpected(const MiscInfo& misc_info,
+                                const std::string& key) {
+  auto lookup = misc_info.find(key);
+  CF_EXPECTF(lookup != misc_info.end(),
+             "Unable to retrieve expected value from key: {}", key);
+  return lookup->second;
+}
+
+std::string MergePartitionLists(const std::string& vendor,
+                                const std::string& system,
+                                const std::set<std::string>& extracted_images) {
+  const std::string full_string = fmt::format("{} {}", vendor, system);
+  const std::vector<std::string> full_list =
+      android::base::Tokenize(full_string, " ");
+  // std::set removes duplicates and orders the elements, which we want
+  const std::set<std::string> full_set(full_list.begin(), full_list.end());
+  std::set<std::string> filtered_set;
+  std::set_intersection(full_set.cbegin(), full_set.cend(),
+                        extracted_images.cbegin(), extracted_images.cend(),
+                        std::inserter(filtered_set, filtered_set.begin()));
+  return android::base::Join(filtered_set, " ");
+}
+
+std::string GetPartitionList(const MiscInfo& vendor_info,
+                             const MiscInfo& system_info,
+                             const std::string& key,
+                             const std::set<std::string>& extracted_images) {
+  std::string vendor_list = GetExpected(vendor_info, key).value_or("");
+  std::string system_list = GetExpected(system_info, key).value_or("");
+  return MergePartitionLists(vendor_list, system_list, extracted_images);
+}
+
+std::vector<std::string> GeneratePartitionKeys(const std::string& name) {
+  std::vector<std::string> result;
+  result.emplace_back("avb_" + name);
+  result.emplace_back("avb_" + name + "_algorithm");
+  result.emplace_back("avb_" + name + "_key_path");
+  result.emplace_back("avb_" + name + kRollbackIndexSuffix);
+  result.emplace_back("avb_" + name + "_hashtree_enable");
+  result.emplace_back("avb_" + name + "_add_hashtree_footer_args");
+  result.emplace_back(name + "_disable_sparse");
+  result.emplace_back("building_" + name + "_image");
+  auto fs_type_key = name + "_fs_type";
+  if (name == "system") {
+    fs_type_key = "fs_type";
+  }
+  result.emplace_back(fs_type_key);
+  return result;
+}
+
+Result<int> ResolveRollbackIndexConflicts(
+    const std::string& index_string,
+    const std::unordered_set<int> used_indices) {
+  int index;
+  CF_EXPECTF(android::base::ParseInt(index_string, &index),
+             "Unable to parse value {} to string.  Maybe a wrong or bad value "
+             "read for the rollback index?",
+             index_string);
+  while (Contains(used_indices, index)) {
+    ++index;
+  }
+  return index;
+}
+
+Result<std::string> GetKeyPath(const std::string_view algorithm) {
+  if (algorithm == kRsa4096Algorithm) {
+    return TestKeyRsa4096();
+  } else if (algorithm == kRsa2048Algorithm) {
+    return TestKeyRsa2048();
+  } else {
+    return CF_ERR("Unexpected algorithm.  No key available.");
+  }
+}
+
+Result<std::string> GetPubKeyPath(const std::string_view algorithm) {
+  if (algorithm == kRsa4096Algorithm) {
+    return TestPubKeyRsa4096();
+  } else if (algorithm == kRsa2048Algorithm) {
+    return TestPubKeyRsa2048();
+  } else {
+    return CF_ERR("Unexpected algorithm.  No key available.");
+  }
+}
 
 }  // namespace
 
@@ -61,73 +175,158 @@
   return misc_info;
 }
 
-std::string WriteMiscInfo(const MiscInfo& misc_info) {
-  std::stringstream out;
+Result<void> WriteMiscInfo(const MiscInfo& misc_info,
+                           const std::string& output_path) {
+  std::stringstream file_content;
   for (const auto& entry : misc_info) {
-    out << entry.first << "=" << entry.second << "\n";
+    file_content << entry.first << "=" << entry.second << "\n";
   }
-  return out.str();
+
+  SharedFD output_file = SharedFD::Creat(output_path.c_str(), 0644);
+  CF_EXPECT(output_file->IsOpen(),
+            "Failed to open output misc file: " << output_file->StrError());
+
+  CF_EXPECT(
+      WriteAll(output_file, file_content.str()) >= 0,
+      "Failed to write output misc file contents: " << output_file->StrError());
+  return {};
 }
 
-std::vector<std::string> SuperPartitionComponents(const MiscInfo& info) {
-  auto value_it = info.find(kDynamicPartitions);
-  if (value_it == info.end()) {
-    return {};
-  }
-  auto components = android::base::Split(value_it->second, " ");
-  for (auto& component : components) {
-    component = android::base::Trim(component);
-  }
-  components.erase(std::remove(components.begin(), components.end(), ""),
-                   components.end());
-  return components;
-}
-
-bool SetSuperPartitionComponents(const std::vector<std::string>& components,
-                                 MiscInfo* misc_info) {
-  auto super_partition_groups = misc_info->find(kSuperPartitionGroups);
-  if (super_partition_groups == misc_info->end()) {
-    LOG(ERROR) << "Failed to find super partition groups in misc_info";
-    return false;
-  }
-
-  // Remove all existing update groups in misc_info
-  auto update_groups =
-      android::base::Split(super_partition_groups->second, " ");
-  for (const auto& group_name : update_groups) {
-    auto partition_list = android::base::StringPrintf("super_%s_partition_list",
-                                                      group_name.c_str());
-    auto partition_size =
-        android::base::StringPrintf("super_%s_group_size", group_name.c_str());
-    for (const auto& key : {partition_list, partition_size}) {
-      auto it = misc_info->find(key);
-      if (it == misc_info->end()) {
-        LOG(ERROR) << "Failed to find " << key << " in misc_info";
-        return false;
-      }
-      misc_info->erase(it);
+// based on build/make/tools/releasetools/merge/merge_target_files.py
+Result<MiscInfo> GetCombinedDynamicPartitions(
+    const MiscInfo& vendor_info, const MiscInfo& system_info,
+    const std::set<std::string>& extracted_images) {
+  auto vendor_use_dp =
+      CF_EXPECT(GetExpected(vendor_info, kUseDynamicPartitions));
+  CF_EXPECTF(vendor_use_dp == "true", "Vendor build must have {}=true",
+             kUseDynamicPartitions);
+  auto system_use_dp =
+      CF_EXPECT(GetExpected(system_info, kUseDynamicPartitions));
+  CF_EXPECTF(system_use_dp == "true", "System build must have {}=true",
+             kUseDynamicPartitions);
+  MiscInfo result;
+  // copy where both files are equal
+  for (const auto& key_val : vendor_info) {
+    const auto value_result = GetExpected(system_info, key_val.first);
+    if (value_result.ok() && *value_result == key_val.second) {
+      result[key_val.first] = key_val.second;
     }
   }
 
-  // For merged target-file, put all dynamic partitions under the
-  // google_dynamic_partitions update group.
-  // TODO(xunchang) use different update groups for system and vendor images.
-  (*misc_info)[kDynamicPartitions] = android::base::Join(components, " ");
-  (*misc_info)[kSuperPartitionGroups] = kGoogleDynamicPartitions;
-  std::string partitions_list_key = android::base::StringPrintf(
-      "super_%s_partition_list", kGoogleDynamicPartitions);
-  (*misc_info)[partitions_list_key] = android::base::Join(components, " ");
+  result[kDynamicPartitions] = GetPartitionList(
+      vendor_info, system_info, kDynamicPartitions, extracted_images);
 
-  // Use the entire super partition as the group size
-  std::string group_size_key = android::base::StringPrintf(
-      "super_%s_group_size", kGoogleDynamicPartitions);
-  auto super_size_it = misc_info->find("super_partition_size");
-  if (super_size_it == misc_info->end()) {
-    LOG(ERROR) << "Failed to find super partition size";
-    return false;
+  const auto block_devices_result =
+      GetExpected(vendor_info, kSuperBlockDevices);
+  if (block_devices_result.ok()) {
+    result[kSuperBlockDevices] = *block_devices_result;
+    for (const auto& block_device :
+         android::base::Tokenize(result[kSuperBlockDevices], " ")) {
+      const auto key = "super_" + block_device + "_device_size";
+      result[key] = CF_EXPECT(GetExpected(vendor_info, key));
+    }
   }
-  (*misc_info)[group_size_key] = super_size_it->second;
-  return true;
+
+  result[kSuperPartitionGroups] =
+      CF_EXPECT(GetExpected(vendor_info, kSuperPartitionGroups));
+  for (const auto& group :
+       android::base::Tokenize(result[kSuperPartitionGroups], " ")) {
+    const auto group_size_key = "super_" + group + "_group_size";
+    result[group_size_key] =
+        CF_EXPECT(GetExpected(vendor_info, group_size_key));
+
+    const auto partition_list_key = "super_" + group + "_partition_list";
+    result[partition_list_key] = GetPartitionList(
+        vendor_info, system_info, partition_list_key, extracted_images);
+  }
+
+  // TODO(chadreynolds): add vabc_cow_version logic if we need to support older
+  // builds
+  for (const auto& key :
+       {"virtual_ab", "virtual_ab_retrofit", "lpmake", "super_metadata_device",
+        "super_partition_error_limit", "super_partition_size"}) {
+    const auto value_result = GetExpected(vendor_info, key);
+    if (value_result.ok()) {
+      result[key] = *value_result;
+    }
+  }
+  return std::move(result);
+}
+
+Result<MiscInfo> MergeMiscInfos(
+    const MiscInfo& vendor_info, const MiscInfo& system_info,
+    const MiscInfo& combined_dp_info,
+    const std::vector<std::string>& system_partitions) {
+  // the combined misc info uses the vendor values as defaults
+  MiscInfo result = vendor_info;
+  std::unordered_set<int> used_indices;
+  for (const auto& partition : system_partitions) {
+    for (const auto& key : GeneratePartitionKeys(partition)) {
+      if (!Contains(system_info, key)) {
+        continue;
+      }
+      auto system_value = system_info.find(key)->second;
+      // avb_<partition>_rollback_index_location values can conflict across
+      // different builds
+      if (android::base::EndsWith(key, kRollbackIndexSuffix)) {
+        const auto index = CF_EXPECT(
+            ResolveRollbackIndexConflicts(system_value, used_indices));
+        used_indices.insert(index);
+        system_value = std::to_string(index);
+      }
+      result[key] = system_value;
+    }
+  }
+  for (const auto& key : kNonPartitionKeysToMerge) {
+    const auto value_result = GetExpected(system_info, key);
+    if (value_result.ok()) {
+      result[key] = *value_result;
+    }
+  }
+  for (const auto& key_val : combined_dp_info) {
+    result[key_val.first] = key_val.second;
+  }
+  return std::move(result);
+}
+
+Result<VbmetaArgs> GetVbmetaArgs(const MiscInfo& misc_info,
+                                 const std::string& image_path) {
+  // The key_path value should exist, but it is a build system path
+  // We use a host artifacts relative path instead
+  CF_EXPECT(Contains(misc_info, kAvbVbmetaKeyPath));
+  const auto algorithm = CF_EXPECT(GetExpected(misc_info, kAvbVbmetaAlgorithm));
+  auto result = VbmetaArgs{
+      .algorithm = algorithm,
+      .key_path = CF_EXPECT(GetKeyPath(algorithm)),
+  };
+  // must split and add --<flag> <arg> arguments(non-equals format) separately
+  // due to how Command.AddParameter handles each argument
+  const auto extra_args_result = GetExpected(misc_info, kAvbVbmetaArgs);
+  if (extra_args_result.ok()) {
+    for (const auto& arg : android::base::Tokenize(*extra_args_result, " ")) {
+      result.extra_arguments.emplace_back(arg);
+    }
+  }
+
+  for (const auto& partition : kVbmetaPartitions) {
+    // The key_path value should exist, but it is a build system path
+    // We use a host artifacts relative path instead
+    if (Contains(misc_info, fmt::format("avb_{}_key_path", partition))) {
+      const auto partition_algorithm = CF_EXPECT(
+          GetExpected(misc_info, fmt::format("avb_{}_algorithm", partition)));
+      result.chained_partitions.emplace_back(ChainPartition{
+          .name = partition,
+          .rollback_index = CF_EXPECT(GetExpected(
+              misc_info,
+              fmt::format("avb_{}_rollback_index_location", partition))),
+          .key_path = CF_EXPECT(GetPubKeyPath(partition_algorithm)),
+      });
+    } else {
+      result.included_partitions.emplace_back(
+          fmt::format("{}/IMAGES/{}.img", image_path, partition));
+    }
+  }
+  return result;
 }
 
 } // namespace cuttlefish
diff --git a/host/commands/assemble_cvd/misc_info.h b/host/commands/assemble_cvd/misc_info.h
index f832a4c..731a5ba 100644
--- a/host/commands/assemble_cvd/misc_info.h
+++ b/host/commands/assemble_cvd/misc_info.h
@@ -16,20 +16,37 @@
 #pragma once
 
 #include <map>
+#include <set>
 #include <string>
-#include <vector>
 
 #include "common/libs/utils/result.h"
+#include "host/libs/avb/avb.h"
 
 namespace cuttlefish {
 
+// TODO(chadreynolds): rename MiscInfo to more generic KeyValueFile since this
+// logic is processing multiple filetypes now
 using MiscInfo = std::map<std::string, std::string>;
 
-Result<MiscInfo> ParseMiscInfo(const std::string& file_contents);
-std::string WriteMiscInfo(const MiscInfo& info);
+struct VbmetaArgs {
+  std::string algorithm;
+  std::string key_path;
+  std::vector<ChainPartition> chained_partitions;
+  std::vector<std::string> included_partitions;
+  std::vector<std::string> extra_arguments;
+};
 
-std::vector<std::string> SuperPartitionComponents(const MiscInfo&);
-bool SetSuperPartitionComponents(const std::vector<std::string>& components,
-                                 MiscInfo* misc_info);
+Result<MiscInfo> ParseMiscInfo(const std::string& file_contents);
+Result<void> WriteMiscInfo(const MiscInfo& misc_info,
+                           const std::string& output_path);
+Result<MiscInfo> GetCombinedDynamicPartitions(
+    const MiscInfo& vendor_info, const MiscInfo& system_info,
+    const std::set<std::string>& extracted_images);
+Result<MiscInfo> MergeMiscInfos(
+    const MiscInfo& vendor_info, const MiscInfo& system_info,
+    const MiscInfo& combined_dp_info,
+    const std::vector<std::string>& system_partitions);
+Result<VbmetaArgs> GetVbmetaArgs(const MiscInfo& misc_info,
+                                 const std::string& image_path);
 
 } // namespace cuttlefish
diff --git a/host/commands/assemble_cvd/super_image_mixer.cc b/host/commands/assemble_cvd/super_image_mixer.cc
index b59d163..ee23244 100644
--- a/host/commands/assemble_cvd/super_image_mixer.cc
+++ b/host/commands/assemble_cvd/super_image_mixer.cc
@@ -21,42 +21,40 @@
 #include <array>
 #include <memory>
 #include <string>
+#include <string_view>
 #include <unordered_set>
+#include <utility>
+#include <vector>
 
 #include <android-base/strings.h>
 #include <android-base/logging.h>
 
-#include "common/libs/fs/shared_buf.h"
 #include "common/libs/utils/archive.h"
 #include "common/libs/utils/contains.h"
 #include "common/libs/utils/files.h"
 #include "common/libs/utils/result.h"
 #include "common/libs/utils/subprocess.h"
 #include "host/commands/assemble_cvd/misc_info.h"
+#include "host/libs/avb/avb.h"
 #include "host/libs/config/config_utils.h"
 #include "host/libs/config/cuttlefish_config.h"
 #include "host/libs/config/fetcher_config.h"
+#include "host/libs/config/known_paths.h"
 
 namespace cuttlefish {
 namespace {
 
 constexpr char kMiscInfoPath[] = "META/misc_info.txt";
+constexpr char kDynamicPartitionsPath[] = "META/dynamic_partitions_info.txt";
 constexpr std::array kVendorTargetImages = {
-    "IMAGES/boot.img",
-    "IMAGES/dtbo.img",
-    "IMAGES/init_boot.img",
-    "IMAGES/odm.img",
-    "IMAGES/odm_dlkm.img",
-    "IMAGES/recovery.img",
-    "IMAGES/system_dlkm.img",
-    "IMAGES/userdata.img",
-    "IMAGES/vbmeta.img",
-    "IMAGES/vbmeta_system_dlkm.img",
-    "IMAGES/vbmeta_vendor.img",
-    "IMAGES/vbmeta_vendor_dlkm.img",
-    "IMAGES/vendor.img",
-    "IMAGES/vendor_dlkm.img",
-    "IMAGES/vendor_kernel_boot.img",
+    "IMAGES/boot.img",          "IMAGES/dtbo.img",
+    "IMAGES/init_boot.img",     "IMAGES/odm.img",
+    "IMAGES/odm_dlkm.img",      "IMAGES/recovery.img",
+    "IMAGES/system_dlkm.img",   "IMAGES/userdata.img",
+    "IMAGES/vbmeta.img",        "IMAGES/vbmeta_system_dlkm.img",
+    "IMAGES/vbmeta_vendor.img", "IMAGES/vbmeta_vendor_dlkm.img",
+    "IMAGES/vendor.img",        "IMAGES/vendor_boot.img",
+    "IMAGES/vendor_dlkm.img",   "IMAGES/vendor_kernel_boot.img",
 };
 constexpr std::array kVendorTargetBuildProps = {
     "ODM/build.prop",
@@ -72,6 +70,11 @@
   std::vector<std::string> system_contents;
 };
 
+struct Extracted {
+  std::set<std::string> images;
+  std::vector<std::string> system_partitions;
+};
+
 void FindImports(Archive* archive, const std::string& build_prop_file) {
   auto contents = archive->ExtractToMemory(build_prop_file);
   auto lines = android::base::Split(contents, "\n");
@@ -92,6 +95,17 @@
   return android::base::EndsWith(filename, "build.prop");
 }
 
+Result<std::string> GetPartitionNameFromPath(const std::string& path) {
+  std::string_view result(path);
+  CF_EXPECTF(
+      android::base::ConsumePrefix(&result, "IMAGES/"),
+      "target_files filepath {} expected to be in the \"IMAGES\" directory",
+      path);
+  CF_EXPECTF(android::base::ConsumeSuffix(&result, ".img"),
+             "target_files filepath {} expected to be a \".img\" file", path);
+  return std::string(result);
+}
+
 Result<TargetFiles> GetTargetFiles(const std::string& vendor_zip_path,
                                    const std::string& system_zip_path) {
   auto result = TargetFiles{
@@ -107,42 +121,50 @@
   return result;
 }
 
-Result<void> CombineMiscInfo(TargetFiles& target_files,
-                             const std::string& misc_output_path) {
+Result<MiscInfo> CombineDynamicPartitionsInfo(
+    TargetFiles& target_files, const std::set<std::string>& extracted_images) {
+  CF_EXPECTF(Contains(target_files.vendor_contents, kDynamicPartitionsPath),
+             "Vendor target files zip does not contain {}",
+             kDynamicPartitionsPath);
+  CF_EXPECTF(Contains(target_files.system_contents, kDynamicPartitionsPath),
+             "System target files zip does not contain {}",
+             kDynamicPartitionsPath);
+
+  const MiscInfo vendor_dp_info = CF_EXPECT(ParseMiscInfo(
+      target_files.vendor_zip.ExtractToMemory(kDynamicPartitionsPath)));
+  const MiscInfo system_dp_info = CF_EXPECT(ParseMiscInfo(
+      target_files.system_zip.ExtractToMemory(kDynamicPartitionsPath)));
+
+  return CF_EXPECT(GetCombinedDynamicPartitions(vendor_dp_info, system_dp_info,
+                                                extracted_images));
+}
+
+Result<MiscInfo> CombineMiscInfo(
+    TargetFiles& target_files, const std::string& misc_output_path,
+    const std::set<std::string>& extracted_images,
+    const std::vector<std::string>& system_partitions) {
   CF_EXPECTF(Contains(target_files.vendor_contents, kMiscInfoPath),
-             "Default target files zip does not contain {}", kMiscInfoPath);
+             "Vendor target files zip does not contain {}", kMiscInfoPath);
   CF_EXPECTF(Contains(target_files.system_contents, kMiscInfoPath),
              "System target files zip does not contain {}", kMiscInfoPath);
 
-  const MiscInfo default_misc = CF_EXPECT(
+  const MiscInfo vendor_misc = CF_EXPECT(
       ParseMiscInfo(target_files.vendor_zip.ExtractToMemory(kMiscInfoPath)));
   const MiscInfo system_misc = CF_EXPECT(
       ParseMiscInfo(target_files.system_zip.ExtractToMemory(kMiscInfoPath)));
 
-  auto output_misc = default_misc;
-  auto system_super_partitions = SuperPartitionComponents(system_misc);
-  // Ensure specific skipped partitions end up in the misc_info.txt
-  for (auto partition :
-       {"odm", "odm_dlkm", "vendor", "vendor_dlkm", "system_dlkm"}) {
-    if (!Contains(system_super_partitions, partition)) {
-      system_super_partitions.push_back(partition);
-    }
-  }
-  CF_EXPECT(SetSuperPartitionComponents(system_super_partitions, &output_misc),
-            "Failed to update super partitions components for misc_info");
+  const auto combined_dp_info =
+      CF_EXPECT(CombineDynamicPartitionsInfo(target_files, extracted_images));
+  const auto output_misc = CF_EXPECT(MergeMiscInfos(
+      vendor_misc, system_misc, combined_dp_info, system_partitions));
 
-  SharedFD misc_output_file = SharedFD::Creat(misc_output_path.c_str(), 0644);
-  CF_EXPECT(misc_output_file->IsOpen(), "Failed to open output misc file: "
-                                            << misc_output_file->StrError());
-
-  CF_EXPECT(WriteAll(misc_output_file, WriteMiscInfo(output_misc)) >= 0,
-            "Failed to write output misc file contents: "
-                << misc_output_file->StrError());
-  return {};
+  CF_EXPECT(WriteMiscInfo(output_misc, misc_output_path));
+  return std::move(output_misc);
 }
 
-Result<void> ExtractTargetFiles(TargetFiles& target_files,
-                                const std::string& combined_output_path) {
+Result<Extracted> ExtractTargetFiles(TargetFiles& target_files,
+                                     const std::string& combined_output_path) {
+  Extracted extracted;
   for (const auto& name : target_files.vendor_contents) {
     if (!IsTargetFilesImage(name)) {
       continue;
@@ -153,6 +175,7 @@
     CF_EXPECT(
         target_files.vendor_zip.ExtractFiles({name}, combined_output_path),
         "Failed to extract " << name << " from the vendor target zip");
+    extracted.images.emplace(CF_EXPECT(GetPartitionNameFromPath(name)));
   }
   for (const auto& name : target_files.vendor_contents) {
     if (!IsTargetFilesBuildProp(name)) {
@@ -177,6 +200,9 @@
     CF_EXPECT(
         target_files.system_zip.ExtractFiles({name}, combined_output_path),
         "Failed to extract " << name << " from the system target zip");
+    const auto partition = CF_EXPECT(GetPartitionNameFromPath(name));
+    extracted.images.emplace(partition);
+    extracted.system_partitions.emplace_back(partition);
   }
   for (const auto& name : target_files.system_contents) {
     if (!IsTargetFilesBuildProp(name)) {
@@ -190,19 +216,36 @@
         target_files.system_zip.ExtractFiles({name}, combined_output_path),
         "Failed to extract " << name << " from the system target zip");
   }
+  return extracted;
+}
+
+Result<void> RegenerateVbmeta(const MiscInfo& misc_info,
+                              const std::string& output_path,
+                              const std::string& image_path) {
+  const VbmetaArgs args = CF_EXPECT(GetVbmetaArgs(misc_info, image_path));
+  auto avbtool = Avb(AvbToolBinary(), args.algorithm, args.key_path);
+  CF_EXPECT(avbtool.MakeVbMetaImage(output_path, args.chained_partitions,
+                                    args.included_partitions,
+                                    args.extra_arguments));
   return {};
 }
 
 Result<void> CombineTargetZipFiles(const std::string& vendor_zip_path,
                                    const std::string& system_zip_path,
-                                   const std::string& output_path) {
-  CF_EXPECT(EnsureDirectoryExists(output_path));
-  CF_EXPECT(EnsureDirectoryExists(output_path + "/META"));
+                                   const std::string& combined_target_path,
+                                   const std::string& vbmeta_output_path) {
+  CF_EXPECT(EnsureDirectoryExists(combined_target_path));
+  CF_EXPECT(EnsureDirectoryExists(combined_target_path + "/META"));
   auto target_files =
       CF_EXPECT(GetTargetFiles(vendor_zip_path, system_zip_path));
-  CF_EXPECT(ExtractTargetFiles(target_files, output_path));
-  const auto misc_output_path = output_path + "/" + kMiscInfoPath;
-  CF_EXPECT(CombineMiscInfo(target_files, misc_output_path));
+  const auto extracted =
+      CF_EXPECT(ExtractTargetFiles(target_files, combined_target_path));
+  const auto misc_output_path = combined_target_path + "/" + kMiscInfoPath;
+  const auto combined_info =
+      CF_EXPECT(CombineMiscInfo(target_files, misc_output_path,
+                                extracted.images, extracted.system_partitions));
+  CF_EXPECT(RegenerateVbmeta(combined_info, vbmeta_output_path,
+                             combined_target_path));
   return {};
 }
 
@@ -240,7 +283,8 @@
 
 Result<void> RebuildSuperImage(const FetcherConfig& fetcher_config,
                                const CuttlefishConfig& config,
-                               const std::string& output_path) {
+                               const std::string& super_image_output,
+                               const std::string& vbmeta_image_output) {
   auto instance = config.ForDefaultInstance();
   // In SuperImageNeedsRebuilding, it already checked both
   // has_default_target_zip and has_system_target_zip are the same.
@@ -262,10 +306,10 @@
   std::string combined_target_path = instance.PerInstanceInternalPath("target_combined");
   // TODO(schuffelen): Use otatools/bin/merge_target_files
   CF_EXPECT(CombineTargetZipFiles(default_target_zip, system_target_zip,
-                                  combined_target_path),
+                                  combined_target_path, vbmeta_image_output),
             "Could not combine target zip files.");
 
-  CF_EXPECT(BuildSuperImage(combined_target_path, output_path),
+  CF_EXPECT(BuildSuperImage(combined_target_path, super_image_output),
             "Could not write the final output super image.");
   return {};
 }
@@ -287,7 +331,8 @@
                                             instance_.default_target_zip(),
                                             instance_.system_target_zip()))) {
       CF_EXPECT(RebuildSuperImage(fetcher_config_, config_,
-                                  instance_.new_super_image()));
+                                  instance_.new_super_image(),
+                                  instance_.new_vbmeta_image()));
     }
     return {};
   }
diff --git a/host/commands/control_env_proxy_server/Android.bp b/host/commands/control_env_proxy_server/Android.bp
index 0494145..0783d31 100644
--- a/host/commands/control_env_proxy_server/Android.bp
+++ b/host/commands/control_env_proxy_server/Android.bp
@@ -56,7 +56,6 @@
     static_libs: [
         "grpc_cli_libs",
         "libabsl_host",
-        "libc++fs",
         "libcuttlefish_control_env",
         "libcuttlefish_host_config",
         "libgflags",
diff --git a/host/commands/cvd_env/Android.bp b/host/commands/cvd_env/Android.bp
index 58c4852..885088d 100644
--- a/host/commands/cvd_env/Android.bp
+++ b/host/commands/cvd_env/Android.bp
@@ -32,7 +32,6 @@
     static_libs: [
         "grpc_cli_libs",
         "libabsl_host",
-        "libc++fs",
         "libcuttlefish_control_env",
         "libcuttlefish_host_config",
         "libgflags",
@@ -40,7 +39,6 @@
     cflags: [
         "-Wno-unused-parameter",
     ],
-    cpp_std: "c++17",
     defaults: ["cuttlefish_host"],
     target: {
         darwin: {
diff --git a/host/commands/host_bugreport/main.cc b/host/commands/host_bugreport/main.cc
index 299e761..a414dfc 100644
--- a/host/commands/host_bugreport/main.cc
+++ b/host/commands/host_bugreport/main.cc
@@ -48,6 +48,25 @@
   }
 }
 
+Result<void> AddNetsimdLogs(ZipWriter& writer) {
+  // The temp directory name depends on whether the `USER` environment variable
+  // is defined.
+  // https://source.corp.google.com/h/googleplex-android/platform/superproject/main/+/main:tools/netsim/rust/common/src/system/mod.rs;l=37-57;drc=360ddb57df49472a40275b125bb56af2a65395c7
+  std::string user = StringFromEnv("USER", "");
+  std::string dir = user.empty() ? "/tmp/android/netsimd"
+                                 : fmt::format("/tmp/android-{}/netsimd", user);
+  if (!DirectoryExists(dir)) {
+    LOG(INFO) << "netsimd logs directory: `" << dir << "` does not exist.";
+    return {};
+  }
+  auto names =
+      CF_EXPECTF(DirectoryContents(dir), "Cannot read from {} directory.", dir);
+  for (const auto& name : names) {
+    SaveFile(writer, "netsimd/" + name, dir + "/" + name);
+  }
+  return {};
+}
+
 Result<void> CvdHostBugreportMain(int argc, char** argv) {
   ::android::base::InitLogging(argv, android::base::StderrLogger);
   google::ParseCommandLineFlags(&argc, &argv, true);
@@ -73,30 +92,34 @@
     };
     save("cuttlefish_config.json");
     save("disk_config.txt");
-    save("kernel.log");
-    save("launcher.log");
-    save("logcat");
-    save("metrics.log");
+    if (DirectoryExists(instance.PerInstancePath("logs"))) {
+      auto logs = CF_EXPECT(DirectoryContents(instance.PerInstancePath("logs")),
+                            "Cannot read from logs directory.");
+      for (const auto& log : logs) {
+        save("logs/" + log);
+      }
+    } else {
+      save("kernel.log");
+      save("launcher.log");
+      save("logcat");
+      save("metrics.log");
+    }
     auto tombstones =
         CF_EXPECT(DirectoryContents(instance.PerInstancePath("tombstones")),
                   "Cannot read from tombstones directory.");
     for (const auto& tombstone : tombstones) {
-      if (tombstone == "." || tombstone == "..") {
-        continue;
-      }
       save("tombstones/" + tombstone);
     }
     auto recordings =
         CF_EXPECT(DirectoryContents(instance.PerInstancePath("recording")),
                   "Cannot read from recording directory.");
     for (const auto& recording : recordings) {
-      if (recording == "." || recording == "..") {
-        continue;
-      }
       save("recording/" + recording);
     }
   }
 
+  CF_EXPECT(AddNetsimdLogs(writer));
+
   writer.Finish();
 
   LOG(INFO) << "Saved to \"" << FLAGS_output << "\"";
diff --git a/host/commands/metrics/Android.bp b/host/commands/metrics/Android.bp
index e8749f7..3209c93 100644
--- a/host/commands/metrics/Android.bp
+++ b/host/commands/metrics/Android.bp
@@ -25,6 +25,11 @@
         "metrics.cc",
         "utils.cc",
     ],
+    product_variables: {
+        shipping_api_level: {
+            cflags: ["-DPRODUCT_SHIPPING_API_LEVEL=%s"],
+        },
+    },
     shared_libs: [
         "cf_metrics_proto",
         "libbase",
diff --git a/host/commands/metrics/events.cc b/host/commands/metrics/events.cc
index ec0147e..efbc326 100644
--- a/host/commands/metrics/events.cc
+++ b/host/commands/metrics/events.cc
@@ -25,7 +25,6 @@
 #include "host/libs/config/cuttlefish_config.h"
 #include "host/libs/vm_manager/crosvm_manager.h"
 #include "host/libs/vm_manager/qemu_manager.h"
-#include "shared/api_level.h"
 
 namespace cuttlefish {
 
@@ -209,4 +208,4 @@
   return output;
 }
 
-}  // namespace cuttlefish
\ No newline at end of file
+}  // namespace cuttlefish
diff --git a/host/commands/modem_simulator/Android.bp b/host/commands/modem_simulator/Android.bp
index 0f684e8..9dec221 100644
--- a/host/commands/modem_simulator/Android.bp
+++ b/host/commands/modem_simulator/Android.bp
@@ -118,7 +118,4 @@
         "device/google/cuttlefish/host/commands",
     ],
     defaults: ["cuttlefish_host", "modem_simulator_base"],
-    whole_static_libs: [
-        "libc++fs"
-    ],
 }
diff --git a/host/commands/process_sandboxer/Android.bp b/host/commands/process_sandboxer/Android.bp
index a685a71..d56459e 100644
--- a/host/commands/process_sandboxer/Android.bp
+++ b/host/commands/process_sandboxer/Android.bp
@@ -26,8 +26,10 @@
     srcs: [
         "main.cpp",
         "policies.cpp",
+        "policies/baseline.cpp",
         "policies/kernel_log_monitor.cpp",
         "policies/logcat_receiver.cpp",
+        "policies/process_sandboxer_test_hello_world.cpp",
     ],
     shared_libs: ["sandboxed_api_sandbox2"],
     static_libs: [
@@ -43,3 +45,26 @@
         },
     },
 }
+
+cc_test_host {
+    name: "process_sandboxer_test",
+    defaults: ["cuttlefish_buildhost_only"],
+    srcs: ["process_sandboxer_test.cpp"],
+    // When running `atest --host process_sandboxer_test`, these are only
+    // refreshed after running `m installclean`.
+    data_bins: [
+        "process_sandboxer",
+        "process_sandboxer_test_hello_world",
+    ],
+    static_libs: [
+        "libbase",
+        "libcuttlefish_fs",
+        "libcuttlefish_utils",
+        "liblog",
+    ],
+    team: "trendy_team_cloud_android",
+    // TODO(schuffelen): See if this is possible to enable on CI
+    test_options: {
+        unit_test: false,
+    },
+}
diff --git a/host/commands/process_sandboxer/main.cpp b/host/commands/process_sandboxer/main.cpp
index 2ddcfb5..db2d860 100644
--- a/host/commands/process_sandboxer/main.cpp
+++ b/host/commands/process_sandboxer/main.cpp
@@ -17,7 +17,9 @@
 #include <stdlib.h>
 
 #include <memory>
+#include <optional>
 #include <string>
+#include <string_view>
 #include <vector>
 
 #include "absl/flags/flag.h"
@@ -46,13 +48,19 @@
 
 namespace cuttlefish {
 
+static std::optional<std::string_view> FromEnv(const std::string& name) {
+  auto value = getenv(name.c_str());
+  return value == NULL ? std::optional<std::string_view>() : value;
+}
+
 int ProcessSandboxerMain(int argc, char** argv) {
   absl::InitializeLog();
   auto args = absl::ParseCommandLine(argc, argv);
 
   HostInfo host;
   host.artifacts_path = CleanPath(absl::GetFlag(FLAGS_host_artifacts_path));
-  host.cuttlefish_config_path = CleanPath(getenv(kCuttlefishConfigEnvVarName));
+  host.cuttlefish_config_path =
+      CleanPath(FromEnv(kCuttlefishConfigEnvVarName).value_or(""));
   host.log_dir = CleanPath(absl::GetFlag(FLAGS_log_dir));
   setenv("LD_LIBRARY_PATH", JoinPath(host.artifacts_path, "lib64").c_str(), 1);
 
@@ -61,6 +69,12 @@
   std::vector<std::string> exe_argv(++args.begin(), args.end());
   auto executor = std::make_unique<sandbox2::Executor>(exe, exe_argv);
 
+  // https://cs.android.com/android/platform/superproject/main/+/main:external/sandboxed-api/sandboxed_api/sandbox2/limits.h;l=116;drc=d451478e26c0352ecd6912461e867a1ae64b17f5
+  // Default is 120 seconds
+  executor->limits()->set_walltime_limit(absl::InfiniteDuration());
+  // Default is 1024 seconds
+  executor->limits()->set_rlimit_cpu(RLIM64_INFINITY);
+
   for (const auto& inherited_fd : absl::GetFlag(FLAGS_inherited_fds)) {
     int fd;
     CHECK(absl::SimpleAtoi(inherited_fd, &fd));
diff --git a/host/commands/process_sandboxer/policies.cpp b/host/commands/process_sandboxer/policies.cpp
index f9777e8..e233996 100644
--- a/host/commands/process_sandboxer/policies.cpp
+++ b/host/commands/process_sandboxer/policies.cpp
@@ -37,6 +37,11 @@
   builders[JoinPath(host.artifacts_path, "bin", "logcat_receiver")] =
       LogcatReceiverPolicy;
 
+  // TODO(schuffelen): Don't include test policies in the production impl
+  builders[JoinPath(host.artifacts_path, "testcases", "process_sandboxer_test",
+                    "x86_64", "process_sandboxer_test_hello_world")] =
+      HelloWorldPolicy;
+
   if (auto it = builders.find(executable); it != builders.end()) {
     return (it->second)(host).BuildOrDie();
   } else {
diff --git a/host/commands/process_sandboxer/policies.h b/host/commands/process_sandboxer/policies.h
index 0f38b2a..aa0ffc9 100644
--- a/host/commands/process_sandboxer/policies.h
+++ b/host/commands/process_sandboxer/policies.h
@@ -29,9 +29,14 @@
   std::string log_dir;
 };
 
+sandbox2::PolicyBuilder BaselinePolicy(const HostInfo&, std::string_view exe);
+
 sandbox2::PolicyBuilder KernelLogMonitorPolicy(const HostInfo&);
 sandbox2::PolicyBuilder LogcatReceiverPolicy(const HostInfo&);
 
+// Testing policies
+sandbox2::PolicyBuilder HelloWorldPolicy(const HostInfo&);
+
 std::unique_ptr<sandbox2::Policy> PolicyForExecutable(
     const HostInfo& host_info, std::string_view executable_path);
 
diff --git a/host/commands/process_sandboxer/policies/baseline.cpp b/host/commands/process_sandboxer/policies/baseline.cpp
new file mode 100644
index 0000000..732fe33
--- /dev/null
+++ b/host/commands/process_sandboxer/policies/baseline.cpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "host/commands/process_sandboxer/policies.h"
+
+#include "sandboxed_api/sandbox2/policybuilder.h"
+#include "sandboxed_api/util/path.h"
+
+using sapi::file::JoinPath;
+
+namespace cuttlefish {
+
+sandbox2::PolicyBuilder BaselinePolicy(const HostInfo& host,
+                                       std::string_view exe) {
+  return sandbox2::PolicyBuilder()
+      .AddLibrariesForBinary(exe, JoinPath(host.artifacts_path, "lib64"))
+      // For dynamic linking and memory allocation
+      .AllowDynamicStartup()
+      .AllowExit()
+      .AllowGetPIDs()
+      .AllowGetRandom()
+      .AllowMmap()
+      .AllowReadlink()
+      .AllowRestartableSequences(sandbox2::PolicyBuilder::kAllowSlowFences)
+      .AllowWrite();
+}
+
+}  // namespace cuttlefish
diff --git a/host/commands/process_sandboxer/policies/kernel_log_monitor.cpp b/host/commands/process_sandboxer/policies/kernel_log_monitor.cpp
index 97b1611..fe9cedd 100644
--- a/host/commands/process_sandboxer/policies/kernel_log_monitor.cpp
+++ b/host/commands/process_sandboxer/policies/kernel_log_monitor.cpp
@@ -28,29 +28,15 @@
 
 sandbox2::PolicyBuilder KernelLogMonitorPolicy(const HostInfo& host) {
   auto exe = JoinPath(host.artifacts_path, "bin", "kernel_log_monitor");
-  auto lib64 = JoinPath(host.artifacts_path, "lib64");
-  return sandbox2::PolicyBuilder()
-      .AddDirectory(lib64)
+  return BaselinePolicy(host, exe)
       .AddDirectory(host.log_dir, /* is_ro= */ false)
       .AddFile(host.cuttlefish_config_path)
-      .AddLibrariesForBinary(exe, lib64)
-      // For dynamic linking
-      .AddPolicyOnSyscall(__NR_prctl,
-                          {ARG_32(0), JEQ32(PR_CAPBSET_READ, ALLOW)})
-      .AllowDynamicStartup()
-      .AllowGetPIDs()
-      .AllowGetRandom()
       .AllowHandleSignals()
-      .AllowMmap()
       .AllowOpen()
       .AllowRead()
-      .AllowReadlink()
-      .AllowRestartableSequences(sandbox2::PolicyBuilder::kAllowSlowFences)
       .AllowSelect()
       .AllowSafeFcntl()
-      .AllowSyscall(__NR_tgkill)
-      .AllowTCGETS()
-      .AllowWrite();
+      .AllowTCGETS();
 }
 
 }  // namespace cuttlefish
diff --git a/host/commands/process_sandboxer/policies/logcat_receiver.cpp b/host/commands/process_sandboxer/policies/logcat_receiver.cpp
index b761144..c03e4d6 100644
--- a/host/commands/process_sandboxer/policies/logcat_receiver.cpp
+++ b/host/commands/process_sandboxer/policies/logcat_receiver.cpp
@@ -28,27 +28,13 @@
 
 sandbox2::PolicyBuilder LogcatReceiverPolicy(const HostInfo& host) {
   auto exe = JoinPath(host.artifacts_path, "bin", "logcat_receiver");
-  auto lib64 = JoinPath(host.artifacts_path, "lib64");
-  return sandbox2::PolicyBuilder()
-      .AddDirectory(lib64)
+  return BaselinePolicy(host, exe)
       .AddDirectory(host.log_dir, /* is_ro= */ false)
       .AddFile(host.cuttlefish_config_path)
-      .AddLibrariesForBinary(exe, lib64)
-      // For dynamic linking
-      .AddPolicyOnSyscall(__NR_prctl,
-                          {ARG_32(0), JEQ32(PR_CAPBSET_READ, ALLOW)})
-      .AllowDynamicStartup()
-      .AllowExit()
-      .AllowGetPIDs()
-      .AllowGetRandom()
       .AllowHandleSignals()
-      .AllowMmap()
       .AllowOpen()
       .AllowRead()
-      .AllowReadlink()
-      .AllowRestartableSequences(sandbox2::PolicyBuilder::kAllowSlowFences)
       .AllowSafeFcntl()
-      .AllowSyscall(__NR_tgkill)
       .AllowWrite();
 }
 
diff --git a/host/commands/process_sandboxer/policies/process_sandboxer_test_hello_world.cpp b/host/commands/process_sandboxer/policies/process_sandboxer_test_hello_world.cpp
new file mode 100644
index 0000000..2a9fe70
--- /dev/null
+++ b/host/commands/process_sandboxer/policies/process_sandboxer_test_hello_world.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "host/commands/process_sandboxer/policies.h"
+
+#include "sandboxed_api/sandbox2/policybuilder.h"
+#include "sandboxed_api/util/path.h"
+
+using sapi::file::JoinPath;
+
+namespace cuttlefish {
+
+sandbox2::PolicyBuilder HelloWorldPolicy(const HostInfo& host) {
+  auto exe =
+      JoinPath(host.artifacts_path, "testcases", "process_sandboxer_test",
+               "x86_64", "process_sandboxer_test_hello_world");
+  return BaselinePolicy(host, exe);
+}
+
+}  // namespace cuttlefish
diff --git a/host/commands/process_sandboxer/process_sandboxer_test.cpp b/host/commands/process_sandboxer/process_sandboxer_test.cpp
new file mode 100644
index 0000000..0043a06
--- /dev/null
+++ b/host/commands/process_sandboxer/process_sandboxer_test.cpp
@@ -0,0 +1,70 @@
+//
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <libgen.h>
+#include <stdlib.h>
+
+#include <string>
+#include <string_view>
+
+#include <fmt/format.h>
+#include <gtest/gtest.h>
+
+#include "common/libs/utils/subprocess.h"
+
+namespace cuttlefish {
+
+static std::string ExecutableSelfPath() {
+  char exe_path[PATH_MAX + 1];
+  auto path_size = readlink("/proc/self/exe", exe_path, PATH_MAX);
+  CHECK_GT(path_size, 0) << strerror(errno);
+  CHECK_LE(path_size, PATH_MAX);
+  exe_path[path_size + 1] = '\0';  // Readlink does not append a null terminator
+  char abs_path[PATH_MAX];
+  char* real = realpath(exe_path, abs_path);
+  CHECK(real) << strerror(errno);
+  return real;
+}
+
+static std::string HostArtifactsDir() {
+  auto path = ExecutableSelfPath();
+  path = dirname(path.data());  // x86_64
+  path = dirname(path.data());  // <test case>
+  path = dirname(path.data());  // testcases
+  path = dirname(path.data());  // linux-86
+  return path;
+}
+
+static std::string ExecutablePath(std::string_view exe) {
+  auto dir_path = ExecutableSelfPath();
+  dir_path = dirname(dir_path.data());
+  return fmt::format("{}/{}", dir_path, exe);
+}
+
+TEST(SandboxExecutable, HelloWorld) {
+  Command executable(ExecutablePath("process_sandboxer_test_hello_world"));
+  auto opt = SubprocessOptions().SandboxArguments({
+      ExecutablePath("process_sandboxer"),
+      "--host_artifacts_path=" + HostArtifactsDir(),
+  });
+
+  std::string in, out, err;
+  int code = RunWithManagedStdio(std::move(executable), &in, &out, &err, opt);
+
+  EXPECT_EQ(code, 0) << err;
+  EXPECT_EQ(out, "Allocated vector with 100 members\n");
+}
+
+}  // namespace cuttlefish
diff --git a/host/commands/process_sandboxer/test_executables/Android.bp b/host/commands/process_sandboxer/test_executables/Android.bp
new file mode 100644
index 0000000..c9feefb
--- /dev/null
+++ b/host/commands/process_sandboxer/test_executables/Android.bp
@@ -0,0 +1,25 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+// Executables intended to validate process_sandboxer itself
+
+cc_binary_host {
+    name: "process_sandboxer_test_hello_world",
+    defaults: ["cuttlefish_buildhost_only"],
+    srcs: ["process_sandboxer_test_hello_world.cpp"],
+}
diff --git a/host/commands/process_sandboxer/test_executables/process_sandboxer_test_hello_world.cpp b/host/commands/process_sandboxer/test_executables/process_sandboxer_test_hello_world.cpp
new file mode 100644
index 0000000..b1cd1bf
--- /dev/null
+++ b/host/commands/process_sandboxer/test_executables/process_sandboxer_test_hello_world.cpp
@@ -0,0 +1,13 @@
+#include <iostream>
+#include <string>
+#include <vector>
+
+int main() {
+  // Exercise dynamic memory allocation
+  std::vector<std::string> test_vec;
+  for (size_t i = 0; i < 100; i++) {
+    test_vec.emplace_back(std::to_string(i));
+  }
+  // Exercise writing to stderr
+  std::cout << "Allocated vector with " << test_vec.size() << " members\n";
+}
diff --git a/host/commands/run_cvd/Android.bp b/host/commands/run_cvd/Android.bp
index faacebb..e1d0355 100644
--- a/host/commands/run_cvd/Android.bp
+++ b/host/commands/run_cvd/Android.bp
@@ -45,6 +45,7 @@
         "launch/webrtc_recorder.cpp",
         "launch/streamer.cpp",
         "launch/netsim_server.cpp",
+        "launch/vhal_proxy_server.cpp",
         "main.cc",
         "reporting.cpp",
         "server_loop.cpp",
@@ -63,6 +64,7 @@
         "libfruit",
         "libjsoncpp",
         "libprotobuf-cpp-full",
+        "libgrpc++_unsecure",
     ],
     static_libs: [
         "libbuildversion",
@@ -75,6 +77,7 @@
         "libcuttlefish_process_monitor",
         "libcuttlefish_utils",
         "libcuttlefish_vm_manager",
+        "libopenwrt_control_server",
         "libgflags",
     ],
     required: [
diff --git a/host/commands/run_cvd/boot_state_machine.cc b/host/commands/run_cvd/boot_state_machine.cc
index fd858a4..5f1f4b7 100644
--- a/host/commands/run_cvd/boot_state_machine.cc
+++ b/host/commands/run_cvd/boot_state_machine.cc
@@ -24,6 +24,11 @@
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <gflags/gflags.h>
+#include <grpc/grpc.h>
+#include <grpcpp/channel.h>
+#include <grpcpp/client_context.h>
+#include <grpcpp/create_channel.h>
+#include "common/libs/utils/result.h"
 
 #include "common/libs/fs/shared_fd.h"
 #include "common/libs/utils/tee_logging.h"
@@ -34,6 +39,13 @@
 #include "host/libs/command_util/runner/defs.h"
 #include "host/libs/command_util/util.h"
 #include "host/libs/config/feature.h"
+#include "openwrt_control.grpc.pb.h"
+
+using grpc::ClientContext;
+using openwrtcontrolserver::LuciRpcReply;
+using openwrtcontrolserver::LuciRpcRequest;
+using openwrtcontrolserver::OpenwrtControlService;
+using openwrtcontrolserver::OpenwrtIpaddrReply;
 
 DEFINE_int32(reboot_notification_fd, CF_DEFAULTS_REBOOT_NOTIFICATION_FD,
              "A file descriptor to notify when boot completes.");
@@ -176,6 +188,11 @@
     if (boot_event_handler_.joinable()) {
       boot_event_handler_.join();
     }
+    if (restore_complete_stop_write_->IsOpen()) {
+      char c = 1;
+      CHECK_EQ(restore_complete_stop_write_->Write(&c, 1), 1)
+          << restore_complete_stop_write_->StrError();
+    }
     if (restore_complete_handler_.joinable()) {
       restore_complete_handler_.join();
     }
@@ -208,19 +225,29 @@
     CF_EXPECTF(boot_events_pipe->IsOpen(), "Could not get boot events pipe: {}",
                boot_events_pipe->StrError());
 
+    // Pipe to tell `ThreadLoop` that the restore is complete.
     SharedFD restore_complete_pipe, restore_complete_pipe_write;
+    // Pipe to tell `restore_complete_handler_` thread to give up.
+    // It isn't perfect, can only break out of the `WaitForRestoreComplete`
+    // step.
+    SharedFD restore_complete_stop_read;
     if (IsRestoring(config_)) {
       CF_EXPECT(
           SharedFD::Pipe(&restore_complete_pipe, &restore_complete_pipe_write),
           "unable to create pipe");
+      CF_EXPECT(SharedFD::Pipe(&restore_complete_stop_read,
+                               &restore_complete_stop_write_),
+                "unable to create pipe");
 
-      // Unlike `boot_event_handler_`, this doesn't support graceful shutdown,
-      // it blocks until it finishes its work.
-      restore_complete_handler_ =
-          std::thread([this, restore_complete_pipe_write]() {
-            const auto result = vm_manager_.WaitForRestoreComplete();
+      restore_complete_handler_ = std::thread(
+          [this, restore_complete_pipe_write, restore_complete_stop_read]() {
+            const auto result =
+                vm_manager_.WaitForRestoreComplete(restore_complete_stop_read);
             CHECK(result.ok()) << "Failed to wait for restore complete: "
                                << result.error().FormatForEnv();
+            if (!result.value()) {
+              return;
+            }
 
             cuttlefish::SharedFD restore_adbd_pipe = cuttlefish::SharedFD::Open(
                 config_.ForDefaultInstance().restore_adbd_pipe_name().c_str(),
@@ -232,6 +259,26 @@
                 << "Error writing to adbd restore pipe: "
                 << restore_adbd_pipe->StrError() << ". This is unrecoverable.";
 
+            // Restart network service in OpenWRT, broken on restore.
+            CHECK(FileExists(instance_.grpc_socket_path() +
+                             "/OpenwrtControlServer.sock"))
+                << "unable to find grpc socket for OpenwrtControlServer";
+            auto openwrt_channel =
+                grpc::CreateChannel("unix:" + instance_.grpc_socket_path() +
+                                        "/OpenwrtControlServer.sock",
+                                    grpc::InsecureChannelCredentials());
+            auto stub_ = OpenwrtControlService::NewStub(openwrt_channel);
+            LuciRpcRequest request;
+            request.set_subpath("sys");
+            request.set_method("exec");
+            request.add_params("service network restart");
+            LuciRpcReply response;
+            ClientContext context;
+            grpc::Status status = stub_->LuciRpc(&context, request, &response);
+            CHECK(status.ok())
+                << "Failed to send network service reset" << status.error_code()
+                << ": " << status.error_message();
+            LOG(DEBUG) << response.result();
             auto SubtoolPath = [](const std::string& subtool_name) {
               auto my_own_dir = android::base::GetExecutableDirectory();
               std::stringstream subtool_path_stream;
@@ -404,6 +451,7 @@
 
   std::thread boot_event_handler_;
   std::thread restore_complete_handler_;
+  SharedFD restore_complete_stop_write_;
   SharedFD fg_launcher_pipe_;
   SharedFD reboot_notification_;
   SharedFD interrupt_fd_read_;
diff --git a/host/commands/run_cvd/launch/launch.h b/host/commands/run_cvd/launch/launch.h
index 4d93984..01abfbb 100644
--- a/host/commands/run_cvd/launch/launch.h
+++ b/host/commands/run_cvd/launch/launch.h
@@ -137,4 +137,7 @@
     fruit::Required<const CuttlefishConfig,
                     const CuttlefishConfig::InstanceSpecific, LogTeeCreator>>
 McuComponent();
+
+std::optional<MonitorCommand> VhalProxyServer(
+    const CuttlefishConfig&, const CuttlefishConfig::InstanceSpecific&);
 }  // namespace cuttlefish
diff --git a/host/commands/run_cvd/launch/open_wrt.cpp b/host/commands/run_cvd/launch/open_wrt.cpp
index 289d63f..e22d30c 100644
--- a/host/commands/run_cvd/launch/open_wrt.cpp
+++ b/host/commands/run_cvd/launch/open_wrt.cpp
@@ -25,8 +25,10 @@
 #include <fruit/fruit.h>
 
 #include "common/libs/utils/files.h"
+#include "common/libs/utils/json.h"
 #include "common/libs/utils/network.h"
 #include "common/libs/utils/result.h"
+#include "host/libs/command_util/snapshot_utils.h"
 #include "host/libs/config/command_source.h"
 #include "host/libs/config/known_paths.h"
 #include "host/libs/config/openwrt_args.h"
@@ -62,12 +64,27 @@
       return wmediumd_server_.WaitForAvailability();
     });
 
+    std::string first_time_argument;
+    if (IsRestoring(config_)) {
+      const std::string snapshot_dir_path = config_.snapshot_path();
+      auto meta_info_json = CF_EXPECT(LoadMetaJson(snapshot_dir_path));
+      const std::vector<std::string> selectors{kGuestSnapshotField,
+                                               instance_.id()};
+      const auto guest_snapshot_dir_suffix =
+          CF_EXPECT(GetValue<std::string>(meta_info_json, selectors));
+      // guest_snapshot_dir_suffix is a relative to
+      // the snapshot_path
+      const auto restore_path = snapshot_dir_path + "/" +
+                                guest_snapshot_dir_suffix + "/" +
+                                kGuestSnapshotBase + "_openwrt";
+      first_time_argument = "--restore=" + restore_path;
+    }
+
     /* TODO(b/305102099): Due to hostapd issue of OpenWRT 22.03.X versions,
      * OpenWRT instance should be rebooted.
      */
     LOG(DEBUG) << "Restart OpenWRT due to hostapd issue";
-    ap_cmd.ApplyProcessRestarter(instance_.crosvm_binary(),
-                                 /*first_time_argument=*/"",
+    ap_cmd.ApplyProcessRestarter(instance_.crosvm_binary(), first_time_argument,
                                  kOpenwrtVmResetExitCode);
     ap_cmd.Cmd().AddParameter("run");
     ap_cmd.AddControlSocket(
@@ -86,13 +103,6 @@
       wifi_tap = ap_cmd.AddTap(instance_.wifi_tap_name());
     }
 
-    // TODO(khei): Enable restore once open_wrt instance restoring is fixed
-    // if (IsRestoring(config_)) {
-    //  const std::string snapshot_dir = config_.snapshot_path();
-    //  CF_EXPECT(ap_cmd.SetToRestoreFromSnapshot(snapshot_dir, instance_.id(),
-    //                                            "_openwrt"));
-    //}
-
     /* TODO(kwstephenkim): delete this code when Minidroid completely disables
      * the AP VM itself
      */
diff --git a/host/commands/run_cvd/launch/vhal_proxy_server.cpp b/host/commands/run_cvd/launch/vhal_proxy_server.cpp
new file mode 100644
index 0000000..97bfca7
--- /dev/null
+++ b/host/commands/run_cvd/launch/vhal_proxy_server.cpp
@@ -0,0 +1,35 @@
+//
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "host/commands/run_cvd/launch/launch.h"
+
+#include "common/libs/utils/subprocess.h"
+#include "host/libs/config/command_source.h"
+#include "host/libs/config/known_paths.h"
+
+namespace cuttlefish {
+
+std::optional<MonitorCommand> VhalProxyServer(
+    const CuttlefishConfig& config,
+    const CuttlefishConfig::InstanceSpecific& instance) {
+  if (!instance.start_vhal_proxy_server()) {
+    return {};
+  }
+  return Command(VhalProxyServerBinary())
+      .AddParameter(VhalProxyServerConfig())
+      .AddParameter(config.vhal_proxy_server_port());
+}
+
+}  // namespace cuttlefish
diff --git a/host/commands/run_cvd/main.cc b/host/commands/run_cvd/main.cc
index 05bd8d2..163199b 100644
--- a/host/commands/run_cvd/main.cc
+++ b/host/commands/run_cvd/main.cc
@@ -134,6 +134,7 @@
       .install(VhostDeviceVsockComponent)
       .install(WmediumdServerComponent)
       .install(launchStreamerComponent)
+      .install(AutoCmd<VhalProxyServer>::Component)
 #endif
       .install(AdbConfigComponent)
       .install(AdbConfigFragmentComponent)
diff --git a/host/commands/run_cvd/server_loop_impl_snapshot.cpp b/host/commands/run_cvd/server_loop_impl_snapshot.cpp
index c90c4ac..f9ccc3c 100644
--- a/host/commands/run_cvd/server_loop_impl_snapshot.cpp
+++ b/host/commands/run_cvd/server_loop_impl_snapshot.cpp
@@ -144,20 +144,29 @@
   }
 }
 
+static Result<void> RunAdbShellCommand(
+    const CuttlefishConfig::InstanceSpecific& ins,
+    const std::vector<std::string>& command_args) {
+  Command adb_command(SubtoolPath("adb"));
+  // Avoid the adb server being started in the runtime directory and looking
+  // like a process that is still using the directory.
+  adb_command.SetWorkingDirectory("/");
+  adb_command.AddParameter("-s").AddParameter(ins.adb_ip_and_port());
+  adb_command.AddParameter("wait-for-device");
+
+  adb_command.AddParameter("shell");
+  for (const auto& argument : command_args) {
+    adb_command.AddParameter(argument);
+  }
+  CF_EXPECT_EQ(adb_command.Start().Wait(), 0);
+  return {};
+}
+
 Result<void> ServerLoopImpl::HandleSuspend(ProcessMonitor& process_monitor) {
   // right order: guest -> host
   LOG(DEBUG) << "Suspending the guest..";
-  const auto adb_bin_path = SubtoolPath("adb");
-  CF_EXPECT(Execute({adb_bin_path, "-s", instance_.adb_ip_and_port(), "shell",
-                     "cmd", "bluetooth_manager", "disable"},
-                    SubprocessOptions(), WEXITED));
-  CF_EXPECT(Execute({adb_bin_path, "-s", instance_.adb_ip_and_port(), "shell",
-                     "cmd", "bluetooth_manager", "wait-for-state:STATE_OFF"},
-                    SubprocessOptions(), WEXITED));
-  CF_EXPECT(Execute({adb_bin_path, "-s", instance_.adb_ip_and_port(), "shell",
-                     "cmd", "uwb", "disable-uwb"},
-                    SubprocessOptions(), WEXITED));
-  // right order: guest -> host
+  CF_EXPECT(
+      RunAdbShellCommand(instance_, {"/vendor/bin/snapshot_hook_pre_suspend"}));
   CF_EXPECT(SuspendGuest());
   LOG(DEBUG) << "The guest is suspended.";
   CF_EXPECT(process_monitor.SuspendMonitoredProcesses(),
@@ -173,14 +182,8 @@
   LOG(DEBUG) << "The host processes are resumed.";
   LOG(DEBUG) << "Resuming the guest..";
   CF_EXPECT(ResumeGuest());
-  // Resume services after guest has resumed.
-  const auto adb_bin_path = SubtoolPath("adb");
-  CF_EXPECT(Execute({adb_bin_path, "-s", instance_.adb_ip_and_port(), "shell",
-                     "cmd", "bluetooth_manager", "enable"},
-                    SubprocessOptions(), WEXITED));
-  CF_EXPECT(Execute({adb_bin_path, "-s", instance_.adb_ip_and_port(), "shell",
-                     "cmd", "uwb", "enable-uwb"},
-                    SubprocessOptions(), WEXITED));
+  CF_EXPECT(
+      RunAdbShellCommand(instance_, {"/vendor/bin/snapshot_hook_post_resume"}));
   LOG(DEBUG) << "The guest resumed.";
   return {};
 }
diff --git a/host/commands/snapshot_util_cvd/main.cc b/host/commands/snapshot_util_cvd/main.cc
index c9ba106..767503d 100644
--- a/host/commands/snapshot_util_cvd/main.cc
+++ b/host/commands/snapshot_util_cvd/main.cc
@@ -65,14 +65,14 @@
 }
 
 Result<void> SnapshotCvdMain(std::vector<std::string> args) {
-  const CuttlefishConfig* config =
-      CF_EXPECT(CuttlefishConfig::Get(), "Failed to obtain config object");
-
   CF_EXPECT(!args.empty(), "No arguments was given");
   const auto prog_path = args.front();
   args.erase(args.begin());
   auto parsed = CF_EXPECT(Parse(args));
 
+  const CuttlefishConfig* config =
+      CF_EXPECT(CuttlefishConfig::Get(), "Failed to obtain config object");
+
   switch (parsed.cmd) {
     case SnapshotCmd::kSuspend: {
       run_cvd::ExtendedLauncherAction extended_action;
diff --git a/host/commands/snapshot_util_cvd/parse.cc b/host/commands/snapshot_util_cvd/parse.cc
index e829389..42fcf9c 100644
--- a/host/commands/snapshot_util_cvd/parse.cc
+++ b/host/commands/snapshot_util_cvd/parse.cc
@@ -127,7 +127,14 @@
   flags.push_back(HelpFlag(flags));
   flags.push_back(HelpXmlFlag(flags, std::cout, help_xml));
   flags.push_back(UnexpectedArgumentGuard());
-  CF_EXPECT(ConsumeFlags(flags, args), "Flag parsing failed");
+  auto parse_res = ConsumeFlags(flags, args);
+  if (!help_xml && !parse_res.ok()) {
+    // Parse fails if helpxml is passed
+    CF_EXPECT(std::move(parse_res), "Flag parsing failed");
+  }
+  if (help_xml) {
+    std::exit(0);
+  }
   parsed.cmd = CF_EXPECT(ConvertToSnapshotCmd(snapshot_op));
   parsed.snapshot_path = snapshot_path;
   parsed.instance_nums = CF_EXPECT(InstanceNums());
diff --git a/host/commands/start/main.cc b/host/commands/start/main.cc
index 236f1a7..8f43cc0 100644
--- a/host/commands/start/main.cc
+++ b/host/commands/start/main.cc
@@ -268,6 +268,7 @@
     "chromeos_boot",
     "enable_host_sandbox",
     "fail_fast",
+    "vhost_user_block",
 };
 
 struct BooleanFlag {
diff --git a/host/commands/vhal_proxy_server/Android.bp b/host/commands/vhal_proxy_server/Android.bp
new file mode 100644
index 0000000..f0499f3
--- /dev/null
+++ b/host/commands/vhal_proxy_server/Android.bp
@@ -0,0 +1,52 @@
+//
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_binary_host {
+    name: "vhal_proxy_server",
+    defaults: [
+        "cuttlefish_host",
+        "FakeVehicleHardwareDefaults",
+        "VehicleHalDefaults",
+    ],
+    srcs: [
+        "VhalProxyServer.cpp",
+    ],
+    required: [
+        "Host_Prebuilt_VehicleHalDefaultProperties_JSON",
+        "Host_Prebuilt_VehicleHalTestProperties_JSON",
+        "Host_Prebuilt_VehicleHalVendorClusterTestProperties_JSON",
+    ],
+    static_libs: [
+        "android.hardware.automotive.vehicle@default-grpc-server-lib",
+        "FakeVehicleHardware",
+    ],
+    shared_libs: [
+        "libbase",
+        "libcutils",
+        "libgrpc++",
+        "liblog",
+        "libprotobuf-cpp-full",
+    ],
+    cflags: [
+        "-Wno-unused-parameter",
+    ],
+}
+
+cc_library_headers {
+    name: "vhal_vsockinfo",
+    host_supported: true,
+    export_include_dirs: ["."],
+    vendor_available: true,
+}
diff --git a/host/commands/vhal_proxy_server/VhalProxyServer.cpp b/host/commands/vhal_proxy_server/VhalProxyServer.cpp
new file mode 100644
index 0000000..c99a42d
--- /dev/null
+++ b/host/commands/vhal_proxy_server/VhalProxyServer.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "FakeVehicleHardware.h"
+#include "GRPCVehicleProxyServer.h"
+#include "vsockinfo.h"
+
+#include <android-base/logging.h>
+#include <cutils/properties.h>
+#include <linux/vm_sockets.h>
+#include <sys/socket.h>
+
+#include <memory>
+
+using ::android::hardware::automotive::utils::VsockConnectionInfo;
+using ::android::hardware::automotive::vehicle::fake::FakeVehicleHardware;
+using ::android::hardware::automotive::vehicle::virtualization::
+    GrpcVehicleProxyServer;
+
+// A GRPC server for VHAL running on the guest Android.
+// argv[1]: Config directory path containing property config file (e.g.
+// DefaultProperties.json).
+// argv[2]: The vsock port number used by this server.
+int main(int argc, char* argv[]) {
+  CHECK(argc >= 3) << "Not enough arguments, require at least 2: config file "
+                      "path and vsock port";
+
+  unsigned int port;
+  CHECK(android::base::ParseUint(argv[2], &port))
+      << "Failed to parse port as uint";
+  VsockConnectionInfo vsock = {.cid = VMADDR_CID_HOST, .port = port};
+
+  auto eth_addr = fmt::format("localhost:{}", port);
+  std::vector<std::string> listen_addrs = {vsock.str(), eth_addr};
+
+  auto fake_hardware =
+      std::make_unique<FakeVehicleHardware>(argv[1], "", false);
+  auto proxy_server = std::make_unique<GrpcVehicleProxyServer>(
+      listen_addrs, std::move(fake_hardware));
+
+  LOG(INFO) << "VHAL Server is listening on " << vsock.str() << ", "
+            << eth_addr;
+
+  proxy_server->Start().Wait();
+  return 0;
+}
diff --git a/host/commands/vhal_proxy_server/debug/Android.bp b/host/commands/vhal_proxy_server/debug/Android.bp
new file mode 100644
index 0000000..78916c7
--- /dev/null
+++ b/host/commands/vhal_proxy_server/debug/Android.bp
@@ -0,0 +1,39 @@
+//
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_binary_host {
+    name: "vhal_proxy_server_cmd",
+    defaults: [
+        "cuttlefish_host",
+    ],
+    srcs: [
+        "VhalProxyServerCmd.cpp",
+    ],
+    cflags: [
+        "-Wno-unused-parameter",
+    ],
+    static_libs: [
+        "android.hardware.automotive.vehicle@default-grpc-libgrpc",
+        "libcuttlefish_fs",
+        "libcuttlefish_utils",
+    ],
+    shared_libs: [
+        "libbase",
+        "libcutils",
+        "libgrpc++",
+        "liblog",
+        "libprotobuf-cpp-full",
+    ],
+}
diff --git a/host/commands/vhal_proxy_server/debug/VhalProxyServerCmd.cpp b/host/commands/vhal_proxy_server/debug/VhalProxyServerCmd.cpp
new file mode 100644
index 0000000..8cb65a2
--- /dev/null
+++ b/host/commands/vhal_proxy_server/debug/VhalProxyServerCmd.cpp
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "VehicleServer.grpc.pb.h"
+#include "VehicleServer.pb.h"
+
+#include <android-base/logging.h>
+#include <grpc++/grpc++.h>
+#include "common/libs/utils/flag_parser.h"
+
+using ::android::hardware::automotive::vehicle::proto::DumpOptions;
+using ::android::hardware::automotive::vehicle::proto::DumpResult;
+using ::android::hardware::automotive::vehicle::proto::VehicleServer;
+using ::cuttlefish::Flag;
+using ::cuttlefish::FlagAliasMode;
+using ::cuttlefish::GflagsCompatFlag;
+using ::grpc::ClientContext;
+using ::grpc::CreateChannel;
+using ::grpc::InsecureChannelCredentials;
+using ::grpc::Status;
+
+static constexpr int DEFAULT_ETH_PORT = 9300;
+
+// A GRPC server for VHAL running on the guest Android.
+// argv[1]: Config directory path containing property config file (e.g.
+// DefaultProperties.json).
+// argv[2]: The vsock port number used by this server.
+int main(int argc, char* argv[]) {
+  std::vector<std::string> args;
+  for (int i = 1; i < argc; i++) {
+    args.push_back(std::string(argv[i]));
+  }
+
+  int32_t eth_port = DEFAULT_ETH_PORT;
+  std::vector<Flag> flags{GflagsCompatFlag("port", eth_port)};
+  CHECK(cuttlefish::ConsumeFlags(flags, args).ok()) << "Failed to parse flags";
+
+  DumpOptions dump_options;
+  // The rest of the arguments are commands passed to VHAL.
+  for (const auto& arg : args) {
+    dump_options.add_options(arg);
+  }
+
+  auto eth_addr = fmt::format("localhost:{}", eth_port);
+
+  auto channel = CreateChannel(eth_addr, InsecureChannelCredentials());
+  auto stub = VehicleServer::NewStub(channel);
+  ClientContext context;
+  DumpResult result;
+  auto status = stub->Dump(&context, dump_options, &result);
+  CHECK(status.ok()) << "Failed to call Dump on VHAL proxy server, error: "
+                     << status.error_message();
+
+  std::cout << "Debug command finished, result: \n" << result.buffer();
+  return 0;
+}
diff --git a/host/commands/vhal_proxy_server/vsockinfo.h b/host/commands/vhal_proxy_server/vsockinfo.h
new file mode 100644
index 0000000..c38b2a0
--- /dev/null
+++ b/host/commands/vhal_proxy_server/vsockinfo.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <array>
+#include <optional>
+#include <sstream>
+#include <string>
+
+namespace android::hardware::automotive::utils {
+
+using PropertyList = std::initializer_list<std::string>;
+
+struct VsockConnectionInfo {
+  unsigned cid = 0;
+  unsigned port = 0;
+
+  std::string str() const {
+    std::stringstream ss;
+
+    ss << "vsock:" << cid << ":" << port;
+    return ss.str();
+  }
+};
+
+}  // namespace android::hardware::automotive::utils
diff --git a/host/frontend/webrtc/libdevice/camera_streamer.cpp b/host/frontend/webrtc/libdevice/camera_streamer.cpp
index bd7acce..78650fd 100644
--- a/host/frontend/webrtc/libdevice/camera_streamer.cpp
+++ b/host/frontend/webrtc/libdevice/camera_streamer.cpp
@@ -34,7 +34,8 @@
 // We are getting frames from the client so try forwarding those to the CVD
 void CameraStreamer::OnFrame(const webrtc::VideoFrame& client_frame) {
   std::lock_guard<std::mutex> lock(onframe_mutex_);
-  if (!cvd_connection_.IsConnected() && !pending_connection_.valid()) {
+  if (!cvd_connection_.IsConnected_Unguarded() &&
+      !pending_connection_.valid()) {
     // Start new connection
     pending_connection_ =
         cvd_connection_.ConnectAsync(port_, cid_, vhost_user_);
diff --git a/host/frontend/webrtc/libdevice/streamer.cpp b/host/frontend/webrtc/libdevice/streamer.cpp
index e4daf1d..b07a708 100644
--- a/host/frontend/webrtc/libdevice/streamer.cpp
+++ b/host/frontend/webrtc/libdevice/streamer.cpp
@@ -60,6 +60,7 @@
 constexpr auto kHardwareField = "hardware";
 constexpr auto kOpenwrtDeviceIdField = "openwrt_device_id";
 constexpr auto kOpenwrtAddrField = "openwrt_addr";
+constexpr auto kAdbPortField = "adb_port";
 constexpr auto kControlEnvProxyServerPathField =
     "control_env_proxy_server_path";
 constexpr auto kControlPanelButtonCommand = "command";
@@ -452,6 +453,7 @@
     device_info[kHardwareField] = hardware;
     device_info[kOpenwrtDeviceIdField] = config_.openwrt_device_id;
     device_info[kOpenwrtAddrField] = config_.openwrt_addr;
+    device_info[kAdbPortField] = config_.adb_port;
     device_info[kControlEnvProxyServerPathField] =
         config_.control_env_proxy_server_path;
     Json::Value custom_control_panel_buttons(Json::arrayValue);
diff --git a/host/frontend/webrtc/libdevice/streamer.h b/host/frontend/webrtc/libdevice/streamer.h
index a0c50c8..d153f29 100644
--- a/host/frontend/webrtc/libdevice/streamer.h
+++ b/host/frontend/webrtc/libdevice/streamer.h
@@ -57,6 +57,8 @@
   std::string openwrt_device_id;
   // Openwrt IP address for accessing Luci interface.
   std::string openwrt_addr;
+  // Adb port number of the device.
+  int adb_port;
   // Path of ControlEnvProxyServer for serving Rest API in WebUI.
   std::string control_env_proxy_server_path;
 };
diff --git a/host/frontend/webrtc/main.cpp b/host/frontend/webrtc/main.cpp
index d4b94d7..c784c93 100644
--- a/host/frontend/webrtc/main.cpp
+++ b/host/frontend/webrtc/main.cpp
@@ -179,6 +179,7 @@
       cvd_config->Instances()[0].webrtc_device_id();
   streamer_config.openwrt_addr = OpenwrtArgsFromConfig(
       cvd_config->Instances()[0])[kOpewnrtWanIpAddressName];
+  streamer_config.adb_port = instance.adb_host_port();
   streamer_config.control_env_proxy_server_path =
       instance.grpc_socket_path() + "/ControlEnvProxyServer.sock";
   streamer_config.operator_server.addr = cvd_config->sig_server_address();
@@ -215,7 +216,7 @@
   CHECK(streamer) << "Could not create streamer";
 
   int frames_fd = FLAGS_frame_server_fd;
-  bool frames_are_rgba = true;
+  bool frames_are_rgba = !instance.guest_uses_bgra_framebuffers();
   auto display_handler =
       std::make_shared<DisplayHandler>(*streamer, frames_fd, frames_are_rgba);
 
diff --git a/host/libs/command_util/snapshot_utils.cc b/host/libs/command_util/snapshot_utils.cc
index 4161ee8..ccb427a 100644
--- a/host/libs/command_util/snapshot_utils.cc
+++ b/host/libs/command_util/snapshot_utils.cc
@@ -70,10 +70,6 @@
     if (!predicate(src_dir_path + "/" + src_base_path)) {
       continue;
     }
-    if (src_base_path == "." || src_base_path == "..") {
-      LOG(DEBUG) << "Skipping \"" << src_base_path << "\"";
-      continue;
-    }
     std::string src_path = src_dir_path + "/" + src_base_path;
     std::string dest_path = dest_dir_path + "/" + src_base_path;
 
diff --git a/host/libs/config/custom_actions.cpp b/host/libs/config/custom_actions.cpp
index 9254d57..207f5d1 100644
--- a/host/libs/config/custom_actions.cpp
+++ b/host/libs/config/custom_actions.cpp
@@ -176,11 +176,10 @@
     CHECK(directory_contents_result.ok())
         << directory_contents_result.error().FormatForEnv();
     auto custom_action_configs = std::move(*directory_contents_result);
-    // Two entries are always . and ..
-    if (custom_action_configs.size() > 3) {
+    if (custom_action_configs.size() > 1) {
       LOG(ERROR) << "Expected at most one custom action config in "
                  << custom_action_config_dir << ". Please delete extras.";
-    } else if (custom_action_configs.size() == 3) {
+    } else if (custom_action_configs.size() == 1) {
       for (const auto& config : custom_action_configs) {
         if (android::base::EndsWithIgnoreCase(config, ".json")) {
           return custom_action_config_dir + "/" + config;
diff --git a/host/libs/config/cuttlefish_config.cpp b/host/libs/config/cuttlefish_config.cpp
index 091b591..ab11c98 100644
--- a/host/libs/config/cuttlefish_config.cpp
+++ b/host/libs/config/cuttlefish_config.cpp
@@ -306,6 +306,14 @@
   return (*dictionary_)[kEnableAutomotiveProxy].asBool();
 }
 
+static constexpr char kVhalProxyServerPort[] = "vhal_proxy_server_port";
+void CuttlefishConfig::set_vhal_proxy_server_port(int port) {
+  (*dictionary_)[kVhalProxyServerPort] = port;
+}
+int CuttlefishConfig::vhal_proxy_server_port() const {
+  return (*dictionary_)[kVhalProxyServerPort].asInt();
+}
+
 static constexpr char kEnableHostNfc[] = "enable_host_nfc";
 void CuttlefishConfig::set_enable_host_nfc(bool enable_host_nfc) {
   (*dictionary_)[kEnableHostNfc] = enable_host_nfc;
diff --git a/host/libs/config/cuttlefish_config.h b/host/libs/config/cuttlefish_config.h
index dfa38cb..cd8ce9f 100644
--- a/host/libs/config/cuttlefish_config.h
+++ b/host/libs/config/cuttlefish_config.h
@@ -154,6 +154,10 @@
   void set_enable_automotive_proxy(bool enable_automotive_proxy);
   bool enable_automotive_proxy() const;
 
+  // The vsock port used by vhal_proxy_server
+  void set_vhal_proxy_server_port(int port);
+  int vhal_proxy_server_port() const;
+
   // Bluetooth is enabled by bt_connector and rootcanal
   void set_enable_host_bluetooth_connector(bool enable_host_bluetooth);
   bool enable_host_bluetooth_connector() const;
@@ -394,6 +398,8 @@
 
     std::string pstore_path() const;
 
+    std::string pflash_path() const;
+
     std::string console_path() const;
 
     std::string logcat_path() const;
@@ -577,6 +583,7 @@
     bool mte() const;
     std::string boot_slot() const;
     bool fail_fast() const;
+    bool vhost_user_block() const;
 
     // Kernel and bootloader logging
     bool enable_kernel_log() const;
@@ -619,6 +626,7 @@
     std::string gpu_renderer_features() const;
     std::string gpu_context_types() const;
     std::string guest_vulkan_driver() const;
+    bool guest_uses_bgra_framebuffers() const;
     std::string frames_socket_path() const;
 
     std::string gpu_vhost_user_mode() const;
@@ -686,6 +694,8 @@
     bool bootconfig_supported() const;
     std::string filename_encryption_mode() const;
     ExternalNetworkMode external_network_mode() const;
+
+    bool start_vhal_proxy_server() const;
   };
 
   // A view into an existing CuttlefishConfig object for a particular instance.
@@ -792,6 +802,7 @@
     void set_boot_slot(const std::string& boot_slot);
     void set_grpc_socket_path(const std::string& sockets);
     void set_fail_fast(bool fail_fast);
+    void set_vhost_user_block(bool qemu_vhost_user_block);
 
     // Kernel and bootloader logging
     void set_enable_kernel_log(bool enable_kernel_log);
@@ -836,6 +847,7 @@
     void set_gpu_renderer_features(const std::string& features);
     void set_gpu_context_types(const std::string& context_types);
     void set_guest_vulkan_driver(const std::string& driver);
+    void set_guest_uses_bgra_framebuffers(bool uses_bgra);
     void set_frames_socket_path(const std::string& driver);
 
     void set_enable_gpu_udmabuf(const bool enable_gpu_udmabuf);
@@ -893,6 +905,10 @@
     void set_filename_encryption_mode(const std::string& userdata_format);
     void set_external_network_mode(ExternalNetworkMode network_mode);
 
+    // Whether we should start vhal_proxy_server for the guest-side VHAL to
+    // connect to.
+    void set_start_vhal_proxy_server(bool enable_vhal_proxy_server);
+
    private:
     void SetPath(const std::string& key, const std::string& path);
   };
diff --git a/host/libs/config/cuttlefish_config_instance.cpp b/host/libs/config/cuttlefish_config_instance.cpp
index de063a8..f2ddda5 100644
--- a/host/libs/config/cuttlefish_config_instance.cpp
+++ b/host/libs/config/cuttlefish_config_instance.cpp
@@ -797,6 +797,16 @@
   (*Dictionary())[kVulkanDriver] = driver;
 }
 
+static constexpr char kGuestUsesBgraFramebuffers[] =
+    "guest_uses_bgra_framebuffers";
+bool CuttlefishConfig::InstanceSpecific::guest_uses_bgra_framebuffers() const {
+  return (*Dictionary())[kGuestUsesBgraFramebuffers].asBool();
+}
+void CuttlefishConfig::MutableInstanceSpecific::
+    set_guest_uses_bgra_framebuffers(bool uses_bgra) {
+  (*Dictionary())[kGuestUsesBgraFramebuffers] = uses_bgra;
+}
+
 static constexpr char kRestartSubprocesses[] = "restart_subprocesses";
 bool CuttlefishConfig::InstanceSpecific::restart_subprocesses() const {
   return (*Dictionary())[kRestartSubprocesses].asBool();
@@ -958,6 +968,15 @@
   return (*Dictionary())[kFailFast].asBool();
 }
 
+static constexpr char kVhostUserBlock[] = "vhost_user_block";
+void CuttlefishConfig::MutableInstanceSpecific::set_vhost_user_block(
+    bool block) {
+  (*Dictionary())[kVhostUserBlock] = block;
+}
+bool CuttlefishConfig::InstanceSpecific::vhost_user_block() const {
+  return (*Dictionary())[kVhostUserBlock].asBool();
+}
+
 static constexpr char kEnableWebRTC[] = "enable_webrtc";
 void CuttlefishConfig::MutableInstanceSpecific::set_enable_webrtc(bool enable_webrtc) {
   (*Dictionary())[kEnableWebRTC] = enable_webrtc;
@@ -1277,6 +1296,10 @@
   return AbsolutePath(PerInstancePath("pstore"));
 }
 
+std::string CuttlefishConfig::InstanceSpecific::pflash_path() const {
+  return AbsolutePath(PerInstancePath("pflash.img"));
+}
+
 std::string CuttlefishConfig::InstanceSpecific::console_path() const {
   return AbsolutePath(PerInstancePath("console"));
 }
@@ -1836,6 +1859,15 @@
   (*Dictionary())[kWifiMacPrefix] = wifi_mac_prefix;
 }
 
+static constexpr char kStartVhalProxyServer[] = "start_vhal_proxy_server";
+void CuttlefishConfig::MutableInstanceSpecific::set_start_vhal_proxy_server(
+    bool start_vhal_proxy_server) {
+  (*Dictionary())[kStartVhalProxyServer] = start_vhal_proxy_server;
+}
+bool CuttlefishConfig::InstanceSpecific::start_vhal_proxy_server() const {
+  return (*Dictionary())[kStartVhalProxyServer].asBool();
+}
+
 std::string CuttlefishConfig::InstanceSpecific::factory_reset_protected_path() const {
   return PerInstanceInternalPath("factory_reset_protected.img");
 }
diff --git a/host/libs/config/host_tools_version.cpp b/host/libs/config/host_tools_version.cpp
index 9ef895d..5c73e5c 100644
--- a/host/libs/config/host_tools_version.cpp
+++ b/host/libs/config/host_tools_version.cpp
@@ -48,13 +48,6 @@
   auto files_result = DirectoryContents(full_path);
   CHECK(files_result.ok()) << files_result.error().FormatForEnv();
   std::vector<std::string> files = std::move(*files_result);
-  for (auto it = files.begin(); it != files.end();) {
-    if (*it == "." || *it == "..") {
-      it = files.erase(it);
-    } else {
-      it++;
-    }
-  }
   std::vector<std::future<uint32_t>> calculations;
   calculations.reserve(files.size());
   for (auto& file : files) {
diff --git a/host/libs/config/known_paths.cpp b/host/libs/config/known_paths.cpp
index a5bfb0c..37e7b4e 100644
--- a/host/libs/config/known_paths.cpp
+++ b/host/libs/config/known_paths.cpp
@@ -132,4 +132,12 @@
   return HostBinaryPath("automotive_vsock_proxy");
 }
 
+std::string VhalProxyServerBinary() {
+  return HostBinaryPath("vhal_proxy_server");
+}
+
+std::string VhalProxyServerConfig() {
+  return DefaultHostArtifactsPath("etc/automotive/vhalconfig");
+}
+
 } // namespace cuttlefish
diff --git a/host/libs/config/known_paths.h b/host/libs/config/known_paths.h
index 355d255..7895b20 100644
--- a/host/libs/config/known_paths.h
+++ b/host/libs/config/known_paths.h
@@ -52,5 +52,7 @@
 std::string WmediumdBinary();
 std::string WmediumdGenConfigBinary();
 std::string AutomotiveProxyBinary();
+std::string VhalProxyServerBinary();
+std::string VhalProxyServerConfig();
 
 } // namespace cuttlefish
diff --git a/host/libs/control_env/Android.bp b/host/libs/control_env/Android.bp
index 3c33d82..5b5264f 100644
--- a/host/libs/control_env/Android.bp
+++ b/host/libs/control_env/Android.bp
@@ -32,13 +32,11 @@
     static_libs: [
         "grpc_cli_libs",
         "libabsl_host",
-        "libc++fs",
         "libgflags",
     ],
     cflags: [
         "-Wno-unused-parameter",
     ],
-    cpp_std: "c++17",
     defaults: ["cuttlefish_buildhost_only"],
     target: {
         darwin: {
diff --git a/host/libs/process_monitor/process_monitor.cc b/host/libs/process_monitor/process_monitor.cc
index 89039af..3831b5b 100644
--- a/host/libs/process_monitor/process_monitor.cc
+++ b/host/libs/process_monitor/process_monitor.cc
@@ -87,7 +87,7 @@
   }
 }
 
-Result<void> MonitorLoop(const std::atomic_bool& running,
+Result<void> MonitorLoop(std::atomic_bool& running,
                          std::mutex& properties_mutex,
                          const bool restart_subprocesses,
                          std::vector<MonitorEntry>& monitored) {
@@ -121,8 +121,8 @@
         if (running.load() && is_critical) {
           LOG(ERROR) << "Stopping all monitored processes due to unexpected "
                         "exit of critical process";
-          Command stop_cmd(StopCvdBinary());
-          stop_cmd.Start();
+          running.store(false);
+          break;
         }
       }
     }
diff --git a/host/libs/vm_manager/Android.bp b/host/libs/vm_manager/Android.bp
index 1bc3ee0..aa6b18e 100644
--- a/host/libs/vm_manager/Android.bp
+++ b/host/libs/vm_manager/Android.bp
@@ -26,6 +26,7 @@
         "host_configuration.cpp",
         "pci.cpp",
         "qemu_manager.cpp",
+        "vhost_user_block.cpp",
         "vm_manager.cpp",
     ],
     header_libs: [
diff --git a/host/libs/vm_manager/crosvm_builder.cpp b/host/libs/vm_manager/crosvm_builder.cpp
index 5e44a16..5fa30b7 100644
--- a/host/libs/vm_manager/crosvm_builder.cpp
+++ b/host/libs/vm_manager/crosvm_builder.cpp
@@ -134,23 +134,6 @@
 
 int CrosvmBuilder::HvcNum() { return hvc_num_; }
 
-Result<void> CrosvmBuilder::SetToRestoreFromSnapshot(
-    const std::string& snapshot_dir_path, const std::string& instance_id_in_str,
-    const std::string& snapshot_name) {
-  auto meta_info_json = CF_EXPECT(LoadMetaJson(snapshot_dir_path));
-  const std::vector<std::string> selectors{kGuestSnapshotField,
-                                           instance_id_in_str};
-  const auto guest_snapshot_dir_suffix =
-      CF_EXPECT(GetValue<std::string>(meta_info_json, selectors));
-  // guest_snapshot_dir_suffix is a relative to
-  // the snapshot_path
-  const auto restore_path = snapshot_dir_path + "/" +
-                            guest_snapshot_dir_suffix + "/" +
-                            kGuestSnapshotBase + snapshot_name;
-  command_.AddParameter("--restore=", restore_path);
-  return {};
-}
-
 Command& CrosvmBuilder::Cmd() { return command_; }
 
 }  // namespace cuttlefish
diff --git a/host/libs/vm_manager/crosvm_builder.h b/host/libs/vm_manager/crosvm_builder.h
index 123d01a..6346465 100644
--- a/host/libs/vm_manager/crosvm_builder.h
+++ b/host/libs/vm_manager/crosvm_builder.h
@@ -57,13 +57,6 @@
 
   int HvcNum();
 
-  /**
-   * Configures the crosvm to start with --restore=<guest snapshot path>
-   */
-  Result<void> SetToRestoreFromSnapshot(const std::string& snapshot_dir_path,
-                                        const std::string& instance_id,
-                                        const std::string& snapshot_name);
-
   Command& Cmd();
 
  private:
diff --git a/host/libs/vm_manager/crosvm_manager.cpp b/host/libs/vm_manager/crosvm_manager.cpp
index 3eb5118..f546344 100644
--- a/host/libs/vm_manager/crosvm_manager.cpp
+++ b/host/libs/vm_manager/crosvm_manager.cpp
@@ -16,6 +16,7 @@
 
 #include "host/libs/vm_manager/crosvm_manager.h"
 
+#include <poll.h>
 #include <signal.h>
 #include <sys/stat.h>
 #include <sys/types.h>
@@ -43,6 +44,7 @@
 #include "host/libs/config/known_paths.h"
 #include "host/libs/vm_manager/crosvm_builder.h"
 #include "host/libs/vm_manager/qemu_manager.h"
+#include "host/libs/vm_manager/vhost_user.h"
 
 namespace cuttlefish {
 namespace vm_manager {
@@ -73,6 +75,8 @@
         {"androidboot.hardware.gralloc", "minigbm"},
         {"androidboot.hardware.hwcomposer", instance.hwcomposer()},
         {"androidboot.hardware.hwcomposer.display_finder_mode", "drm"},
+        {"androidboot.hardware.hwcomposer.display_framebuffer_format",
+         instance.guest_uses_bgra_framebuffers() ? "bgra" : "rgba"},
         {"androidboot.hardware.egl", "angle"},
         {"androidboot.hardware.vulkan", "pastel"},
         {"androidboot.opengles.version", "196609"},  // OpenGL ES 3.1
@@ -84,6 +88,8 @@
         {"androidboot.hardware.hwcomposer", "ranchu"},
         {"androidboot.hardware.hwcomposer.mode", "client"},
         {"androidboot.hardware.hwcomposer.display_finder_mode", "drm"},
+        {"androidboot.hardware.hwcomposer.display_framebuffer_format",
+         instance.guest_uses_bgra_framebuffers() ? "bgra" : "rgba"},
         {"androidboot.hardware.egl", "mesa"},
         // No "hardware" Vulkan support, yet
         {"androidboot.opengles.version", "196608"},  // OpenGL ES 3.0
@@ -109,6 +115,8 @@
         {"androidboot.hardware.gralloc", "minigbm"},
         {"androidboot.hardware.hwcomposer", instance.hwcomposer()},
         {"androidboot.hardware.hwcomposer.display_finder_mode", "drm"},
+        {"androidboot.hardware.hwcomposer.display_framebuffer_format",
+         instance.guest_uses_bgra_framebuffers() ? "bgra" : "rgba"},
         {"androidboot.hardware.egl", gles_impl},
         {"androidboot.hardware.vulkan", "ranchu"},
         {"androidboot.hardware.gltransport", gfxstream_transport},
@@ -120,6 +128,8 @@
         {"androidboot.hardware.gralloc", "minigbm"},
         {"androidboot.hardware.hwcomposer", instance.hwcomposer()},
         {"androidboot.hardware.hwcomposer.display_finder_mode", "drm"},
+        {"androidboot.hardware.hwcomposer.display_framebuffer_format",
+         instance.guest_uses_bgra_framebuffers() ? "bgra" : "rgba"},
         {"androidboot.hardware.egl", "angle"},
         {"androidboot.hardware.vulkan", instance.guest_vulkan_driver()},
         {"androidboot.hardware.gltransport", "virtio-gpu-asg"},
@@ -199,10 +209,6 @@
                                        << " for vhost user gpu crosvm");
 }
 
-struct VhostUserDeviceCommands {
-  Command device_cmd;
-  Command device_logs_cmd;
-};
 Result<VhostUserDeviceCommands> BuildVhostUserGpu(
     const CuttlefishConfig& config, Command* main_crosvm_cmd) {
   const auto& instance = config.ForDefaultInstance();
@@ -338,6 +344,7 @@
   return VhostUserDeviceCommands{
       .device_cmd = std::move(gpu_device_cmd.Cmd()),
       .device_logs_cmd = std::move(gpu_device_logs_cmd),
+      .socket_path = gpu_device_socket_path,
   };
 }
 
@@ -425,6 +432,8 @@
   auto instance = config.ForDefaultInstance();
   auto environment = config.ForDefaultEnvironment();
 
+  std::vector<MonitorCommand> commands;
+
   CrosvmBuilder crosvm_cmd;
   crosvm_cmd.Cmd().AddPrerequisite([&dependencyCommands]() -> Result<void> {
     for (auto dependencyCommand : dependencyCommands) {
@@ -531,12 +540,31 @@
   CF_EXPECT(VmManager::kMaxDisks >= disk_num,
             "Provided too many disks (" << disk_num << "), maximum "
                                         << VmManager::kMaxDisks << "supported");
+  size_t disk_i = 0;
   for (const auto& disk : instance.virtual_disk_paths()) {
     if (instance.protected_vm()) {
       crosvm_cmd.AddReadOnlyDisk(disk);
+    } else if (instance.vhost_user_block() && disk_i == 2) {
+      // TODO: b/346855591 - Run on all devices
+      auto block = CF_EXPECT(VhostUserBlockDevice(config, disk_i, disk));
+      commands.emplace_back(std::move(block.device_cmd));
+      commands.emplace_back(std::move(block.device_logs_cmd));
+      auto socket_path = std::move(block.socket_path);
+      crosvm_cmd.Cmd().AddPrerequisite([socket_path]() -> Result<void> {
+#ifdef __linux__
+        return WaitForUnixSocketListeningWithoutConnect(socket_path,
+                                                        /*timeoutSec=*/30);
+#else
+        return CF_ERR("Unhandled check if vhost user block ready.");
+#endif
+      });
+      auto pci_addr = fmt::format("00:{:0>2x}.0", 0x13 + disk_i);
+      crosvm_cmd.Cmd().AddParameter("--vhost-user=block,socket=", socket_path,
+                                    ",pci-address=", pci_addr);
     } else {
       crosvm_cmd.AddReadWriteDisk(disk);
     }
+    disk_i++;
   }
 
   if (instance.enable_webrtc()) {
@@ -816,11 +844,13 @@
         ":shared:type=fs");
   }
 
+  if (instance.target_arch() == Arch::X86_64) {
+    crosvm_cmd.Cmd().AddParameter("--pflash=", instance.pflash_path());
+  }
+
   // This needs to be the last parameter
   crosvm_cmd.Cmd().AddParameter("--bios=", instance.bootloader());
 
-  std::vector<MonitorCommand> commands;
-
   if (vhost_user_gpu) {
     // The vhost user gpu crosvm command should be added before the main
     // crosvm command so that the main crosvm command can use a prerequisite
@@ -890,14 +920,21 @@
   return commands;
 }
 
-Result<void> CrosvmManager::WaitForRestoreComplete() const {
+Result<bool> CrosvmManager::WaitForRestoreComplete(SharedFD stop_fd) const {
   auto instance = CF_EXPECT(CuttlefishConfig::Get())->ForDefaultInstance();
 
   // Wait for the control socket to exist. It is created early in crosvm's
   // startup sequence, but the process may not even have been exec'd by CF at
   // this point.
   while (!FileExists(instance.CrosvmSocketPath())) {
-    usleep(50000);  // 50 ms, arbitrarily chosen
+    std::vector<PollSharedFd> poll = {{.fd = stop_fd, .events = POLLIN}};
+    const int result = SharedFD::Poll(poll, 50 /* ms */);
+    // Check for errors.
+    CF_EXPECT(result >= 0, "failed to wait on stop_fd: " << strerror(errno));
+    // Check if pipe became readable or closed.
+    if (result > 0) {
+      return false;
+    }
   }
 
   // Ask crosvm to resume the VM. crosvm promises to not complete this command
@@ -914,7 +951,7 @@
   CF_EXPECT_EQ(infop.si_code, CLD_EXITED);
   CF_EXPECTF(infop.si_status == 0, "crosvm resume returns non zero code {}",
              infop.si_status);
-  return {};
+  return true;
 }
 
 }  // namespace vm_manager
diff --git a/host/libs/vm_manager/crosvm_manager.h b/host/libs/vm_manager/crosvm_manager.h
index cb119fe..6e26c05 100644
--- a/host/libs/vm_manager/crosvm_manager.h
+++ b/host/libs/vm_manager/crosvm_manager.h
@@ -46,7 +46,7 @@
       const CuttlefishConfig& config,
       std::vector<VmmDependencyCommand*>& dependencyCommands) override;
 
-  Result<void> WaitForRestoreComplete() const override;
+  Result<bool> WaitForRestoreComplete(SharedFD stop_fd) const override;
 
  private:
   static constexpr int kCrosvmVmResetExitCode = 32;
diff --git a/host/libs/vm_manager/gem5_manager.cpp b/host/libs/vm_manager/gem5_manager.cpp
index fb7f033..78f3e80 100644
--- a/host/libs/vm_manager/gem5_manager.cpp
+++ b/host/libs/vm_manager/gem5_manager.cpp
@@ -229,6 +229,8 @@
         {"androidboot.hardware.hwcomposer", "ranchu"},
         {"androidboot.hardware.hwcomposer.mode", "noop"},
         {"androidboot.hardware.hwcomposer.display_finder_mode", "gem5"},
+        {"androidboot.hardware.hwcomposer.display_framebuffer_format",
+         instance.guest_uses_bgra_framebuffers() ? "bgra" : "rgba"},
         {"androidboot.hardware.egl", "angle"},
         {"androidboot.hardware.vulkan", "pastel"},
         {"androidboot.opengles.version", "196609"},  // OpenGL ES 3.1
@@ -240,6 +242,8 @@
         {"androidboot.hardware.gralloc", "minigbm"},
         {"androidboot.hardware.hwcomposer", "ranchu"},
         {"androidboot.hardware.hwcomposer.display_finder_mode", "gem5"},
+        {"androidboot.hardware.hwcomposer.display_framebuffer_format",
+         instance.guest_uses_bgra_framebuffers() ? "bgra" : "rgba"},
         {"androidboot.hardware.egl", "emulation"},
         {"androidboot.hardware.vulkan", "ranchu"},
         {"androidboot.hardware.gltransport", "virtio-gpu-pipe"},
diff --git a/host/libs/vm_manager/qemu_manager.cpp b/host/libs/vm_manager/qemu_manager.cpp
index ffd431d..83a957c 100644
--- a/host/libs/vm_manager/qemu_manager.cpp
+++ b/host/libs/vm_manager/qemu_manager.cpp
@@ -40,6 +40,7 @@
 #include "common/libs/utils/subprocess.h"
 #include "host/libs/config/command_source.h"
 #include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/vm_manager/vhost_user.h"
 
 namespace cuttlefish {
 namespace vm_manager {
@@ -130,6 +131,8 @@
         {"androidboot.hardware.gralloc", "minigbm"},
         {"androidboot.hardware.hwcomposer", instance.hwcomposer()},
         {"androidboot.hardware.hwcomposer.display_finder_mode", "drm"},
+        {"androidboot.hardware.hwcomposer.display_framebuffer_format",
+         instance.guest_uses_bgra_framebuffers() ? "bgra" : "rgba"},
         {"androidboot.hardware.egl", "angle"},
         {"androidboot.hardware.vulkan", "pastel"},
         // OpenGL ES 3.1
@@ -142,6 +145,8 @@
         {"androidboot.hardware.hwcomposer", "ranchu"},
         {"androidboot.hardware.hwcomposer.mode", "client"},
         {"androidboot.hardware.hwcomposer.display_finder_mode", "drm"},
+        {"androidboot.hardware.hwcomposer.display_framebuffer_format",
+         instance.guest_uses_bgra_framebuffers() ? "bgra" : "rgba"},
         {"androidboot.hardware.egl", "mesa"},
         // No "hardware" Vulkan support, yet
         // OpenGL ES 3.0
@@ -162,6 +167,8 @@
         {"androidboot.hardware.gralloc", "minigbm"},
         {"androidboot.hardware.hwcomposer", instance.hwcomposer()},
         {"androidboot.hardware.hwcomposer.display_finder_mode", "drm"},
+        {"androidboot.hardware.hwcomposer.display_framebuffer_format",
+         instance.guest_uses_bgra_framebuffers() ? "bgra" : "rgba"},
         {"androidboot.hardware.egl", gles_impl},
         {"androidboot.hardware.vulkan", "ranchu"},
         {"androidboot.hardware.gltransport", gltransport},
@@ -205,7 +212,8 @@
     case Arch::X86_64: {
       // QEMU has additional PCI devices for an ISA bridge and PIIX4
       // virtio_gpu precedes the first console or disk
-      int pci_offset = 2 + num_gpu - VmManager::kDefaultNumHvcs;
+      // TODO(schuffelen): Simplify this logic when crosvm uses multiport
+      int pci_offset = 3 + num_gpu - VmManager::kDefaultNumHvcs;
       return ConfigureMultipleBootDevices("pci0000:00/0000:00:", pci_offset,
                                           num_disks);
     }
@@ -213,7 +221,9 @@
 }
 
 Result<std::vector<MonitorCommand>> QemuManager::StartCommands(
-    const CuttlefishConfig& config, std::vector<VmmDependencyCommand*>&) {
+    const CuttlefishConfig& config,
+    std::vector<VmmDependencyCommand*>& dependency_commands) {
+  std::vector<MonitorCommand> commands;
   auto instance = config.ForDefaultInstance();
   std::string qemu_binary = instance.qemu_binary_dir();
   switch (arch_) {
@@ -237,18 +247,22 @@
   auto qemu_version = CF_EXPECT(GetQemuVersion(qemu_binary));
   Command qemu_cmd(qemu_binary, KillSubprocessFallback(Stop));
 
+  qemu_cmd.AddPrerequisite([&dependency_commands]() -> Result<void> {
+    for (auto dependencyCommand : dependency_commands) {
+      CF_EXPECT(dependencyCommand->WaitForAvailability());
+    }
+
+    return {};
+  });
+
   int hvc_num = 0;
   int serial_num = 0;
   auto add_hvc_sink = [&qemu_cmd, &hvc_num]() {
     qemu_cmd.AddParameter("-chardev");
     qemu_cmd.AddParameter("null,id=hvc", hvc_num);
     qemu_cmd.AddParameter("-device");
-    qemu_cmd.AddParameter(
-        "virtio-serial-pci-non-transitional,max_ports=1,id=virtio-serial",
-        hvc_num, ",bus=hvc-bridge,addr=", fmt::format("{:0>2x}", hvc_num + 1));
-    qemu_cmd.AddParameter("-device");
-    qemu_cmd.AddParameter("virtconsole,bus=virtio-serial", hvc_num,
-                          ".0,chardev=hvc", hvc_num);
+    qemu_cmd.AddParameter("virtconsole,bus=virtio-serial.0,chardev=hvc",
+                          hvc_num);
     hvc_num++;
   };
   auto add_serial_sink = [&qemu_cmd, &serial_num]() {
@@ -280,36 +294,24 @@
     qemu_cmd.AddParameter("file,id=hvc", hvc_num, ",path=", output,
                           ",append=on");
     qemu_cmd.AddParameter("-device");
-    qemu_cmd.AddParameter(
-        "virtio-serial-pci-non-transitional,max_ports=1,id=virtio-serial",
-        hvc_num, ",bus=hvc-bridge,addr=", fmt::format("{:0>2x}", hvc_num + 1));
-    qemu_cmd.AddParameter("-device");
-    qemu_cmd.AddParameter("virtconsole,bus=virtio-serial", hvc_num,
-                          ".0,chardev=hvc", hvc_num);
+    qemu_cmd.AddParameter("virtconsole,bus=virtio-serial.0,chardev=hvc",
+                          hvc_num);
     hvc_num++;
   };
   auto add_hvc = [&qemu_cmd, &hvc_num](const std::string& prefix) {
     qemu_cmd.AddParameter("-chardev");
     qemu_cmd.AddParameter("pipe,id=hvc", hvc_num, ",path=", prefix);
     qemu_cmd.AddParameter("-device");
-    qemu_cmd.AddParameter(
-        "virtio-serial-pci-non-transitional,max_ports=1,id=virtio-serial",
-        hvc_num, ",bus=hvc-bridge,addr=", fmt::format("{:0>2x}", hvc_num + 1));
-    qemu_cmd.AddParameter("-device");
-    qemu_cmd.AddParameter("virtconsole,bus=virtio-serial", hvc_num,
-                          ".0,chardev=hvc", hvc_num);
+    qemu_cmd.AddParameter("virtconsole,bus=virtio-serial.0,chardev=hvc",
+                          hvc_num);
     hvc_num++;
   };
   auto add_hvc_serial = [&qemu_cmd, &hvc_num](const std::string& prefix) {
     qemu_cmd.AddParameter("-chardev");
     qemu_cmd.AddParameter("serial,id=hvc", hvc_num, ",path=", prefix);
     qemu_cmd.AddParameter("-device");
-    qemu_cmd.AddParameter(
-        "virtio-serial-pci-non-transitional,max_ports=1,id=virtio-serial",
-        hvc_num, ",bus=hvc-bridge,addr=", fmt::format("{:0>2x}", hvc_num + 1));
-    qemu_cmd.AddParameter("-device");
-    qemu_cmd.AddParameter("virtconsole,bus=virtio-serial", hvc_num,
-                          ".0,chardev=hvc", hvc_num);
+    qemu_cmd.AddParameter("virtconsole,bus=virtio-serial.0,chardev=hvc",
+                          hvc_num);
     hvc_num++;
   };
 
@@ -434,13 +436,6 @@
     qemu_cmd.AddParameter("none");
   }
 
-  qemu_cmd.AddParameter("-device");
-  if (is_x86) {
-    qemu_cmd.AddParameter("pcie-pci-bridge,id=hvc-bridge,addr=01.2");
-  } else {
-    qemu_cmd.AddParameter("pcie-pci-bridge,id=hvc-bridge");
-  }
-
   if (instance.hwcomposer() != kHwComposerNone) {
     auto display_configs = instance.display_configs();
     CF_EXPECT(display_configs.size() >= 1);
@@ -491,6 +486,10 @@
     }
   }
 
+  qemu_cmd.AddParameter("-device");
+  qemu_cmd.AddParameter(
+      "virtio-serial-pci-non-transitional,max_ports=31,id=virtio-serial");
+
   // /dev/hvc0 = kernel console
   // If kernel log is enabled, the virtio-console port will be specified as
   // a true console for Linux, and kernel messages will be printed there.
@@ -633,17 +632,39 @@
   auto readonly = instance.protected_vm() ? ",readonly" : "";
   size_t i = 0;
   for (const auto& disk : instance.virtual_disk_paths()) {
-    qemu_cmd.AddParameter("-drive");
-    qemu_cmd.AddParameter("file=", disk, ",if=none,id=drive-virtio-disk", i,
-                          ",aio=threads", readonly);
-    qemu_cmd.AddParameter("-device");
-    qemu_cmd.AddParameter(
-#ifdef __APPLE__
-        "virtio-blk-pci-non-transitional,drive=drive-virtio-disk", i,
+    if (instance.vhost_user_block()) {
+      auto block = CF_EXPECT(VhostUserBlockDevice(config, i, disk));
+      commands.emplace_back(std::move(block.device_cmd));
+      commands.emplace_back(std::move(block.device_logs_cmd));
+      auto socket_path = std::move(block.socket_path);
+      qemu_cmd.AddPrerequisite([socket_path]() -> Result<void> {
+#ifdef __linux__
+        return WaitForUnixSocketListeningWithoutConnect(socket_path,
+                                                        /*timeoutSec=*/30);
 #else
-        "virtio-blk-pci-non-transitional,scsi=off,drive=drive-virtio-disk", i,
+        return CF_ERR("Unhandled check if vhost user block ready.");
 #endif
-        ",id=virtio-disk", i, (i == 0 ? ",bootindex=1" : ""));
+      });
+
+      qemu_cmd.AddParameter("-chardev");
+      qemu_cmd.AddParameter("socket,id=vhost-user-block-", i,
+                            ",path=", socket_path);
+      qemu_cmd.AddParameter("-device");
+      qemu_cmd.AddParameter(
+          "vhost-user-blk-pci-non-transitional,chardev=vhost-user-block-", i);
+    } else {
+      qemu_cmd.AddParameter("-drive");
+      qemu_cmd.AddParameter("file=", disk, ",if=none,id=drive-virtio-disk", i,
+                            ",aio=threads", readonly);
+      qemu_cmd.AddParameter("-device");
+      qemu_cmd.AddParameter(
+#ifdef __APPLE__
+          "virtio-blk-pci-non-transitional,drive=drive-virtio-disk", i,
+#else
+          "virtio-blk-pci-non-transitional,scsi=off,drive=drive-virtio-disk", i,
+#endif
+          ",id=virtio-disk", i, (i == 0 ? ",bootindex=1" : ""));
+    }
     ++i;
   }
 
@@ -802,10 +823,17 @@
 
   if (is_riscv64) {
     qemu_cmd.AddParameter("-kernel");
-  } else {
+    qemu_cmd.AddParameter(instance.bootloader());
+  } else if (is_arm) {
     qemu_cmd.AddParameter("-bios");
+    qemu_cmd.AddParameter(instance.bootloader());
+  } else {
+    qemu_cmd.AddParameter("-drive");
+    qemu_cmd.AddParameter("if=pflash,format=raw,readonly=on,file=",
+                          instance.bootloader());
+    qemu_cmd.AddParameter("-drive");
+    qemu_cmd.AddParameter("if=pflash,format=raw,file=", instance.pflash_path());
   }
-  qemu_cmd.AddParameter(instance.bootloader());
 
   if (instance.gdb_port() > 0) {
     qemu_cmd.AddParameter("-S");
@@ -813,7 +841,6 @@
     qemu_cmd.AddParameter("tcp::", instance.gdb_port());
   }
 
-  std::vector<MonitorCommand> commands;
   commands.emplace_back(std::move(qemu_cmd), true);
   return commands;
 }
diff --git a/host/libs/vm_manager/vhost_user.h b/host/libs/vm_manager/vhost_user.h
new file mode 100644
index 0000000..f1784fd
--- /dev/null
+++ b/host/libs/vm_manager/vhost_user.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <string>
+#include <string_view>
+
+#include "common/libs/utils/subprocess.h"
+#include "host/libs/config/cuttlefish_config.h"
+
+namespace cuttlefish {
+namespace vm_manager {
+
+struct VhostUserDeviceCommands {
+  Command device_cmd;
+  Command device_logs_cmd;
+  std::string socket_path;
+};
+
+Result<VhostUserDeviceCommands> VhostUserBlockDevice(
+    const CuttlefishConfig& config, int num, std::string_view disk_path);
+
+}  // namespace vm_manager
+}  // namespace cuttlefish
diff --git a/host/libs/vm_manager/vhost_user_block.cpp b/host/libs/vm_manager/vhost_user_block.cpp
new file mode 100644
index 0000000..bc70055
--- /dev/null
+++ b/host/libs/vm_manager/vhost_user_block.cpp
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "host/libs/vm_manager/vhost_user.h"
+
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <cstdlib>
+#include <string>
+#include <utility>
+
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+#include <vulkan/vulkan.h>
+
+#include "common/libs/utils/files.h"
+#include "common/libs/utils/result.h"
+#include "common/libs/utils/subprocess.h"
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/vm_manager/crosvm_builder.h"
+
+namespace cuttlefish {
+namespace vm_manager {
+
+// TODO(schuffelen): Deduplicate with BuildVhostUserGpu
+Result<VhostUserDeviceCommands> VhostUserBlockDevice(
+    const CuttlefishConfig& config, int num, std::string_view disk_path) {
+  const auto& instance = config.ForDefaultInstance();
+
+  CF_EXPECT(instance.vhost_user_block(), "Feature is not enabled");
+
+  auto block_device_socket_path = instance.PerInstanceInternalUdsPath(
+      fmt::format("vhost-user-block-{}-socket", num));
+  auto block_device_logs_path = instance.PerInstanceInternalPath(
+      fmt::format("crosvm_vhost_user_block_{}.fifo", num));
+  auto block_device_logs =
+      CF_EXPECT(SharedFD::Fifo(block_device_logs_path, 0666));
+
+  Command block_device_logs_cmd(HostBinaryPath("log_tee"));
+  block_device_logs_cmd.AddParameter("--process_name=crosvm_block_", num);
+  block_device_logs_cmd.AddParameter("--log_fd_in=", block_device_logs);
+  block_device_logs_cmd.SetStopper(KillSubprocessFallback([](Subprocess* proc) {
+    // Ask nicely so that log_tee gets a chance to process all the logs.
+    // TODO: b/335934714 - Make sure the process actually exits
+    bool res = kill(proc->pid(), SIGINT) == 0;
+    return res ? StopperResult::kStopSuccess : StopperResult::kStopFailure;
+  }));
+
+  const std::string crosvm_path = config.crosvm_binary();
+
+  CrosvmBuilder block_device_cmd;
+
+  // NOTE: The "main" crosvm process returns a kCrosvmVmResetExitCode when the
+  // guest exits but the "block" crosvm just exits cleanly with 0 after the
+  // "main" crosvm disconnects.
+  block_device_cmd.ApplyProcessRestarter(config.crosvm_binary(),
+                                         /*first_time_argument=*/"",
+                                         /*exit_code=*/0);
+
+  block_device_cmd.Cmd().AddParameter("devices");
+  block_device_cmd.Cmd().AddParameter("--block");
+  block_device_cmd.Cmd().AddParameter("vhost=", block_device_socket_path,
+                                      ",path=", disk_path);
+
+  if (instance.enable_sandbox()) {
+    const bool seccomp_exists = DirectoryExists(instance.seccomp_policy_dir());
+    const std::string& var_empty_dir = kCrosvmVarEmptyDir;
+    const bool var_empty_available = DirectoryExists(var_empty_dir);
+    CF_EXPECT(var_empty_available && seccomp_exists,
+              var_empty_dir << " is not an existing, empty directory."
+                            << "seccomp-policy-dir, "
+                            << instance.seccomp_policy_dir()
+                            << " does not exist");
+    block_device_cmd.Cmd().AddParameter("--jail");
+    block_device_cmd.Cmd().AddParameter("seccomp-policy-dir=",
+                                        instance.seccomp_policy_dir());
+  } else {
+    block_device_cmd.Cmd().AddParameter("--disable-sandbox");
+  }
+
+  return (VhostUserDeviceCommands){
+      .device_cmd = std::move(block_device_cmd.Cmd()),
+      .device_logs_cmd = std::move(block_device_logs_cmd),
+      .socket_path = block_device_socket_path,
+  };
+}
+
+}  // namespace vm_manager
+}  // namespace cuttlefish
diff --git a/host/libs/vm_manager/vm_manager.h b/host/libs/vm_manager/vm_manager.h
index 9b70477..fe66824 100644
--- a/host/libs/vm_manager/vm_manager.h
+++ b/host/libs/vm_manager/vm_manager.h
@@ -107,8 +107,10 @@
   // Block until the restore work is finished and the guest is running. Only
   // called if a snapshot is being restored.
   //
+  // If FD becomes readable or closed, gives up and returns false.
+  //
   // Must be thread safe.
-  virtual Result<void> WaitForRestoreComplete() const {
+  virtual Result<bool> WaitForRestoreComplete(SharedFD) const {
     return CF_ERR("not implemented");
   }
 };
diff --git a/shared/BoardConfig.mk b/shared/BoardConfig.mk
index 004372f..97cb156 100644
--- a/shared/BoardConfig.mk
+++ b/shared/BoardConfig.mk
@@ -18,10 +18,14 @@
 # Common BoardConfig for all supported architectures.
 #
 
-# Wear 32 bit is currently supported and 6.6 kernels don't support
-# 32 bit devices
+# Some targets still require 32 bit, and 6.6 kernels don't support
+# 32 bit devices (Wear, Go, Auto)
 ifneq (,$(findstring gwear_x86,$(PRODUCT_NAME)))
 TARGET_KERNEL_USE ?= 6.1
+else ifneq (,$(findstring x86_phone,$(PRODUCT_NAME)))
+TARGET_KERNEL_USE ?= 6.1
+else ifneq (,$(findstring x86_tv,$(PRODUCT_NAME)))
+TARGET_KERNEL_USE ?= 6.1
 else
 TARGET_KERNEL_USE ?= 6.6
 endif
@@ -266,12 +270,12 @@
 
 # Wifi.
 BOARD_WLAN_DEVICE           := emulator
-BOARD_HOSTAPD_PRIVATE_LIB   := lib_driver_cmd_simulated_cf
+BOARD_HOSTAPD_PRIVATE_LIB   := lib_driver_cmd_simulated_cf_bp
 WIFI_HIDL_FEATURE_DUAL_INTERFACE := true
 WIFI_HAL_INTERFACE_COMBINATIONS := {{{STA}, 1}, {{AP}, 1}, {{P2P}, 1}}
 BOARD_HOSTAPD_DRIVER        := NL80211
 BOARD_WPA_SUPPLICANT_DRIVER := NL80211
-BOARD_WPA_SUPPLICANT_PRIVATE_LIB := lib_driver_cmd_simulated_cf
+BOARD_WPA_SUPPLICANT_PRIVATE_LIB := lib_driver_cmd_simulated_cf_bp
 WPA_SUPPLICANT_VERSION      := VER_0_8_X
 WIFI_DRIVER_FW_PATH_PARAM   := "/dev/null"
 WIFI_DRIVER_FW_PATH_STA     := "/dev/null"
diff --git a/shared/api_level.h b/shared/api_level.h
deleted file mode 100644
index 06cf49c..0000000
--- a/shared/api_level.h
+++ /dev/null
@@ -1,18 +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.
- */
-#pragma once
-
-#define PRODUCT_SHIPPING_API_LEVEL 35
diff --git a/shared/auto/sepolicy/vendor/hal_vehicle_default.te b/shared/auto/sepolicy/vendor/hal_vehicle_default.te
index d49d1d4..96c447a 100644
--- a/shared/auto/sepolicy/vendor/hal_vehicle_default.te
+++ b/shared/auto/sepolicy/vendor/hal_vehicle_default.te
@@ -2,3 +2,4 @@
 typeattribute hal_vehicle_default hal_automotive_socket_exemption;
 
 net_domain(hal_vehicle_default)
+get_prop(hal_vehicle_default, vendor_vhal_proxy_server_port_prop)
diff --git a/shared/camera/device_vendor.mk b/shared/camera/device_vendor.mk
index 599c32f..3e9c4f2 100644
--- a/shared/camera/device_vendor.mk
+++ b/shared/camera/device_vendor.mk
@@ -20,14 +20,8 @@
     androidx.camera.extensions.impl.advanced advancedSample_camera_extensions.xml \
     libencoderjpeg_jni
 
-PRODUCT_ARTIFACT_PATH_REQUIREMENT_ALLOWED_LIST += \
-    system/app/EyesFreeVidService/EyesFreeVidService.apk
-
 PRODUCT_PACKAGES += EyesFreeVidService
 
-PRODUCT_VENDOR_PROPERTIES += \
-    ro.vendor.camera.extensions.package=android.camera.extensions.impl.service \
-    ro.vendor.camera.extensions.service=android.camera.extensions.impl.service.EyesFreeVidService
 else
 PRODUCT_PACKAGES += androidx.camera.extensions.impl sample_camera_extensions.xml
 endif
diff --git a/shared/config/graphics/init_graphics.vendor.rc b/shared/config/graphics/init_graphics.vendor.rc
index 1605c3d..2dafd6d 100644
--- a/shared/config/graphics/init_graphics.vendor.rc
+++ b/shared/config/graphics/init_graphics.vendor.rc
@@ -7,6 +7,7 @@
     setprop ro.hardware.gralloc ${ro.boot.hardware.gralloc}
     setprop ro.hardware.hwcomposer ${ro.boot.hardware.hwcomposer}
     setprop ro.vendor.hwcomposer.display_finder_mode ${ro.boot.hardware.hwcomposer.display_finder_mode}
+    setprop ro.vendor.hwcomposer.display_framebuffer_format ${ro.boot.hardware.hwcomposer.display_framebuffer_format}
     setprop ro.vendor.hwcomposer.mode ${ro.boot.hardware.hwcomposer.mode}
     setprop ro.hardware.vulkan ${ro.boot.hardware.vulkan}
     setprop ro.cpuvulkan.version ${ro.boot.cpuvulkan.version}
diff --git a/shared/config/input/Android.bp b/shared/config/input/Android.bp
index 7db65df..544e31c 100644
--- a/shared/config/input/Android.bp
+++ b/shared/config/input/Android.bp
@@ -26,6 +26,9 @@
     // Install the apex in /vendor/apex
     soc_specific: true,
     prebuilts: [
+        // Set input_device.config_file.apex={apexname} sysprop
+        "com.google.cf.input.config.rc",
+        // Configs
         "Crosvm_Virtio_Multitouch_Touchpad_0.idc",
         "Crosvm_Virtio_Multitouch_Touchscreen_0.idc",
         "Crosvm_Virtio_Multitouch_Touchscreen_1.idc",
@@ -35,6 +38,12 @@
     ],
 }
 
+prebuilt_etc {
+    name: "com.google.cf.input.config.rc",
+    src: "com.google.cf.input.config.rc",
+    installable: false,
+}
+
 prebuilt_defaults {
     name: "crosvm_idc_defaults",
     relative_install_path: "usr/idc",
diff --git a/shared/config/input/com.google.cf.input.config.rc b/shared/config/input/com.google.cf.input.config.rc
new file mode 100644
index 0000000..917c3fb
--- /dev/null
+++ b/shared/config/input/com.google.cf.input.config.rc
@@ -0,0 +1,2 @@
+on property:apex.all.ready=true
+  setprop input_device.config_file.apex com.google.cf.input.config
diff --git a/shared/config/input/file_contexts b/shared/config/input/file_contexts
index e982bd5..3f8e059 100644
--- a/shared/config/input/file_contexts
+++ b/shared/config/input/file_contexts
@@ -1,4 +1,5 @@
 (/.*)?                          u:object_r:vendor_file:s0
+/etc(/.*)?                      u:object_r:vendor_configs_file:s0
 /etc/usr/keylayout(/.*)?\.kl    u:object_r:vendor_keylayout_file:s0
 /etc/usr/keychars(/.*)?\.kcm    u:object_r:vendor_keychars_file:s0
 /etc/usr/idc(/.*)?\.idc         u:object_r:vendor_idc_file:s0
diff --git a/shared/consumerir/device_vendor.mk b/shared/consumerir/device_vendor.mk
index 7dc9373..0ece81d 100644
--- a/shared/consumerir/device_vendor.mk
+++ b/shared/consumerir/device_vendor.mk
@@ -13,13 +13,5 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-
-# TODO(b/302088370) remove the condition when libapexsupport is available
-ifeq ($(RELEASE_AIDL_USE_UNFROZEN),true)
 PRODUCT_PACKAGES += \
     com.google.cf.ir
-else
-PRODUCT_PACKAGES += \
-    android.hardware.ir-service.example \
-    consumerir.default
-endif # RELEASE_AIDL_USE_UNFROZEN
diff --git a/shared/device.mk b/shared/device.mk
index 34d87ea..fb60c3a 100644
--- a/shared/device.mk
+++ b/shared/device.mk
@@ -157,6 +157,8 @@
     tombstone_producer \
     suspend_blocker \
     metrics_helper \
+    snapshot_hook_post_resume \
+    snapshot_hook_pre_suspend
 
 $(call soong_config_append,cvd,launch_configs,cvd_config_auto.json cvd_config_auto_portrait.json cvd_config_auto_md.json cvd_config_foldable.json cvd_config_go.json cvd_config_phone.json cvd_config_slim.json cvd_config_tablet.json cvd_config_tv.json cvd_config_wear.json)
 $(call soong_config_append,cvd,grub_config,grub.cfg)
@@ -218,6 +220,8 @@
 endif
 DEVICE_MANIFEST_FILE += $(LOCAL_DEVICE_FCM_MANIFEST_FILE)
 
+PRODUCT_CHECK_PREBUILT_MAX_PAGE_SIZE := true
+
 #
 # General files
 #
@@ -258,7 +262,6 @@
 # Install .kcm/.kl/.idc files via input.config apex
 #
 PRODUCT_PACKAGES += com.google.cf.input.config
-PRODUCT_VENDOR_PROPERTIES += input_device.config_file.apex=com.google.cf.input.config
 
 PRODUCT_PACKAGES += \
     fstab.cf.f2fs.hctr2 \
@@ -291,6 +294,9 @@
     com.android.hardware.authsecret
 
 ifndef LOCAL_AUDIO_PRODUCT_PACKAGE
+#
+# Still use HIDL Audio HAL on 'next'
+#
 LOCAL_AUDIO_PRODUCT_PACKAGE += \
     android.hardware.audio.parameter_parser.example_service \
     com.android.hardware.audio
@@ -420,18 +426,14 @@
 #
 # Non-secure implementation of AuthGraph HAL for compliance.
 #
-ifeq ($(RELEASE_AIDL_USE_UNFROZEN),true)
 PRODUCT_PACKAGES += \
     com.android.hardware.security.authgraph
-endif
 
 #
 # Non-secure implementation of Secretkeeper HAL for compliance.
 #
-ifeq ($(RELEASE_AIDL_USE_UNFROZEN),true)
 PRODUCT_PACKAGES += \
     com.android.hardware.security.secretkeeper
-endif
 
 #
 # Power and PowerStats HALs
@@ -541,6 +543,11 @@
 PRODUCT_VENDOR_PROPERTIES += \
     ro.surface_flinger.supports_background_blur=1
 
+# Set Game Default Frame Rate
+# See b/286084594
+PRODUCT_DEFAULT_PROPERTY_OVERRIDES += \
+    ro.surface_flinger.game_default_frame_rate_override=60
+
 # Disable GPU-intensive background blur for widget picker
 PRODUCT_SYSTEM_PROPERTIES += \
     ro.launcher.depth.widget=0
diff --git a/shared/graphics/device_vendor.mk b/shared/graphics/device_vendor.mk
index 2d01146..d13904d 100644
--- a/shared/graphics/device_vendor.mk
+++ b/shared/graphics/device_vendor.mk
@@ -45,7 +45,6 @@
 # Gfxstream Vulkan implementation (Vulkan streamed to the host).
 ifeq ($(TARGET_VULKAN_SUPPORT),true)
 PRODUCT_PACKAGES += com.google.cf.vulkan
-PRODUCT_VENDOR_PROPERTIES += ro.vulkan.apex=com.google.cf.vulkan
 endif
 
 #
diff --git a/shared/graphics/sepolicy/property_contexts b/shared/graphics/sepolicy/property_contexts
new file mode 100644
index 0000000..4af2db1
--- /dev/null
+++ b/shared/graphics/sepolicy/property_contexts
@@ -0,0 +1,10 @@
+ro.boot.cpuvulkan.version  u:object_r:vendor_graphics_config_prop:s0 exact int
+ro.boot.hardware.egl u:object_r:vendor_graphics_config_prop:s0 exact string
+ro.boot.hardware.gralloc u:object_r:vendor_graphics_config_prop:s0 exact string
+ro.boot.hardware.hwcomposer u:object_r:vendor_graphics_config_prop:s0 exact string
+ro.boot.hardware.vulkan u:object_r:vendor_graphics_config_prop:s0 exact string
+ro.boot.lcd_density u:object_r:vendor_graphics_config_prop:s0 exact int
+ro.vendor.hwcomposer.display_finder_mode  u:object_r:vendor_hwcomposer_prop:s0 exact string
+ro.vendor.hwcomposer.display_framebuffer_format u:object_r:vendor_hwcomposer_prop:s0 exact string
+ro.vendor.hwcomposer.mode  u:object_r:vendor_hwcomposer_prop:s0 exact string
+ro.vendor.hwcomposer.pmem  u:object_r:vendor_hwcomposer_prop:s0 exact string
diff --git a/shared/overlays/core/res/values/config.xml b/shared/overlays/core/res/values/config.xml
index bfcec6c..b9d31ed 100644
--- a/shared/overlays/core/res/values/config.xml
+++ b/shared/overlays/core/res/values/config.xml
@@ -20,4 +20,11 @@
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <!-- Show the "Adaptive Brightness" toggle. -->
     <bool name="config_automatic_brightness_available">true</bool>
+
+    <string name="config_extensionFallbackPackageName" translatable="false">
+            android.camera.extensions.impl.service
+    </string>
+    <string name="config_extensionFallbackServiceName" translatable="false">
+            android.camera.extensions.impl.service.EyesFreeVidService
+    </string>
 </resources>
\ No newline at end of file
diff --git a/shared/sepolicy/system_ext/private/platform_app.te b/shared/sepolicy/system_ext/private/platform_app.te
index 3a789d8..fc1b99f 100644
--- a/shared/sepolicy/system_ext/private/platform_app.te
+++ b/shared/sepolicy/system_ext/private/platform_app.te
@@ -2,3 +2,6 @@
 
 # allow systemui to set boot animation colors
 set_prop(platform_app, bootanim_system_prop);
+
+# allow platform_app/systemui access to fingerprint
+hal_client_domain(platform_app, hal_fingerprint)
diff --git a/shared/sepolicy/vendor/file_contexts b/shared/sepolicy/vendor/file_contexts
index ca19b92..f5a5cbe 100644
--- a/shared/sepolicy/vendor/file_contexts
+++ b/shared/sepolicy/vendor/file_contexts
@@ -115,6 +115,8 @@
 /vendor/bin/hw/android\.hardware\.authsecret-service.example u:object_r:hal_authsecret_default_exec:s0
 /vendor/bin/dlkm_loader  u:object_r:dlkm_loader_exec:s0
 /vendor/bin/init\.wifi    u:object_r:init_wifi_sh_exec:s0
+/vendor/bin/snapshot_hook_post_resume u:object_r:snapshot_hook_sh:s0
+/vendor/bin/snapshot_hook_pre_suspend u:object_r:snapshot_hook_sh:s0
 
 /vendor/lib(64)?/hw/android\.hardware\.health@2\.0-impl-2\.1-cuttlefish\.so  u:object_r:same_process_hal_file:s0
 /vendor/lib(64)?/libcuttlefish_fs.so  u:object_r:same_process_hal_file:s0
diff --git a/shared/sepolicy/vendor/property.te b/shared/sepolicy/vendor/property.te
index e9ba199..589e729 100644
--- a/shared/sepolicy/vendor/property.te
+++ b/shared/sepolicy/vendor/property.te
@@ -5,3 +5,4 @@
 vendor_internal_prop(vendor_device_prop)
 vendor_internal_prop(vendor_uwb_prop)
 vendor_internal_prop(vendor_otsim_local_interface_prop)
+vendor_internal_prop(vendor_vhal_proxy_server_port_prop)
diff --git a/shared/sepolicy/vendor/property_contexts b/shared/sepolicy/vendor/property_contexts
index ade0a8e..44a8b87 100644
--- a/shared/sepolicy/vendor/property_contexts
+++ b/shared/sepolicy/vendor/property_contexts
@@ -1,17 +1,9 @@
-ro.boot.cpuvulkan.version  u:object_r:vendor_graphics_config_prop:s0 exact int
-ro.boot.hardware.egl u:object_r:vendor_graphics_config_prop:s0 exact string
-ro.boot.hardware.gralloc u:object_r:vendor_graphics_config_prop:s0 exact string
-ro.boot.hardware.hwcomposer u:object_r:vendor_graphics_config_prop:s0 exact string
-ro.boot.hardware.vulkan u:object_r:vendor_graphics_config_prop:s0 exact string
-ro.boot.lcd_density u:object_r:vendor_graphics_config_prop:s0 exact int
 ro.boot.enable_confirmationui  u:object_r:vendor_enable_confirmationui_prop:s0
 ro.boot.modem_simulator_ports  u:object_r:vendor_modem_simulator_ports_prop:s0
 ro.boot.wifi_mac_prefix  u:object_r:vendor_wifi_mac_prefix:s0 exact string
+ro.boot.vhal_proxy_server_port  u:object_r:vendor_vhal_proxy_server_port_prop:s0
 ro.vendor.wifi_impl u:object_r:vendor_wifi_impl:s0 exact string
 ro.vendor.boot_security_patch u:object_r:vendor_boot_security_patch_level_prop:s0
-ro.vendor.hwcomposer.display_finder_mode  u:object_r:vendor_hwcomposer_prop:s0 exact string
-ro.vendor.hwcomposer.mode  u:object_r:vendor_hwcomposer_prop:s0 exact string
-ro.vendor.hwcomposer.pmem  u:object_r:vendor_hwcomposer_prop:s0 exact string
 ro.vendor.uwb.dev              u:object_r:vendor_uwb_prop:s0 exact string
 vendor.wlan.firmware.version   u:object_r:vendor_wlan_versions_prop:s0 exact string
 vendor.wlan.driver.version     u:object_r:vendor_wlan_versions_prop:s0 exact string
diff --git a/shared/sepolicy/vendor/snapshot_hook.te b/shared/sepolicy/vendor/snapshot_hook.te
new file mode 100644
index 0000000..03d9f75
--- /dev/null
+++ b/shared/sepolicy/vendor/snapshot_hook.te
@@ -0,0 +1,2 @@
+type snapshot_hook_sh, exec_type, vendor_file_type, file_type;
+allow shell snapshot_hook_sh:file { getattr open read execute execute_no_trans };
diff --git a/shared/tv/device_vendor.mk b/shared/tv/device_vendor.mk
index f6e10ab..6b1de33 100644
--- a/shared/tv/device_vendor.mk
+++ b/shared/tv/device_vendor.mk
@@ -91,3 +91,13 @@
      CuttlefishTetheringOverlayGoogle \
      CuttlefishWifiOverlayGoogle \
      TvWifiOverlayGoogle
+
+# OEM Key:
+#   ATV00       - Schema identifier.
+#   0           - Voice remote not included.
+#   000         - Not a panel TV.
+#   24          - Year of production.
+#   CUTTLEFISH  - Custom OEM key for future use.
+#   EMU         - Last 3 char for custom targeting.
+PRODUCT_PRODUCT_PROPERTIES += \
+    ro.oem.key1=ATV00000024CUTTLEFISHEMU
diff --git a/system_image/Android.bp b/system_image/Android.bp
index e4295da..b914062 100644
--- a/system_image/Android.bp
+++ b/system_image/Android.bp
@@ -69,7 +69,6 @@
         "DroidSansMono.ttf",
         "NotoColorEmoji.ttf",
         "NotoColorEmojiFlags.ttf",
-        "NotoColorEmojiLegacy.ttf",
         "NotoNaskhArabic-Bold.ttf",
         "NotoNaskhArabic-Regular.ttf",
         "NotoNaskhArabicUI-Bold.ttf",
@@ -242,6 +241,7 @@
         "NotoSerifGurmukhi-VF.ttf",
         "NotoSerifHebrew-Bold.ttf",
         "NotoSerifHebrew-Regular.ttf",
+        "NotoSerifHentaigana.ttf",
         "NotoSerifKannada-VF.ttf",
         "NotoSerifKhmer-Bold.otf",
         "NotoSerifKhmer-Regular.otf",
@@ -304,6 +304,11 @@
     avb_hash_algorithm: "sha256",
 
     deps: [
+        "abx",
+        "aconfigd",
+        "aflags",
+        "am",
+        "android_vintf_manifest",
         "android.hardware.biometrics.fingerprint@2.1", // generic_system
         "android.hardware.radio@1.0", // generic_system
         "android.hardware.radio@1.1", // generic_system
@@ -313,15 +318,11 @@
         "android.hardware.radio.config@1.0", // generic_system
         "android.hardware.radio.deprecated@1.0", // generic_system
         "android.hardware.secure_element@1.0", // generic_system
-        "abx",
-        "aconfigd",
-        "aflags",
-        "am",
-        "android_build_prop",
-        "android_vintf_manifest",
+        "android.software.credentials.prebuilt.xml", // generic_system
+        "android.software.webview.prebuilt.xml", // media_system
+        "android.software.window_magnification.prebuilt.xml", // handheld_system
         "android.system.suspend-service",
         "apexd",
-        "app_process",
         "appops",
         "appwidget",
         "atrace",
@@ -332,7 +333,6 @@
         "bmgr",
         "bootanimation",
         "bootstat",
-        "boringssl_self_test",
         "bpfloader",
         "bu",
         "bugreport",
@@ -354,6 +354,7 @@
         "dumpstate",
         "dumpsys",
         "e2fsck",
+        "enhanced-confirmation.xml", // base_system
         "etc_hosts",
         "flags_health_check",
         "framework-audio_effects.xml", // for handheld // handheld_system
@@ -402,7 +403,6 @@
         "libaaudio",
         "libalarm_jni",
         "libamidi",
-        "linker", // ok
         "llkd", // base_system
         "lmkd", // base_system
         "local_time.default", // handheld_vendo
@@ -427,6 +427,7 @@
         "netutils-wrapper-1.0", // full_base
         "odsign", // base_system
         "otapreopt_script", // generic_system
+        "package-shareduid-allowlist.xml", // base_system
         "passwd_system", // base_system
         "perfetto", // base_system
         "ping", // base_system
@@ -443,6 +444,7 @@
         "printflags", // base_system
         "privapp-permissions-platform.xml", // base_system
         "prng_seeder", // base_system
+        "public.libraries.android.txt",
         "recovery-persist", // base_system
         "recovery-refresh", // generic_system
         "requestsync", // media_system
@@ -462,8 +464,8 @@
         "sgdisk", // base_system
         "sm", // base_system
         "snapshotctl", // base_system
-        "snapuserd_ramdisk", // ramdisk
         "snapuserd", // base_system
+        "snapuserd_ramdisk", // ramdisk
         "storaged", // base_system
         "surfaceflinger", // base_system
         "svc", // base_system
@@ -489,7 +491,17 @@
         "wifi.rc", // base_system
         "wificond", // base_system
         "wm", // base_system
-    ] + select(product_variable("debuggable"), {
+    ] + select(release_flag("RELEASE_PLATFORM_VERSION_CODENAME"), {
+        "REL": [],
+        default: [
+            "android.software.preview_sdk.prebuilt.xml", // media_system
+        ],
+    }) + select(soong_config_variable("ANDROID", "release_package_profiling_module"), {
+        "true": [
+            "trace_redactor", // base_system (RELEASE_PACKAGE_PROFILING_MODULE)
+        ],
+        default: [],
+    }) + select(product_variable("debuggable"), {
         true: [
             "adevice_fingerprint",
             "arping",
@@ -544,12 +556,14 @@
                 "android.test.base", // from runtime_libart
                 "android.test.mock", // base_system
                 "android.test.runner", // base_system
+                "aosp_cf_x86_64_system-build.prop",
                 "aosp_mainline_modules", // ok
                 "BackupRestoreConfirmation", // base_system
                 "BasicDreams", // handheld_system
                 "BlockedNumberProvider", // handheld_system
                 "BluetoothMidiService", // handheld_system
                 "BookmarkProvider", // handheld_system
+                "build_flag_system", // base_system
                 "BuiltInPrintService", // handheld_system
                 "CalendarProvider", // handheld_system
                 "CallLogBackup", // telephony_system
@@ -567,15 +581,18 @@
                 "com.android.media.remotedisplay", // media_system
                 "com.android.mediadrm.signer", // media_system
                 "com.android.nfc_extras", // ok
+                "com.android.nfcservices", // base_system (RELEASE_PACKAGE_NFC_STACK != NfcNci)
                 "com.android.runtime", // ok
                 "CompanionDeviceManager", // media_system
                 "ContactsProvider", // base_system
                 "CredentialManager", // handheld_system
                 "DeviceAsWebcam", // handheld_system
+                "dex_bootjars",
                 "DocumentsUI", // handheld_system
                 "DownloadProvider", // base_system
                 "DownloadProviderUi", // handheld_system
                 "DynamicSystemInstallationService", // base_system
+                "E2eeContactKeysProvider", // base_system
                 "EasterEgg", // handheld_system
                 "ext", // from runtime_libart
                 "ExternalStorageProvider", // handheld_system
@@ -584,7 +601,6 @@
                 "framework-graphics", // base_system
                 "framework-location", // base_system
                 "framework-minus-apex-install-dependencies", // base_system
-                "framework-nfc", // base_system
                 "FusedLocation", // handheld_system
                 "HTMLViewer", // media_system
                 "hwservicemanager_compat_symlink_module", // base_system
@@ -604,7 +620,6 @@
                 "MtpService", // handheld_system
                 "MusicFX", // handheld_system
                 "NetworkStack", // base_system
-                "NfcNci", // base_system
                 "ONS", // telephony_system
                 "org.apache.http.legacy", // base_system
                 "perfetto-extras", // system
@@ -616,16 +631,18 @@
                 "PrintSpooler", // handheld_system
                 "ProxyHandler", // handheld_system
                 "SecureElement", // handheld_system
-                "selinux_policy_system_soong", // ok
-                "services", // base_system
                 "SettingsProvider", // base_system
                 "SharedStorageBackup", // handheld_system
-                "shell_and_utilities_system", // ok
                 "Shell", // base_system
                 "SimAppDialog", // handheld_system
                 "SoundPicker", // not installed by anyone
                 "StatementService", // media_system
                 "Stk", // generic_system
+                "sanitizer.libraries.txt", // base_system
+                "selinux_policy_system_soong", // ok
+                "services", // base_system
+                "shell_and_utilities_system", // ok
+                "system_compatibility_matrix.xml", //base_system
                 "Tag", // generic_system
                 "Telecom", // handheld_system
                 "telephony-common", // libs from TeleService
@@ -636,7 +653,22 @@
                 "voip-common", // base_system
                 "VpnDialogs", // handheld_system
                 "WallpaperBackup", // base_system
-            ],
+            ] + select(soong_config_variable("ANDROID", "release_crashrecovery_module"), {
+                "true": [
+                    "com.android.crashrecovery", // base_system (RELEASE_CRASHRECOVERY_MODULE)
+                ],
+                default: [],
+            }) + select(soong_config_variable("ANDROID", "release_package_profiling_module"), {
+                "true": [
+                    "com.android.profiling", // base_system (RELEASE_PACKAGE_PROFILING_MODULE)
+                ],
+                default: [],
+            }) + select(release_flag("RELEASE_AVATAR_PICKER_APP"), {
+                true: [
+                    "AvatarPicker", // generic_system (RELEASE_AVATAR_PICKER_APP)
+                ],
+                default: [],
+            }),
         },
         prefer32: {
             deps: [
@@ -646,13 +678,14 @@
         },
         lib64: {
             deps: [
-                "boringssl_self_test",
                 "libgsi",
                 "servicemanager",
             ],
         },
         both: {
             deps: [
+                "app_process", // base_system
+                "boringssl_self_test", // base_system
                 "libandroid_runtime",
                 "libandroid_servers",
                 "libandroid",
@@ -741,6 +774,7 @@
                 "libwebviewchromium_loader", // media_system
                 "libwebviewchromium_plat_support", // media_system
                 "libwilhelm", // base_system
+                "linker", // base_system
             ] + select(soong_config_variable("ANDROID", "TARGET_DYNAMIC_64_32_DRMSERVER"), {
                 "true": ["drmserver"],
                 default: [],
@@ -760,9 +794,9 @@
     installable: false,
 }
 
-prebuilt_root {
-    name: "android_build_prop",
-    filename: "build.prop",
-    src: "build.prop",
-    installable: false,
+build_prop {
+    name: "aosp_cf_x86_64_system-build.prop",
+    stem: "build.prop",
+    product_config: ":product_config",
+    no_full_install: true,
 }
diff --git a/system_image/build.prop b/system_image/build.prop
deleted file mode 100644
index e65b528..0000000
--- a/system_image/build.prop
+++ /dev/null
@@ -1,118 +0,0 @@
-####################################
-# from generate-common-build-props
-# These properties identify this partition image.
-####################################
-ro.product.system.brand=Android
-ro.product.system.device=generic
-ro.product.system.manufacturer=Android
-ro.product.system.model=mainline
-ro.product.system.name=mainline
-ro.system.product.cpu.abilist=x86_64,arm64-v8a
-ro.system.product.cpu.abilist32=
-ro.system.product.cpu.abilist64=x86_64,arm64-v8a
-ro.system.build.date=Tue Jan 23 13:45:29 KST 2024
-ro.system.build.date.utc=1705985129
-ro.system.build.id=MAIN
-ro.system.build.tags=test-keys
-ro.system.build.type=userdebug
-ro.system.build.version.release=14
-ro.system.build.version.release_or_codename=VanillaIceCream
-ro.system.build.version.sdk=34
-####################################
-# from out/target/product/vsoc_x86_64_only/obj/PACKAGING/system_build_prop_intermediates/buildinfo.prop
-####################################
-# begin build properties
-# autogenerated by buildinfo.sh
-ro.build.legacy.id=MAIN
-ro.build.version.sdk=34
-ro.build.version.preview_sdk=1
-ro.build.version.preview_sdk_fingerprint=67142e4165a8947eaad71ba44204ce05
-ro.build.version.codename=VanillaIceCream
-ro.build.version.all_codenames=UpsideDownCake,VanillaIceCream
-ro.build.version.known_codenames=Base,Base11,Cupcake,Donut,Eclair,Eclair01,EclairMr1,Froyo,Gingerbread,GingerbreadMr1,Honeycomb,HoneycombMr1,HoneycombMr2,IceCreamSandwich,IceCreamSandwichMr1,JellyBean,JellyBeanMr1,JellyBeanMr2,Kitkat,KitkatWatch,Lollipop,LollipopMr1,M,N,NMr1,O,OMr1,P,Q,R,S,Sv2,Tiramisu,UpsideDownCake,VanillaIceCream
-ro.build.version.release=14
-ro.build.version.release_or_codename=VanillaIceCream
-ro.build.version.release_or_preview_display=VanillaIceCream
-ro.build.version.security_patch=2023-12-05
-ro.build.version.base_os=
-ro.build.version.min_supported_target_sdk=28
-ro.build.date=Tue Jan 23 13:45:29 KST 2024
-ro.build.date.utc=1705985129
-ro.build.type=userdebug
-ro.build.tags=test-keys
-ro.build.flavor=aosp_cf_x86_64_only_phone-userdebug
-# ro.product.cpu.abi and ro.product.cpu.abi2 are obsolete,
-# use ro.product.cpu.abilist instead.
-ro.product.cpu.abi=x86_64
-ro.product.locale=en-US
-ro.wifi.channels=
-# ro.build.product is obsolete; use ro.product.device
-ro.build.product=vsoc_x86_64_only
-# Do not try to parse description or thumbprint
-# end build properties
-####################################
-# from variable ADDITIONAL_SYSTEM_PROPERTIES
-####################################
-ro.treble.enabled=true
-ro.llndk.api_level=202404
-ro.actionable_compatible_property.enabled=true
-persist.debug.dalvik.vm.core_platform_api_policy=just-warn
-ro.postinstall.fstab.prefix=/system
-ro.vndk.deprecate=true
-ro.secure=1
-security.perf_harden=1
-ro.allow.mock.location=0
-ro.debuggable=1
-dalvik.vm.lockprof.threshold=500
-net.bt.name=Android
-ro.force.debuggable=0
-####################################
-# from variable PRODUCT_SYSTEM_PROPERTIES
-####################################
-debug.atrace.tags.enableflags=0
-persist.traced.enable=1
-dalvik.vm.image-dex2oat-Xms=64m
-dalvik.vm.image-dex2oat-Xmx=64m
-dalvik.vm.dex2oat-Xms=64m
-dalvik.vm.dex2oat-Xmx=512m
-dalvik.vm.usejit=true
-dalvik.vm.dexopt.secondary=true
-dalvik.vm.dexopt.thermal-cutoff=2
-dalvik.vm.appimageformat=lz4
-ro.dalvik.vm.native.bridge=0
-pm.dexopt.post-boot=verify
-pm.dexopt.first-boot=verify
-pm.dexopt.boot-after-ota=verify
-pm.dexopt.boot-after-mainline-update=verify
-pm.dexopt.install=speed-profile
-pm.dexopt.install-fast=skip
-pm.dexopt.install-bulk=speed-profile
-pm.dexopt.install-bulk-secondary=verify
-pm.dexopt.install-bulk-downgraded=verify
-pm.dexopt.install-bulk-secondary-downgraded=verify
-pm.dexopt.bg-dexopt=speed-profile
-pm.dexopt.ab-ota=speed-profile
-pm.dexopt.inactive=verify
-pm.dexopt.cmdline=verify
-pm.dexopt.shared=speed
-dalvik.vm.dex2oat-resolve-startup-strings=true
-dalvik.vm.dex2oat-max-image-block-size=524288
-dalvik.vm.minidebuginfo=true
-dalvik.vm.dex2oat-minidebuginfo=true
-dalvik.vm.madvise.vdexfile.size=104857600
-dalvik.vm.madvise.odexfile.size=104857600
-dalvik.vm.madvise.artfile.size=4294967295
-dalvik.vm.usap_pool_enabled=false
-dalvik.vm.usap_refill_threshold=1
-dalvik.vm.usap_pool_size_max=3
-dalvik.vm.usap_pool_size_min=1
-dalvik.vm.usap_pool_refill_delay_ms=3000
-dalvik.vm.useartservice=true
-ro.apex.updatable=true
-ro.launcher.depth.widget=0
-####################################
-# from variable PRODUCT_SYSTEM_DEFAULT_PROPERTIES
-####################################
-# Auto-added by post_process_props.py
-persist.sys.usb.config=adb
-# end of file
diff --git a/tests/hal/hal_implementation_test.cpp b/tests/hal/hal_implementation_test.cpp
index 5ed62d7..5513595 100644
--- a/tests/hal/hal_implementation_test.cpp
+++ b/tests/hal/hal_implementation_test.cpp
@@ -262,8 +262,8 @@
     {"android.hardware.identity.", 5, 266869317},
 
     {"android.se.omapi.", 1, 266870904},
-    {"android.hardware.soundtrigger3.", 2, 266941225},
-    {"android.media.soundtrigger.", 2, 266941225},
+    {"android.hardware.soundtrigger3.", 3, 266941225},
+    {"android.media.soundtrigger.", 3, 266941225},
     {"android.hardware.weaver.", 2, 262418065},
 
     {"android.automotive.computepipe.registry.", 2, 273549907},
diff --git a/tests/snapshot/src/com/android/cuttlefish/tests/SnapshotTest.java b/tests/snapshot/src/com/android/cuttlefish/tests/SnapshotTest.java
index a697863..1f147ab 100644
--- a/tests/snapshot/src/com/android/cuttlefish/tests/SnapshotTest.java
+++ b/tests/snapshot/src/com/android/cuttlefish/tests/SnapshotTest.java
@@ -52,9 +52,10 @@
     public void testSnapshot() throws Exception {
         String snapshotId = "snapshot_" + UUID.randomUUID().toString();
 
+        // Reboot to make sure device isn't dirty from previous tests.
+        getDevice().reboot();
         // Snapshot the device
-        boolean snapshotRes = new DeviceSnapshotHandler().snapshotDevice(getDevice(), snapshotId);
-        assertTrue("failed to snapshot.", snapshotRes);
+        new DeviceSnapshotHandler().snapshotDevice(getDevice(), snapshotId);
 
         // Create a file in tmp directory
         final String tmpFile = "/data/local/tmp/snapshot_tmp";
@@ -71,11 +72,7 @@
         for (int i = 0; i < mTestCount; i++) {
             CLog.d("Restore snapshot attempt #%d", i);
             long start = System.currentTimeMillis();
-            boolean restoreRes =
-                    new DeviceSnapshotHandler().restoreSnapshotDevice(getDevice(), snapshotId);
-            assertTrue(
-                    String.format("Restore snapshot for device reset failed during attempt #%d", i),
-                    restoreRes);
+            new DeviceSnapshotHandler().restoreSnapshotDevice(getDevice(), snapshotId);
             long duration = System.currentTimeMillis() - start;
             CLog.d("Restore snapshot took %dms to finish", duration);
         }
@@ -98,13 +95,12 @@
     public void testSnapshotReboot() throws Exception {
         String snapshotId = "snapshot_" + UUID.randomUUID().toString();
 
+        // Reboot to make sure device isn't dirty from previous tests.
+        getDevice().reboot();
         // Snapshot the device.
-        boolean snapshotRes = new DeviceSnapshotHandler().snapshotDevice(getDevice(), snapshotId);
-        assertTrue("failed to snapshot", snapshotRes);
+        new DeviceSnapshotHandler().snapshotDevice(getDevice(), snapshotId);
         // Restore the device.
-        boolean restoreRes =
-                new DeviceSnapshotHandler().restoreSnapshotDevice(getDevice(), snapshotId);
-        assertTrue("Restore snapshot for device reset failed", restoreRes);
+        new DeviceSnapshotHandler().restoreSnapshotDevice(getDevice(), snapshotId);
         // Reboot the device.
         getDevice().reboot();
         // Verify that the device is back online.
@@ -116,13 +112,12 @@
     public void testSnapshotPowerwash() throws Exception {
         String snapshotId = "snapshot_" + UUID.randomUUID().toString();
 
-        // Snapshot the device>
-        boolean snapshotRes = new DeviceSnapshotHandler().snapshotDevice(getDevice(), snapshotId);
-        assertTrue("failed to snapshot", snapshotRes);
+        // Reboot to make sure device isn't dirty from previous tests.
+        getDevice().reboot();
+        // Snapshot the device.
+        new DeviceSnapshotHandler().snapshotDevice(getDevice(), snapshotId);
         // Restore the device.
-        boolean restoreRes =
-                new DeviceSnapshotHandler().restoreSnapshotDevice(getDevice(), snapshotId);
-        assertTrue("Restore snapshot for device reset failed before powerwash", restoreRes);
+        new DeviceSnapshotHandler().restoreSnapshotDevice(getDevice(), snapshotId);
         CLog.d("Powerwash attempt after restore");
         long start = System.currentTimeMillis();
         boolean success = new DeviceResetHandler(getInvocationContext()).resetDevice(getDevice());
@@ -147,12 +142,9 @@
         // Verify that the device is back online.
         getDevice().executeShellCommand("echo test");
         // Snapshot the device>
-        boolean snapshotRes = new DeviceSnapshotHandler().snapshotDevice(getDevice(), snapshotId);
-        assertTrue("failed to snapshot", snapshotRes);
+        new DeviceSnapshotHandler().snapshotDevice(getDevice(), snapshotId);
         // Restore the device.
-        boolean restoreRes =
-                new DeviceSnapshotHandler().restoreSnapshotDevice(getDevice(), snapshotId);
-        assertTrue("Restore snapshot after powerwash for device reset failed", restoreRes);
+        new DeviceSnapshotHandler().restoreSnapshotDevice(getDevice(), snapshotId);
         // Verify that the device is back online.
         getDevice().executeShellCommand("echo test");
     }
diff --git a/tools/create_base_image_gce.sh b/tools/create_base_image_gce.sh
index ba189cd..607c1ad 100755
--- a/tools/create_base_image_gce.sh
+++ b/tools/create_base_image_gce.sh
@@ -37,6 +37,19 @@
 # Stuff we need to get build support
 sudo apt install -y debhelper ubuntu-dev-tools equivs "${extra_packages[@]}"
 
+function install_bazel() {
+  # From https://bazel.build/install/ubuntu
+  echo "Installing bazel"
+  sudo apt install apt-transport-https curl gnupg -y
+  curl -fsSL https://bazel.build/bazel-release.pub.gpg | gpg --dearmor >bazel-archive-keyring.gpg
+  sudo mv bazel-archive-keyring.gpg /usr/share/keyrings
+  echo "deb [arch=amd64 signed-by=/usr/share/keyrings/bazel-archive-keyring.gpg] https://storage.googleapis.com/bazel-apt stable jdk1.8" | sudo tee /etc/apt/sources.list.d/bazel.list
+  # bazel needs the zip command to gather test outputs but doesn't depend on it
+  sudo apt-get update && sudo apt-get install -y bazel zip unzip
+}
+
+install_bazel
+
 # Resize
 sudo apt install -y cloud-utils
 sudo apt install -y cloud-guest-utils
diff --git a/tools/launch_cvd_arm64_server_docker.sh b/tools/launch_cvd_arm64_server_docker.sh
index ac1a939..d3fcceb 100755
--- a/tools/launch_cvd_arm64_server_docker.sh
+++ b/tools/launch_cvd_arm64_server_docker.sh
@@ -18,9 +18,10 @@
 color_yellow="\033[0;33m"
 
 # validate number of arguments
-if [ "$#" -lt 1 ] || [ "$#" -gt 3 ]; then
-  echo "This script requires 1 mandatory and 2 optional parameters,"
-  echo "server address and optionally cvd instances per docker, and number of docker instances to invoke"
+if [ "$#" -lt 1 ] || [ "$#" -gt 4 ]; then
+  echo "This script requires 1 mandatory and 3 optional parameters,"
+  echo "server address and optionally cvd instances per docker, and number of " \
+       "docker instances to invoke, and vendor_boot image to replace."
   exit 1
 fi
 
@@ -28,6 +29,7 @@
 # $1: ARM server address
 # $2: CVD Instance number per docker (Optional, default is 1)
 # $3: Docker Instance number (Optional, default is 1)
+# $4: Vendor Boot Image path (Optional, default is "")
 server=$1
 
 if [ "$#" -lt 2 ]; then
@@ -42,6 +44,12 @@
  num_dockers=$3
 fi
 
+if [ "$#" -lt 4 ]; then
+ vendor_boot_image=""
+else
+ vendor_boot_image=$4
+fi
+
 # set img_dir and cvd_host_tool_dir
 img_dir=${ANDROID_PRODUCT_OUT:-$PWD}
 cvd_host_tool_dir=${ANDROID_HOST_OUT:+"$ANDROID_HOST_OUT/../linux_musl-arm64"}
@@ -58,6 +66,10 @@
   cvd_home_files=($(rsync -rzan --recursive $img_dir/bootloader --out-format="%n" $img_dir/*.img $server:~/$cvd_home_dir --info=name2 | awk '{print $1}'))
 fi
 
+if [[ $vendor_boot_image != "" ]]; then
+  scp $vendor_boot_image $server:~/$cvd_home_dir/vendor_boot.img
+fi
+
 # upload cvd-host_package.tar.gz into ARM server
 temp_dir=/tmp/cvd_dist
 rm -rf $temp_dir
diff --git a/vsoc_arm64_only/slim/aosp_cf.mk b/vsoc_arm64_only/slim/aosp_cf.mk
index 821b79e..1b9ab17 100644
--- a/vsoc_arm64_only/slim/aosp_cf.mk
+++ b/vsoc_arm64_only/slim/aosp_cf.mk
@@ -37,7 +37,6 @@
 #
 # All components inherited here go to vendor image
 #
-LOCAL_PREFER_VENDOR_APEX := true
 $(call inherit-product, device/google/cuttlefish/shared/slim/device_vendor.mk)
 
 #
diff --git a/vsoc_riscv64/phone/aosp_cf.mk b/vsoc_riscv64/phone/aosp_cf.mk
index b5af1b9..668ea09 100644
--- a/vsoc_riscv64/phone/aosp_cf.mk
+++ b/vsoc_riscv64/phone/aosp_cf.mk
@@ -36,7 +36,6 @@
 #
 # All components inherited here go to vendor image
 #
-LOCAL_PREFER_VENDOR_APEX := true
 LOCAL_ENABLE_WIDEVINE := false
 $(call inherit-product, device/google/cuttlefish/shared/phone/device_vendor.mk)
 
diff --git a/vsoc_riscv64/slim/aosp_cf.mk b/vsoc_riscv64/slim/aosp_cf.mk
index ce71274..285317a 100644
--- a/vsoc_riscv64/slim/aosp_cf.mk
+++ b/vsoc_riscv64/slim/aosp_cf.mk
@@ -37,7 +37,6 @@
 #
 # All components inherited here go to vendor image
 #
-LOCAL_PREFER_VENDOR_APEX := true
 LOCAL_ENABLE_WIDEVINE := false
 $(call inherit-product, device/google/cuttlefish/shared/slim/device_vendor.mk)
 
diff --git a/vsoc_x86_64/phone/aosp_cf.mk b/vsoc_x86_64/phone/aosp_cf.mk
index 41d7d29..d3cfcb9 100644
--- a/vsoc_x86_64/phone/aosp_cf.mk
+++ b/vsoc_x86_64/phone/aosp_cf.mk
@@ -36,7 +36,6 @@
 #
 # All components inherited here go to vendor image
 #
-LOCAL_PREFER_VENDOR_APEX := true
 $(call inherit-product, device/google/cuttlefish/shared/phone/device_vendor.mk)
 
 # Nested virtualization support
@@ -62,3 +61,10 @@
 PRODUCT_VENDOR_PROPERTIES += \
     ro.soc.manufacturer=$(PRODUCT_MANUFACTURER) \
     ro.soc.model=$(PRODUCT_DEVICE)
+
+# TODO(b/350000347) Enable Soong defined system image from coverage build
+ifneq ($(CLANG_COVERAGE),true)
+ifneq ($(NATIVE_COVERAGE),true)
+PRODUCT_SOONG_DEFINED_SYSTEM_IMAGE := aosp_cf_system_x86_64
+endif # NATIVE_COVERAGE
+endif # CLANG_COVERAGE
diff --git a/vsoc_x86_64_only/slim/aosp_cf.mk b/vsoc_x86_64_only/slim/aosp_cf.mk
index 47b94c2..b38e918 100644
--- a/vsoc_x86_64_only/slim/aosp_cf.mk
+++ b/vsoc_x86_64_only/slim/aosp_cf.mk
@@ -37,7 +37,6 @@
 #
 # All components inherited here go to vendor image
 #
-LOCAL_PREFER_VENDOR_APEX := true
 $(call inherit-product, device/google/cuttlefish/shared/slim/device_vendor.mk)
 
 #